Click here to Skip to main content
15,883,843 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
I have written C++ code that is able to read from and write to a com port using the Win32 API method ReadFile. The code is for the purpose of sending HPGL instructions to a legacy graphics plotter and retrieving status data from it. I have ported this code to C# and added DllImport definitions for all the Win32 API methods that the code requires and everything is working fine except for ReadFile which never retrieves any data from the port and never returns an error or throws an exception. I have created a pair of small apps that make all the necessary Win32 API calls to communicate with the plotter, one in C++ which works correctly and the other in C# which fails consistently. This is the C++ app, followed by the C# app. I've been looking at this code for a few days trying various changes to the ReadFile call (Unicode vs. Ansi, out vs. ref, etc.) and nothing has changed. I've also done extensive searching online but I haven't been able to find anything relevant. I'd appreciate any suggestions anyone might have.

What I have tried:

int _tmain(int argc, _TCHAR* argv[])
{
    HANDLE hSerial = CreateFile (TEXT("\\\\.\\COM3"),
                                 GENERIC_READ | GENERIC_WRITE,
                                 0,
                                 NULL,
                                 OPEN_EXISTING,
                                 FILE_ATTRIBUTE_NORMAL,
                                 NULL);
	
    DCB dcbSerialParams;
    COMMTIMEOUTS timeouts;
    ZeroMemory (&dcbSerialParams, sizeof (DCB));
    ZeroMemory (&timeouts, sizeof (COMMTIMEOUTS));

    dcbSerialParams.DCBlength = sizeof (dcbSerialParams);
    if (GetCommState(hSerial, &dcbSerialParams) == 0)
    {
        CloseHandle (hSerial);
        return 1;
    }

    dcbSerialParams.BaudRate = 9600;
    dcbSerialParams.ByteSize = 8;
    dcbSerialParams.StopBits = ONESTOPBIT;
    dcbSerialParams.Parity = NOPARITY;
    if (SetCommState (hSerial, &dcbSerialParams) == 0)
    {
        CloseHandle (hSerial);
        return 2;
    }
   
    // Set COM port timeout settings
    timeouts.ReadIntervalTimeout = 50;
    timeouts.ReadTotalTimeoutConstant = 50;
    timeouts.ReadTotalTimeoutMultiplier = 10;
    timeouts.WriteTotalTimeoutConstant = 50;
    timeouts.WriteTotalTimeoutMultiplier = 10;
    if (SetCommTimeouts (hSerial, &timeouts) == 0)
    {
        CloseHandle (hSerial);
        return 3;
    }

    DWORD dwBytesWritten = 0;
    int iResult = WriteFile (hSerial, (LPVOID)"OI;", 3, &dwBytesWritten, NULL);

    FlushFileBuffers (hSerial);

    char szBuffer[10];
    ::ZeroMemory (szBuffer, 10);
	DWORD   dwBytesRead  = 0,
            dwErrorFlags = 0;
	COMSTAT comstat;
    ::ZeroMemory ((void*)&comstat, sizeof (COMSTAT));
    ClearCommError (hSerial, &dwErrorFlags, &comstat);

    //int iBytesRead = ReadData ((void*) szBuffer, BUFFER_SIZE - 1);
    int iRepeat = 100; // For a maximum of 1 second, a long time for some input to show up
    int iLastInQue = 0;
    while (iRepeat--)
    {
        iLastInQue = comstat.cbInQue;
        ClearCommError (hSerial, &dwErrorFlags, &comstat);

        if (comstat.cbInQue > 0 &&
            comstat.cbInQue == iLastInQue)
        {
            break;
        }
        Sleep (10);
    }

	dwBytesRead = (DWORD) comstat.cbInQue;

	ReadFile (hSerial, (void*)szBuffer, dwBytesRead, &dwBytesRead, NULL);
    puts (szBuffer);

    CloseHandle (hSerial);

    return 0;
}


