Introduction
I recently read CLR via C# by J. Richter dedicated to the internals of .NET 2.0 and the C# peculiarities deriving from it. I’ve decided to practice a little and write my new application in C#. The part of this application I present here. It’s an asynchronous named pipe server using overlapped I/O plus a synchronous (or they also say blocking) named pipe client.
Problems encountered
The first problem encountered was the fact that the only example by John Korres of using overlapped structure written in VB.NET provides asynchronous calls for two Win32 API functions ConnectNamedPipe
and DisconnectNamedPipe
. Read and write from and to a named pipe server implemented in a blocking mode using the System.IO.FileStream
.NET class. I tried to use the BeginRead
and BeginWrite
methods but it remained unclear how these two interact with the NativeOverlapped
structure if they do interact at all. So eventually, I adopted this sample code written in C from MSDN.
And that’s how the second problem came up. After the code was re-written, I found out that the GetOverlappedResult
Win32 API function works incorrectly due to the fact that the pointer to the overlapped structure is not fixed, i.e., you have to work with the unmanaged memory of the process to make it work like in plain old C. Investigating farther, I understood that all buffers for the ReadFile
and WriteFile
Win32 API functions must also use unmanaged memory. I decided to use pointers and compile the application with the unsafe option; that upset me a bit because my code looked like almost plain old C to me now. And that was not just the case. Byte*
to IntPtr
cast did not work correctly either. So I switched to using IntPtr
instead for the overlapped structure and buffers that allowed me to remove this odious unsafe parameter. And I was rewarded. Now the adopted code worked exactly like the sample from MSDN.
The last but not the least problem which I have to mention here is permissions for the named pipe server. Using the default permissions won’t allow interprocess communication between two computers, so look up buildSecurityAttributes
in NamedPipeServer.cs. I won’t dwell on it here since it’s not the main point of the article.
Using the code
The following code extract shows the main server loop and exposes the peculiarities of asynchronous programming. The actual overlapped magic happens when the ReadFile
Win32 API inside of the selectOperation
method returns FALSE
and GetLastError
returns ERROR_IO_PENDING
which is not an error but a signal that a ReadFile
asynchronous call has not succeeded to complete reading so we have to call the GetOverlappedResult
Win32 API to find out if the Operating System has put cbRet
bytes into mPipeServer[i].Pipe.chRequest
. If it hasn’t, we call the DisconnectAndReconnect
method.
Another scenario happens if the application runs under Visual Studio 2005, no matter if it’s the release or debug configuration. To actually witness the difference, uncomment MessageBox.Show(“GetOverlappedResult:...”)
altogether with the line above and run the executable. Afterwards, run it under Visual Studio in the debug configuration. You might not encounter it though.
while (mListenning)
{
i = (UInt32)WaitHandle.WaitAny(hEvents);
if (i < 0 || i > (mNumOfInstances - 1))
{
ThrowWin32Exception ("Index out of range.");
return;
}
Debug.WriteLine("Instance -> " + i.ToString());
#region ERROR_IO_PENDING == TRUE
if (mPipeServer[i].Pipe.fPendingIO)
{
fSuccess = NativeMethods.GetOverlappedResult(
mPipeServer[i].Pipe.hPipeInst,
mPipeServer[i].Pipe.OverlappedPtr,
out cbRet,
false);
switch (mPipeServer[i].Pipe.dwState)
{
case CONNECTING_STATE:
Debug.WriteLine("CONNECTING_STATE");
if (!fSuccess)
ThrowWin32Exception("CONNECTING_STATE_ERROR");
mPipeServer[i].Pipe.dwState = READING_STATE;
break;
case READING_STATE:
Debug.WriteLine("READING_STATE " + cbRet.ToString());
if (!fSuccess || cbRet == 0)
{
DebugWin32Exception("READING_STATE");
DisconnectAndReconnect(i);
continue;
}
mPipeServer[i].Pipe.dwState = WRITING_STATE;
break;
case WRITING_STATE:
Debug.WriteLine("WRITING_STATE " + cbRet.ToString());
if (!fSuccess || cbRet != mPipeServer[i].Pipe.cbToWrite)
{
DebugWin32Exception("WRITING_STATE");
DisconnectAndReconnect(i);
continue;
}
mPipeServer[i].Pipe.dwState = READING_STATE;
break;
default:
ThrowWin32Exception("runInstance - INVALID PIPE STATE");
break;
}
}
#endregion
if (!selectOperation((Int32) i) )
{
DisconnectAndReconnect(i);
}
}
Points of interest
While testing the application, it turned out that it behaves differently when run under Visual Studio 2005.