Click here to Skip to main content
15,900,816 members
Please Sign up or sign in to vote.
5.00/5 (1 vote)
I am trying to emulate communication between an existing piece of software and hardware, using Advanced Serial Port Monitor I was able to come up with this information about the communication.

In/out queue size 1024/512
Purge the serial port: RXABORT, RXCLEAR, TXABORT, TXCLEAR
Set timeouts: ReadInterval=-1, ReadTotalTimeoutMultiplier=0, ReadTotalTimeoutConstant=0, WriteTotalTimeoutMultiplier=0, WriteTotalTimeoutConstant=5000
Baud rate 115200
RTS on
DTR off
Data bits=8, Stop bits=1, Parity=None
Set chars: Eof=0x1A, Error=0x00, Break=0x00, Event=0x00, Xon=0x11, Xoff=0x13
Handflow: ControlHandShake=(), FlowReplace=(TRANSMIT_TOGGLE, RTS_CONTROL), XonLimit=256, XoffLimit=256


First I tried using the built in SerialPort class on it's own, but it lacks the versatility to be able to complete such a task, I then tried using System.Reflection to set the DCB fields, but whenever I come to a field that begins with a lowercase f, such as "fParity" or "fDtrControl", it gives me a NullReferenceException.

Is there any other way that I can modify these values to match the above?