class Program
{
    #region DllImport statements
    [DllImport ("Kernel32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
    private static extern UIntPtr CreateFileW (string lpFileName, UInt32 dwDesiredAccess, UInt32 dwShareMode, UIntPtr lpSecurityAttributes,
                                               UInt32 dwCreationDisposition, UInt32 dwFlagsAndAttributes, UIntPtr hTemplateFile);

    [StructLayout (LayoutKind.Sequential)]
    private struct COMSTAT
    {
        public uint uiCtsHold;
        public uint uiDsrHold;
        public uint uiRlsdHold;
        public uint uiXoffHold;
        public uint uiXoffSent;
        public uint uiEof;
        public uint uiTxim;
        public UInt32 uiFlags;
        public UInt32 cbInQue;
        public UInt32 cbOutQue;
    }
    [DllImport ("Kernel32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
    private static extern int ClearCommError (UIntPtr hFile, out UInt32 lpErrors, out COMSTAT lpStat);

    [DllImport ("Kernel32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
    private static extern int CloseHandle (UIntPtr hObject);

    [DllImport ("Kernel32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
    private static extern int FlushFileBuffers (UIntPtr hFile);

    [StructLayout (LayoutKind.Sequential)]
    public struct DCB
    {
        public UInt32 DCBlength;    // sizeof(DCB)
        public UInt32 BaudRate;     // Baudrate at which running
        public UInt32 uiFlagBits;   // Defined separately
        public UInt16 wReserved;    // Not currently used
        public UInt16 XonLim;       // Transmit X-ON threshold
        public UInt16 XoffLim;      // Transmit X-OFF threshold
        public byte ByteSize;       // Number of bits/byte, 4-8
        public byte Parity;         // 0-4=None,Odd,Even,Mark,Space
        public byte StopBits;       // 0,1,2 = 1, 1.5, 2
        public char XonChar;        // Tx and Rx X-ON character
        public char XoffChar;       // Tx and Rx X-OFF character
        public char ErrorChar;      // Error replacement char
        public char EofChar;        // End of Input character
        public char EvtChar;        // Received Event character
        public UInt16 wReserved1;   // Fill for now.
    }
    [DllImport ("Kernel32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
    private static extern int GetCommState (UIntPtr hFile, out DCB lpDCB);

    [DllImport ("Kernel32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
    private static extern int ReadFile (UIntPtr hFile, out string lpBuffer, UInt32 nNumberOfBytesToRead, out UInt32 lpNumberOfBytesRead, UIntPtr lpOverlapped);

    [DllImport ("Kernel32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
    private static extern int SetCommState (UIntPtr hFile, ref DCB lpDCB);

    private class COMMTIMEOUTS
    {
        public UInt32 ReadIntervalTimeout = 0;
        public UInt32 ReadTotalTimeoutMultiplier = 0;
        public UInt32 ReadTotalTimeoutConstant = 0;
        public UInt32 WriteTotalTimeoutMultiplier = 0;
        public UInt32 WriteTotalTimeoutConstant = 0;
    }
    [DllImport ("Kernel32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
    private static extern int SetCommTimeouts (UIntPtr hFile, ref COMMTIMEOUTS lpCommTimeouts);

    [DllImport ("Kernel32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
    private static extern int WriteFile (UIntPtr hFile, string lpBuffer, UInt32 nNumberOfBytesToWrite, out UInt32 lpNumberOfBytesWritten, UIntPtr lpOverlapped);
    #endregion

    // This code fails to retrieve the plotter's ID string
    static void Main (string[] args)
    {
        const uint OPEN_EXISTING = 3;
        const uint GENERIC_READ = 0x80000000;
        const uint GENERIC_WRITE = 0x40000000;
        const uint FILE_ATTRIBUTE_NORMAL = 0x00000080;
        const int NOPARITY = 0;
        const int ONESTOPBIT = 0;

        UIntPtr hSerial = CreateFileW ("COM3",
                                       GENERIC_READ | GENERIC_WRITE,
                                       0,
                                       (UIntPtr)null,
                                       OPEN_EXISTING,
                                       FILE_ATTRIBUTE_NORMAL,
                                       (UIntPtr)null);

        DCB dcbSerialParams = new DCB ();
        COMMTIMEOUTS timeouts = new COMMTIMEOUTS ();

        dcbSerialParams.DCBlength = (uint)Marshal.SizeOf (dcbSerialParams);
        if (GetCommState (hSerial, out dcbSerialParams) == 0)
        {
            CloseHandle (hSerial);
            return;
        }

        dcbSerialParams.BaudRate = 9600;
        dcbSerialParams.ByteSize = 8;
        dcbSerialParams.StopBits = ONESTOPBIT;
        dcbSerialParams.Parity   = NOPARITY;
        if (SetCommState (hSerial, ref dcbSerialParams) == 0)
        {
            CloseHandle (hSerial);
            return;
        }

        // Set COM port timeout settings
        timeouts.ReadIntervalTimeout = 50;
        timeouts.ReadTotalTimeoutConstant = 50;
        timeouts.ReadTotalTimeoutMultiplier = 10;
        timeouts.WriteTotalTimeoutConstant = 50;
        timeouts.WriteTotalTimeoutMultiplier = 10;
        if (SetCommTimeouts (hSerial, ref timeouts) == 0)
        {
            CloseHandle (hSerial);
            return;
        }

        UInt32 dwBytesWritten = 0;
        int iResult = WriteFile (hSerial, "OI;", 3, out dwBytesWritten, (UIntPtr)null);

        FlushFileBuffers (hSerial);

        string strBuffer;
        UInt32 dwBytesRead = 0,
               dwErrorFlags = 0;
        COMSTAT comstat;
        ClearCommError (hSerial, out dwErrorFlags, out comstat);

        //int iBytesRead = ReadData ((void*) szBuffer, BUFFER_SIZE - 1);
        int iRepeat = 100; // For a maximum of 1 second, a long time for some input to show up
        int iLastInQue = 0;
        while (iRepeat-- > 0)
        {
            iLastInQue = (int)comstat.cbInQue;
            ClearCommError (hSerial, out dwErrorFlags, out comstat);

            if (comstat.cbInQue > 0 &&
                comstat.cbInQue == iLastInQue)
            {
                break;
            }
            Thread.Sleep (10);
        }

        dwBytesRead = (UInt32)comstat.cbInQue;

        // ReadFile consistently returns no data and no indication of failure
        ReadFile (hSerial, out strBuffer, dwBytesRead, out dwBytesRead, (UIntPtr)null);
        Console.WriteLine (strBuffer);

        CloseHandle (hSerial);
    }
}
Posted
Updated 19-Jul-21 10:18am
Comments
11917640 Member 15-Nov-20 6:40am    
Run portmon program https://docs.microsoft.com/en-us/sysinternals/downloads/portmon and compare behavior of working and non-working programs.

Why?
Why not just use the built in SerialPort class?
SerialPort Class (System.IO.Ports) | Microsoft Docs[^]
 
Share this answer
 
Comments
Greggo 14-Nov-20 19:41pm    
Because I've tried and haven't been able to get it to work. Also because I already have C++ code that has been proven to be reliable and see no reason why I shouldn't be able to port it to C# using P/Invoke, and because after that porting everything works EXCEPT for ReadFile. That's why not.
Quote:
C#
[DllImport ("Kernel32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
private static extern int ReadFile (UIntPtr hFile, out string lpBuffer, UInt32 nNumberOfBytesToRead, out UInt32 lpNumberOfBytesRead, UIntPtr lpOverlapped);
That is not a valid P/Invoke declaration for the ReadFile function. The lpBuffer parameter needs to be a buffer allocated by your code which receives the bytes read from the file.
C#
[DllImport ("Kernel32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
private static extern int ReadFile (UIntPtr hFile, ref byte[] lpBuffer, UInt32 nNumberOfBytesToRead, out UInt32 lpNumberOfBytesRead, UIntPtr lpOverlapped);
...
byte[] buffer = new byte[10];
ReadFile(hSerial, ref buffer, buffer.Length, out dwBytesRead, (UIntPtr)null);
Alternatively, you can use the handle returned from CreateFile to construct a FileStream instance, which you can then use to read from and write to the file.
FileStream Constructor (System.IO) | Microsoft Docs[^]

NB: You should really be using SafeFileHandle instead of IntPtr for the file handle:
SafeFileHandle Class (Microsoft.Win32.SafeHandles) | Microsoft Docs[^]
Why SafeHandle? | Microsoft Docs[^]
 
Share this answer
 
Comments
Greggo 24-Nov-20 14:07pm    
This made a difference, but 2 problems remain:

The CreateFileW function call consistently results in an exception with this message:
"An unhandled exception of type 'System.AccessViolationException' occurred in WorkingCodeCS.exe
Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt."

The other problem is that the call to ClearCommError (hSerial, out dwErrorFlags, out comstat) always returns a comstat.cbInQue value of 0, while in the C++ code this call worked properly and always returned a non-zero value when there was data to be retrieved.
Hello.
Any update in the subject?
 
Share this answer
 

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