Greggo Ответов: 2

Как использовать win32 readfile в C# с помощью dllimport для чтения из последовательного порта


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.

Что я уже пробовал:

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);
    }
}

11917640 Member

Запустите программу portmon https://docs.microsoft.com/en-us/sysinternals/downloads/portmon и сравнить поведение рабочих и нерабочих программ.

2 Ответов

Рейтинг:
2

OriginalGriff

Почему?
Почему бы просто не использовать встроенный класс SerialPort?
Класс SerialPort (System.IO.Ports) | Microsoft Docs[^]


Greggo

Потому что я пытался и не смог заставить его работать. Также потому, что у меня уже есть код C++, который был доказан как надежный, и я не вижу причин, по которым я не мог бы перенести его на C# с помощью P/Invoke, и потому, что после этого переноса все работает, кроме ReadFile. Вот почему нет.

Рейтинг:
2

Richard Deeming

Цитата:
[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);
Это не является допустимым объявлением P/Invoke для ReadFile функция. То lpBuffer параметр должен быть буфером, выделенным вашим кодом, который получает байты, считанные из файла.
[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);
Кроме того, вы можете использовать дескриптор, возвращенный из CreateFile чтобы построить FileStream экземпляр, который затем можно использовать для чтения и записи в файл.
Конструктор FileStream (System.IO) | Microsoft Docs[^]

NB: Вы действительно должны использовать SafeFileHandle вместо IntPtr для дескриптора файла:
Класс SafeFileHandle (Microsoft.Win32.SafeHandles) | Microsoft Docs[^]
Почему SafeHandle? | Майкрософт Документы[^]