【问题标题】:Why is C# PInvoke with serial port overlapped I/O sometimes giving me all zeros为什么带有串行端口重叠 I/O 的 C# PInvoke 有时会给我全零
【发布时间】:2020-03-11 16:43:28
【问题描述】:

我正在用 C# 制作一个简单的 RS-232 测试应用程序。在 .NET SerialPort 遇到问题后,我决定直接调用 Win32 API。

我正在使用重叠 I/O。它有 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是1ms,其他都是零。

然后应用程序将 DTR 通过 EscapeCommFunction 循环到 CLRDTR,然后进行 200 毫秒的睡眠,然后将 EscapeCommFunction 循环到 SETDTR

所有这些都很好地设置了 COM 端口。当连接到 shell 时,I/O 工作得很好,因为消息很短。当消息具有任何显着长度时,例如一次 30 个字节,我会在 ReadFile 上遇到重叠 I/O 响应的问题。

我的 rx 代码如下。

问题 数据正确进入,重叠操作按预期完成,重叠操作的长度始终正确,但ioBuffer 30% 的时间未填充并保留全部为零。

似乎是pinvoke。虽然我在使用 .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)
{
}

和pinvoke签名

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

【问题讨论】:

  • 为什么不解决您对 SerialPort 类的问题? docs.microsoft.com/en-us/dotnet/api/system.io.ports.serialport
  • SerialPort 有几个设计问题。您可以在网上搜索以了解它们是什么。我用一个非常简单的用例解决了几个众所周知的问题,即通知不可靠和 UI 线程死锁。

标签: c# serial-port pinvoke overlapped-io


【解决方案1】:

ReadFile 的出站数据可能正在被 GC 移动。

PInvoke 需要固定重叠 I/O 使用的 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();

【讨论】:

  • 你有代码的工作示例吗?我正在做类似的事情,这真的会帮助我有一些东西可以参考
  • 我手边没有。所缺少的只是在打开端口后将其作为工作线程启动的代码。成员变量 DataReceived 是标准事件。 delegate void DataReceivedDelegate(object sender, DataReceivedArgs args); event DataReceivedDelegate DataReceived; 和 DataRecievedArgs 是 EventArgs (class DataReceivedArgs : EventArgs...) 的简单包装器。用于发送的 WriteFile 非常简单。
  • 我不明白为什么ioBuffer 在第三行byte[] ioBuffer = new byte[1024]; 中仍被引用时被垃圾收集。
  • Rick - 引用的分配可以在被取消固定时被 GC 在内存中移动。
猜你喜欢
  • 1970-01-01
  • 2023-03-19
  • 1970-01-01
  • 2017-07-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-07
相关资源
最近更新 更多