Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Asynchronous Named Pipe Server Using Overlapped I/O in C#

3.13/5 (7 votes)
20 Jul 2008CPOL3 min read 1   1.2K  
Asynchronous named pipe server in C#.

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.

C#
while (mListenning)
{
    i = (UInt32)WaitHandle.WaitAny(hEvents);
    // determines which pipe instance

    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);
           //if (cbRet > 0)
           //     MessageBox.Show("GetOverlappedResult: " +
           //     Marshal.PtrToStringAnsi(mPipeServer[i].Pipe.chRequest));

        switch (mPipeServer[i].Pipe.dwState)
        {
            // Pending connect operation 
            case CONNECTING_STATE:
                Debug.WriteLine("CONNECTING_STATE");
                if (!fSuccess)
                    ThrowWin32Exception("CONNECTING_STATE_ERROR");
                //SetEventAsync(CONNECTING_STATE, mPipeServer[i].Pipe.chRequest,
                //              mPipeServer[i].Pipe.cbRead);
                //STEP #0
                mPipeServer[i].Pipe.dwState = READING_STATE;
                break;
            // Pending read operation 
            case READING_STATE:
                Debug.WriteLine("READING_STATE " + cbRet.ToString());
                if (!fSuccess || cbRet == 0)
                {
                    DebugWin32Exception("READING_STATE");
                    DisconnectAndReconnect(i);
                    continue;
                }
                //STEP #1
                //NB! If we got here and cbRet > 0
                //    the mPipeServer[i].Pipe.chRequest already has client data 
                mPipeServer[i].Pipe.dwState = WRITING_STATE;
                break;
            // Pending write operation 
            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;
        }

    } //IO_PENDING
    #endregion

    if (!selectOperation((Int32) i) )
    //does ReadFile or WriteFile depending on the pipe instance state
    {
        // An error occurred; disconnect from the client. 
        DisconnectAndReconnect(i);
    }
}

Points of interest

While testing the application, it turned out that it behaves differently when run under Visual Studio 2005.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)