Playing with .NET's Named Pipe Streams (with .NET-to-.NET and Win32-to-.NET samples)

June 22, 2008

I get my first deadline-free and commitment weekend in weeks and what do I do?  I spend 12 hours writing code.  I didn't leave my house yesterday.  I'm just that cool.  (And yes, it was completely worth it.  I had a great time just writing code.)

Anyway, I decided that I wanted to try to write to a named pipe from unmanaged C++ and read it from .NET.  When I started looking at it, I found that there's a couple of new classes in .NET for working with named pipes: PipeStream, NamedPipeServerStream, and NamedPipeClientStream.

Since my C++ skills are virtually non-existent, I decided to try writing a purely .NET version first.  I wanted a Windows Form that would be able to read and write messages in both directions (duplex) via NamedPipeServerStream and NamedPipeClientStream.

Here's a screenshot of the Windows Forms app:

image

In order to be able to read from the PipeStream without blocking the UI thread, I needed to figure out a way to make the Read() logic run in a separate thread.  After working at this for a  bit, it started getting complex enough that I wanted to refactor the PipeStream logic into its own class.  Also, the logic for working with the NamedPipeServerStream was only a little bit different from the NamedPipeClientStream.  After a few refactorings, I ended up with a design that uses a generic PipeStreamWrapperBase.

Here's the class diagram:

image

The "heavy lifting" and the multi-threaded code is all in PipeStreamWrapperBase.  NamedPipeClient and NamedPipeServer extend from PipeStreamWrapperBase and consist mostly of logic to create and initialize either a NamedPipeServerStream or NamedPipeClientStream.

After I got the classes designed, I ran into a tricky problem where calls to Write() and Flush() on the NamedPipeServerStream were hanging.  I could write from the server to the client but not from the client to the server.  It turns out that if either side of the pipe isn't actively waiting to read from the pipe, the writes on the opposite end will block.  This was especially hard to debug because trying to look at the NamedPipeServerStream through the Visual Studio 2008 debugger was causing Visual Studio to hang.  (Yikes!)

Here was my mistake: NamedPipeClientStream has a property called IsMessageComplete.  I was thinking that I'd get notified of a new incoming message by having the IsMessageComplete suddenly go from True to False.

while (Pipe != null && m_stopRequested == false)
{
if (Pipe.IsConnected == true && Pipe.IsMessageComplete == false)
{
byte[] msg = ReadMessage(Pipe);

ThrowOnReceivedMessage(msg);
}
}

Since IsMessageComplete never flipped to false, the code above would NEVER try to read from the pipe.  When I removed that "&& Pipe.IsMessageComplete == false" everything started to work.

Another problem that I ran in to was not being absolutely clear about what size my input and output buffers were on the PipeStreams.  This was also causing some blocking/hanging behavior.

After I got the .NET client working, the hardest part of getting the unmanaged C++ (Win32) console app going was mostly just figuring out how to convert between the myriad C++ string types and how to read lines from the console.  (Yah...I know...it's pathetic but keep in mind that my only training in C++ was a class in college that I skipped most the time.)

Here's a screenshot of the unmanaged C++ app sending messages to the .NET Form over the pipe.

image

Download the source, Download the binaries

-Ben

Tags: c-sharp