Reputation: 2774
I am making a simple RS-232 test app in C#. After having problems with .NET SerialPort
, I decided to call Win32 APIs directly.
I am using overlapped I/O. It is working 70% of the time, and the other time the inbound data isn't written to the buffer.
My test app opens the COM port this way:
_handle = CreateFile(
@"\\.\COM1",
FileAccess.ReadWrite,
FileShare.None,
IntPtr.Zero,
FileMode.Open,
EFileAttributes.Overlapped,
IntPtr.Zero
);
Note the overlapped flag.
Then the app sets COM parameters. GetCommState
, set baud, 0 stop bit, no parity, disable all flow control and RTS and DTR (all of this except baud is default anyway), then SetCommState
.
Next the app sets timeout via SetCommTimeouts
, and ReadIntervalTimeout
is 1ms, all others are zero.
And then the app cycles DTR via EscapeCommFunction
to CLRDTR
, and a 200ms sleep, and EscapeCommFunction
to SETDTR
.
All of this sets up the COM port well. When connected to a shell, I/O works perfectly, because messages are short. When messages have any significant length, say 30 bytes at once, I am encountering problems with overlapped I/O response on ReadFile
.
My rx code is below.
Problem The data is coming in properly, the overlapped operation completes as expected, the length from the overlapped operation is always correct, but ioBuffer
30% of the time isn't filled and remains all zeros.
Seems to be pinvoke. While I had problems using .NET SerialPort
, losing data like this wasn't an issue.
Anyone spot something wrong?
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)
{
}
And pinvoke signature
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool ReadFile(
IntPtr hFile,
[Out] byte[] lpBuffer,
int nNumberOfBytesToRead,
[Out] out int lpNumberOfBytesRead,
ref NativeOverlapped lpOverlapped
);
Upvotes: 1
Views: 1072
Reputation: 2774
The outbound data of ReadFile
is possibly getting moved by the GC.
PInvoke requires OUT parameters used by overlapped I/O to be pinned.
Fixed code:
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();
Upvotes: 1