Почему С# PInvoke с перекрывающимся вводом-выводом последовательного порта иногда дает мне все нули

Я делаю простое тестовое приложение RS-232 на С#. После проблем с .NET SerialPort я решил напрямую вызывать Win32 API.

Я использую перекрывающийся ввод-вывод. Он работает 70% времени, а в остальное время входящие данные не записываются в буфер.

Мое тестовое приложение открывает COM-порт следующим образом:

_handle = CreateFile(
    @"\\.\COM1",
    FileAccess.ReadWrite,
    FileShare.None,
    IntPtr.Zero,
    FileMode.Open,
    EFileAttributes.Overlapped,
    IntPtr.Zero
    );

Обратите внимание на перекрывающийся флаг.

Затем приложение устанавливает параметры COM. GetCommState, установить скорость, 0 стоповых бит, без четности, отключить все управление потоком и RTS и DTR (все это, кроме скорости, по умолчанию), затем SetCommState.

Затем приложение устанавливает тайм-аут через SetCommTimeouts, а ReadIntervalTimeout составляет 1 мс, все остальные равны нулю.

Затем приложение циклически переключает DTR с EscapeCommFunction на CLRDTR, затем на 200 мс засыпает и с EscapeCommFunction на SETDTR.

Все это хорошо настраивает COM-порт. При подключении к оболочке ввод-вывод работает отлично, потому что сообщения короткие. Когда сообщения имеют значительную длину, скажем, 30 байт за раз, я сталкиваюсь с проблемами с перекрывающимися ответами ввода-вывода на ReadFile.

Мой код rx ниже.

Проблема Данные поступают правильно, перекрывающаяся операция завершается должным образом, длина перекрывающейся операции всегда верна, но ioBuffer 30% времени не заполняется и остается нулевой.

Кажется, это пинвок. Хотя у меня были проблемы с использованием .NET SerialPort, такая потеря данных не была проблемой.

Кто-нибудь заметил что-то не так?

DataReceivedArgs args = new DataReceivedArgs();
ManualResetEvent completionEvent = new ManualResetEvent(false);
NativeOverlapped nol = new NativeOverlapped();
nol.EventHandle = completionEvent.SafeWaitHandle.DangerousGetHandle();
int dontCare = 0;

try
{
    for (; ; )
    {
        completionEvent.Reset();
        uint bytesRead = 0;

        nol.InternalHigh = IntPtr.Zero;
        nol.InternalLow = IntPtr.Zero;
        nol.OffsetHigh = 0;
        nol.OffsetLow = 0;

        byte[] ioBuffer = new byte[1024];

        if (!ReadFile(_handle.DangerousGetHandle(), ioBuffer, ioBuffer.Length, out dontCare, ref nol))
        {
            int lastError = Marshal.GetLastWin32Error();
            if (lastError != OperationInProgress)
            {
                if (lastError != ErrorInvalidHandle)
                {
                    Win32Exception ex = new Win32Exception(lastError);
                    MessageBox.Show("ReadFile failed. Error: " + ex.Message);
                }
                break;
            }

            completionEvent.WaitOne();
            // Have tried sleeping here to see if there is timing involved, no luck

            if (!GetOverlappedResult(_handle.DangerousGetHandle(), ref nol, out bytesRead, true))
            {
                bytesRead = 0;
            }
        }
        else
        {
            throw new IOException();
        }

        if (bytesRead > 0)
        {
            byte[] sizedBuffer = new byte[bytesRead];
            Array.Copy(ioBuffer, 0, sizedBuffer, 0, bytesRead);
            args.Data = sizedBuffer;
            DataReceived?.Invoke(this, args);
        }
    }
}
catch (ThreadAbortException)
{
}

И пинвок подпись

[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool ReadFile(
    IntPtr hFile, 
    [Out] byte[] lpBuffer, 
    int nNumberOfBytesToRead, 
    [Out] out int lpNumberOfBytesRead,
    ref NativeOverlapped lpOverlapped
    );

person jws    schedule 15.11.2019    source источник
comment
Почему бы не решить вашу проблему с классом SerialPort? docs.microsoft.com/en-us/dotnet/ api/system.io.ports.serialport   -  person Wim ten Brink    schedule 15.11.2019
comment
У SerialPort есть несколько конструктивных проблем. Вы можете поискать в Интернете, чтобы узнать, что они из себя представляют. Я столкнулся с несколькими хорошо известными проблемами с ненадежными уведомлениями и взаимоблокировками потоков пользовательского интерфейса с очень простым вариантом использования.   -  person jws    schedule 15.11.2019


Ответы (1)


Исходящие данные ReadFile, возможно, перемещаются сборщиком мусора.

PInvoke требует, чтобы параметры OUT, используемые перекрывающимся вводом-выводом, были закреплены.

Фиксированный код:

DataReceivedArgs args = new DataReceivedArgs();
ManualResetEvent completionEvent = new ManualResetEvent(false);
byte[] ioBuffer = new byte[1024];
NativeOverlapped nol = new NativeOverlapped
{
    EventHandle = completionEvent.SafeWaitHandle.DangerousGetHandle()
};

try
{
    for (; ; )
    {
        completionEvent.Reset();
        uint bytesRead = 0;

        GCHandle pin = GCHandle.Alloc(ioBuffer, GCHandleType.Pinned);

        try
        {
            if (!ReadFile(_handle.DangerousGetHandle(), ioBuffer, ioBuffer.Length, out int dontCare, ref nol))
            {
                int lastError = Marshal.GetLastWin32Error();
                if (lastError != OperationInProgress)
                {
                    if (lastError != ErrorInvalidHandle)
                    {
                        Win32Exception ex = new Win32Exception(lastError);
                        MessageBox.Show("ReadFile failed. Error: " + ex.Message);
                    }
                    break;
                }

                completionEvent.WaitOne();

                if (!GetOverlappedResult(_handle.DangerousGetHandle(), ref nol, out bytesRead, true))
                {
                    bytesRead = 0;
                }
            }
            else
            {
                throw new IOException();
            }

            if (bytesRead > 0)
            {
                byte[] sizedBuffer = new byte[bytesRead];
                Array.Copy(ioBuffer, 0, sizedBuffer, 0, bytesRead);
                args.Data = sizedBuffer;
                DataReceived?.Invoke(this, args);
            }

        }
        finally
        {
            pin.Free();
        }
    }
}
catch (ThreadAbortException)
{
}

completionEvent.Dispose();
person jws    schedule 15.11.2019
comment
У вас есть рабочий пример кода? Я работаю над чем-то подобным, мне бы действительно было на что ссылаться - person Pratik Gaikwad; 24.01.2020
comment
У меня его нет под рукой. Не хватает только кода, который запускает это как рабочий поток после открытия порта. Переменная-член DataReceived — это стандартное событие. delegate void DataReceivedDelegate(object sender, DataReceivedArgs args); event DataReceivedDelegate DataReceived; и DataRecievedArgs — это простая оболочка EventArgs (class DataReceivedArgs : EventArgs...). WriteFile для отправки был простым. - person jws; 29.01.2020
comment
Я не понимаю, почему ioBuffer собирает мусор, если он все еще является ссылкой в ​​третьей строке byte[] ioBuffer = new byte[1024];. - person Rick; 19.03.2021
comment
Выделения, на которые ссылаются Rick, могут перемещаться в памяти сборщиком мусора, когда они откреплены. - person jws; 22.03.2021