This is the extension method I wrote enabling the modification of DCB fields
C#
internal static class SerialPortExtensions
    {
        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        public static void SetFlag(this SerialPort port, string flag, object value)
        {
            if (port == null)
                throw new NullReferenceException();
            if (port.BaseStream == null)
                throw new InvalidOperationException("Cannot change X chars until after the port has been opened.");

            try
            {
                // Get the base stream and its type which is System.IO.Ports.SerialStream
                object baseStream = port.BaseStream;
                Type baseStreamType = baseStream.GetType();

                // Get the Win32 file handle for the port
                SafeFileHandle portFileHandle = (SafeFileHandle)baseStreamType.GetField("_handle", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(baseStream);

                // Get the value of the private DCB field (a value type)
                FieldInfo dcbFieldInfo = baseStreamType.GetField("dcb", BindingFlags.NonPublic | BindingFlags.Instance);
                object dcbValue = dcbFieldInfo.GetValue(baseStream);

                // The type of dcb is Microsoft.Win32.UnsafeNativeMethods.DCB which is an internal type. We can only access it through reflection.
                Type dcbType = dcbValue.GetType();
                dcbType.GetField(flag).SetValue(dcbValue, value);

                // We need to call SetCommState but because dcbValue is a private type, we don't have enough
                //  information to create a p/Invoke declaration for it. We have to do the marshalling manually.

                // Create unmanaged memory to copy DCB into
                IntPtr hGlobal = Marshal.AllocHGlobal(Marshal.SizeOf(dcbValue));
                try
                {
                    // Copy their DCB value to unmanaged memory
                    Marshal.StructureToPtr(dcbValue, hGlobal, false);

                    // Call SetCommState
                    if (!SetCommState(portFileHandle, hGlobal))
                        throw new Win32Exception(Marshal.GetLastWin32Error());

                    // Update the BaseStream.dcb field if SetCommState succeeded
                    dcbFieldInfo.SetValue(baseStream, dcbValue);
                }
                finally
                {
                    if (hGlobal != IntPtr.Zero)
                        Marshal.FreeHGlobal(hGlobal);
                }
            }
            catch (SecurityException) { throw; }
            catch (OutOfMemoryException) { throw; }
            catch (Win32Exception) { throw; }
            catch (Exception)
            {
                throw;
            }
        }

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern bool SetCommState(SafeFileHandle hFile, IntPtr lpDCB);
    }


And this is where I initialize all the values:
C#
COM.Open();
            COM.SetFlag("BaudRate", (UInt32)115200);
            COM.SetFlag("StopBits", (byte)0);
            COM.SetFlag("ByteSize", (byte)8);
            COM.SetFlag("Parity", (byte)0);
            COM.SetFlag("fDtrControl", 0x00);
            COM.SetFlag("fRtsControl", 0x01);
            COM.SetFlag("XonChar", 0x11);
            COM.SetFlag("XoffChar", 0x13);
            COM.SetFlag("XonLim", (ushort)256);
            COM.SetFlag("XoffLim", (ushort)256);



EDIT
Upon further investigation it appears that the fields prefixed with f are not in the DCB structure used by SerialPort for some odd reason. However there is a flags field, but it is of type UInt32, so I'm unsure of how to use it, if anyone has further information on how to use this field, please submit it as an answer or comment.
Posted
Updated 23-Nov-17 9:14am
v3

1 solution

Alright I solved my problem, I'll post the code and explanation here if anyone is interested.

The DCB struct requires a C++ class known as a bitfield, which C# does not have, so instead they have a field called "Flags" stored as a UInt32. They have their own melarchy set up there as to how it's stored and read from, but they did put a method in the SerialStream called SetDcbFlag which accepts two ints. After digging through the .net source a bit, I managed to come up with a set of constants equating to the original DCB Fields and their new int code, I haven't yet made a list of values for these flags, but those can be easily found in the DCB Documentation. I used system.reflection to gain access to the method as it was an internal method only to be used by internal .NET source.

So here it is, the code, it's an extension class for the SerialPort class which is shipped stock with .NET 2.0+. My extension class adds three methods, SetField(string name, object value) which will set any of the fields that aren't prefixed with "f", SetFlag(int Flag, int Value) which will take care of those fields prefixed with "f" (I'll provide a list of constants for use with the Flag parameter), and finally UpdateComm() this will update the serial connection once you have changed all of your values, it was originally part of the SetField method, but it takes slightly longer to complete things if it's calling that every single time during initialization.

NOTE:
The serial port must be opened before using any of these methods!

Usage:
C#
SerialPort COM = new SerialPort("COM7");
COM.Open();
COM.DiscardInBuffer();
COM.DiscardOutBuffer();
COM.SetFlag(FBINARY, 1);
COM.SetFlag(FPARITY, 0);
COM.SetFlag(FDTRCONTROL, 0x00);
COM.SetFlag(FRTSCONTROL, 0x01);
COM.SetField("BaudRate", (UInt32)115200);
COM.SetField("StopBits", (byte)0);
COM.SetField("ByteSize", (byte)8);
COM.SetField("Parity", (byte)0);
COM.SetField("XonChar", (byte)0x11);
COM.SetField("XoffChar", (byte)0x13);
COM.SetField("EvtChar", (byte)0x1A);
COM.SetField("XonLim", (ushort)256);
COM.SetField("XoffLim", (ushort)256);
COM.UpdateComm();
/* Do Stuff */
COM.Close();


Constants:
C#
internal const int FBINARY = 0;
internal const int FPARITY = 1;
internal const int FOUTXCTSFLOW = 2;
internal const int FOUTXDSRFLOW = 3;
internal const int FDTRCONTROL = 4;
internal const int FDSRSENSITIVITY = 6;
internal const int FTXCONTINUEONXOFF = 7;
internal const int FOUTX = 8;
internal const int FINX = 9;
internal const int FERRORCHAR = 10;
internal const int FNULL = 11;
internal const int FRTSCONTROL = 12;
internal const int FABORTONOERROR = 14;
internal const int FDUMMY2 = 15;


And finally, the extension class:
C#
internal static class SerialPortExtensions
{
    [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
    public static void SetField(this SerialPort port, string field, object value)
    {
        if (port == null)
            throw new NullReferenceException();
        if (port.BaseStream == null)
            throw new InvalidOperationException("Cannot change fields until after the port has been opened.");

        try
        {
            object baseStream = port.BaseStream;
            Type baseStreamType = baseStream.GetType();

            FieldInfo dcbFieldInfo = baseStreamType.GetField("dcb", BindingFlags.NonPublic | BindingFlags.Instance);
            object dcbValue = dcbFieldInfo.GetValue(baseStream);

            Type dcbType = dcbValue.GetType();
            dcbType.GetField(field).SetValue(dcbValue, value);
            dcbFieldInfo.SetValue(baseStream, dcbValue);
        }
        catch (SecurityException) { throw; }
        catch (OutOfMemoryException) { throw; }
        catch (Win32Exception) { throw; }
        catch (Exception)
        {
            throw;
        }
    }

    [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
    public static void SetFlag(this SerialPort port, int flag, int value)
    {
        object BaseStream = port.BaseStream;
        Type SerialStream = BaseStream.GetType();
        SerialStream.GetMethod("SetDcbFlag", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(BaseStream, new object[] { flag, value });
    }

    [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
    public static void UpdateComm(this SerialPort port)
    {
        object baseStream = port.BaseStream;
        Type baseStreamType = baseStream.GetType();

        FieldInfo dcbFieldInfo = baseStreamType.GetField("dcb", BindingFlags.NonPublic | BindingFlags.Instance);
        object dcbValue = dcbFieldInfo.GetValue(baseStream);

        SafeFileHandle portFileHandle = (SafeFileHandle)baseStreamType.GetField("_handle", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(baseStream);
        IntPtr hGlobal = Marshal.AllocHGlobal(Marshal.SizeOf(dcbValue));
        try
        {
            Marshal.StructureToPtr(dcbValue, hGlobal, false);

            if (!SetCommState(portFileHandle, hGlobal))
                throw new Win32Exception(Marshal.GetLastWin32Error());
        }
        finally
        {
            if (hGlobal != IntPtr.Zero)
                Marshal.FreeHGlobal(hGlobal);
        }
    }

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool SetCommState(SafeFileHandle hFile, IntPtr lpDCB);
}
 
Share this answer
 
Comments
NikolaB 19-Dec-16 14:57pm    
Thanks! I could not find a better explanation and example. For me it is very useful. Вork perfectly and will use it immediately.
Sicppy 13-Jan-17 15:50pm    
No problem, it took me like a week of digging to get all this so I figured I'd post it here and save someone the trouble, glad someone got use out of it!
DuanChengHua 1-Apr-17 3:01am    
Good job!help me a lot.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900