Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Simple Managed Wrapper for Windows Pipes

4.94/5 (18 votes)
4 May 2008Zlib5 min read 3   1.3K  
This article presents a very simple and easy to use managed wrapper for Windows Pipes.
Screenshot - SimpleManagedPipe1.jpg Screenshot - SimpleManagedPipe2.jpg

Introduction

I know this is the nth article about using Windows Pipes in .NET. The only reason I am writing this is because I wanted a very simple implementation of a managed wrapper for Windows Pipes. Ivan's article on The Code Project is like an example of military-grade implementation! I wanted something simpler and easier to use.

How much easier? Sending a message should be as easy as _pipe.Send("Some command string") and receiving a message should be as easy as string message = _pipe.Receive()

My implementation uses only one class, ManagedPipe, that can create a server pipe connection:

C#
_pipe = new ManagedPipe("Test1", ManagedPipeType.Server,...

or a client pipe connection:

C#
_pipe = new ManagedPipe("Test1", ManagedPipeType.Client,...

The ManagedPipe wrapper allows full duplex communication between the server and the client. This means the server and client can send and receive messages at the same time!

Background

People looking for something more flexible and robust should read the articles by Ivan and John Korres.

You will also be interested in reading about Windows Pipes in the MSDN library.

Server Connection

Since this is supposed to be a very easy-to-use wrapper, let's see how quickly we can create a connection and start sending messages.

A server connection is created like this:

C#
_pipe = new ManagedPipe("Test1", ManagedPipeType.Server,
   BlockingMode.Wait, 1024, 1024);
_pipe.Open();

The first parameter is the name of the pipe. The second parameter specifies if this is a server application or a client application. The third parameter is to specify whether the send and receive operations should block or return immediately. The fourth and fifth parameters are input and output buffer sizes. You can set them to 1K or more based on your average message size.

I have not added any try-catch block to keep the implementation simple. Your production code must have try-catch when calling Open(). Before the server application can send or receive on the pipe you have to call:

C#
_pipe.WaitForClientToConnect();

The above function is a blocking call and will not return until a client connects to the server. Once this function returns, you can call the send receive functions like this:

C#
_pipe.Send("Hello");
......
string message = _pipe.Receive();
......

If a blocking _pipe.Receive() returns null, that means the client has closed the connection. Any more calls to send or receive are not valid. Keep in mind: you must have all these functions inside a try-catch block. Here's the code from my sample:

C#
private void ReceiveThreadFunction()
{
    _pipe.WaitForClientToConnect();
    btnSend.Invoke((MethodInvoker)delegate()
    {
        btnSend.Enabled = true;
    });

    while (true)
    {
        string message = _pipe.Receive();

        if (message == null)
            return;

        txtReceiveMessage.Invoke((MethodInvoker)delegate()
        {
            txtReceiveMessage.Text += message + "\r\n";
        });
    }
}

Client Connection

The client connection code is as follows:

C#
_pipe = new ManagedPipe("Test1", ManagedPipeType.Client,
    BlockingMode.Wait, 1024, 1024);
_pipe.Open();

This is exactly similar to the server connection call except for the second parameter. All the other parameters should be identical to the ones specified on the server connection. To find out why, please read about Windows Pipes on MSDN.

Sending and receiving messages is similar to what we did in the server connection. The difference is that from the client you do not call WaitForClientToConnect(). The client can send and receive messages as soon as it connects to the server. When the server goes down, a blocking _pipe.Receive() will return null. Future calls to send or receive are not valid. Here's the code from my sample:

C#
private void ReceiveThreadFunction()
{
    while (true)
    {
        string message = _pipe.Receive();

        if (message == null)
            return;

        txtReceiveMessage.Invoke((MethodInvoker)delegate()
        {
            txtReceiveMessage.Text += message + "\r\n";
        });
    }
}

Dispose!

Once you are finished using the ManagedPipe wrapper, do not forget to call:

C#
_pipe.Dispose();

I know .NET has garbage collection and all that. The GC might eventually call dispose, depending on what kind of code you write. Holding on to resources after you are done using them is a waste. As a good programming practice, you should call dispose on an object once you are done using it. This makes sure all managed and unmanaged resources used by that object are released for other processes to use.

The ManagedPipe Wrapper

You would have noticed that I have not mentioned anything about the wrapper code. This was deliberate. To use the wrapper, one does not need to know its internals. Remember, it's supposed to be very easy to use! Now that we have covered the "how to use", lets' take a look at the wrapper itself.

The wrapper internally creates two pipe connections. The first connection is a dedicated pipe to send messages and the second connection is a dedicated pipe to receive messages. This allows full duplex communication between the server and the client.

Design

Screenshot - SimpleManagedPipe3.jpg

Code

The server pipe connection is created like this:

C#
protected void CreateServer()
{
    UInt32 pipeMode;

    if (_blockingMode == BlockingMode.Wait)
        pipeMode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT;
    else
        pipeMode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_NOWAIT;

    _hSendPipe = CreateNamedPipe(@"\\.\pipe\Send_" + _name,
        PIPE_ACCESS_OUTBOUND,
        pipeMode,
        1,
        _outputBufferSize,
        _inputBufferSize,
        0,
        IntPtr.Zero);

    if (_hSendPipe.IsInvalid)
        throw new Win32Exception(Marshal.GetLastWin32Error());

    _hReceivePipe = CreateNamedPipe(@"\\.\pipe\Receive_" + _name,
        PIPE_ACCESS_INBOUND,
        pipeMode,
        1,
        _outputBufferSize,
        _inputBufferSize,
        0,
        IntPtr.Zero);

    if (_hReceivePipe.IsInvalid)
        throw new Win32Exception(Marshal.GetLastWin32Error());
}

As mentioned earlier, you will notice that I create two pipe connections to facilitate parallel send and receive. To keep the implementation simple, I have kept the maximum number of pipe instances to one and I have not used overlapped input and output. Remember, this is not the military grade implementation! If you need something more powerful, then you should read the other articles mentioned in the "Background" section.

The client pipe connection is created like this:

C#
protected void CreateClient()
{
    _hSendPipe = CreateFile(@"\\.\pipe\Receive_" + _name,
        GENERIC_WRITE,
        0,
        IntPtr.Zero,
        OPEN_EXISTING,
        0,
        IntPtr.Zero);

    if (_hSendPipe.IsInvalid)
        throw new Win32Exception(Marshal.GetLastWin32Error());

    _hReceivePipe = CreateFile(@"\\.\pipe\Send_" + _name,
        GENERIC_READ,
        0,
        IntPtr.Zero,
        OPEN_EXISTING,
        0,
        IntPtr.Zero);

    if (_hReceivePipe.IsInvalid)
        throw new Win32Exception(Marshal.GetLastWin32Error());
}

Here we swap the server's send pipe with the client's receive pipe and vice versa. The pipe on which the server is sending messages is the client's receive pipe and the pipe on which the server is receiving messages is the client's send pipe.

The rest of the wrapper code is pretty straight forward.

C#
public void Open()
{
    if (_type == ManagedPipeType.Server)
        CreateServer();
    else
        CreateClient();

    _textReader = new StreamReader(new FileStream(_hReceivePipe,
        FileAccess.Read));
    _textWriter = new StreamWriter(new FileStream(_hSendPipe,
        FileAccess.Write));
}

public void Send(string message)
{
    if (_hSendPipe == null)
        throw new InvalidOperationException("Pipe is not open.");

    _textWriter.WriteLine(message);
    _textWriter.Flush();
}

public string Receive()
{
    if (_hReceivePipe == null)
        throw new InvalidOperationException("Pipe is not open.");

    return _textReader.ReadLine();
}

When Open() is called, we initialize a text stream reader and a writer. We attach them to receive and send pipes respectively. The text reader and writer are used to send and receive text messages between the server and client.

Points of Interest

throw new Win32Exception(Marshal.GetLastWin32Error()) is a great time saver statement in .NET. I remember how painful it was to get the Win32 error message during my Visual C++, MFC days.

History

  • 09/10/2007: First published
  • 05/02/2008: Edited the "Points of Interest" section

License

This article, along with any associated source code and files, is licensed under The zlib/libpng License