Now that, as of .NET 3.5, Named Pipes is a full-fledged citizen of the .NET Framework,
it is a good exercise to learn how this transport can be useful.
In Windows, the design of named pipes is based towards client-server communication,
and they work much like sockets, other than the usual read and write operations.
Windows named pipes also support an explicit "passive" mode for server
applications. The named pipe can be accessed much like a file. Win32 SDK functions
such as CreateFile, ReadFile, WriteFile and CloseHandle can be used to open,
read from, write to, and close a pipe. In the .NET Framework, the NamedPipeServerStream
and NamedPipeClientStream wrap this underlying OS API, exposing native Named
Pipes as managed code. .NET Named Pipes are bidirectional, meaning that two applications
can both send and receive over the same pipe. Among other protocols, Named Pipes
is a primary transport mode used by SQL Server and other RDBMS systems.
At the office, I heard some talk about making separate applications be able to send
messages to each other, and that's what sparked my interest at this time. I've
written several articles about Named Pipes in the past, in particular an implementation
of a Named Pipe Cache here.
In keeping with my credo of "Don't reinvent the wheel" when approaching
a programming problem, I spent some time Googling and Binging around for a decent
.NET asynchronous implementation of a Named Pipes library, and was unable to
find anything except your typical synchronous Console Application samples, which
are essentially useless for the task at hand. But finally, I ran into something
by Rudi Grobler at codeproject.com who was using something from Mario Lionello,
who apparently was at one time, at least, an MVP who had implemented a reasonably
decent async server and client pipes library. Unable to find any original source
code with my Nimble Ninja Search TechniquesTM, I eventually Reflectored (is that a verb?) and disassembled the assembly and reconstructed
my own implementation using it as a guide. Since it was originally only designed
to handle string messages, I needed to "soup it up" to to make it useful
for what I wanted to do. The original author did what I think is an admirable
job but forgot that people might actually want to connect to a pipe on another
machine, so in the process I took care of that, too.
The first step after getting the library assembly into a state consistent with my
needs was to implement an IMessage interface. Thinking about being able to send
a message containing any object you want over a pipe, what would be some of the
kinds of properties you would want to have? This is what I came up with:
using System;
namespace AsyncPipes
{
/// <summary>
/// Interface for Async Pipes Message objects.
/// </summary>
public interface IMessage
{
Guid MessageId { get; set; }
String Originator { get; set; }
String Recipient { get; set;}
DateTime MessageDateTime { get; set; }
Type MessageType { get; set;}
byte[] Payload { get; set; }
}
}
Because one could have multiple application instances all talking on the same PipeStream,
it would be a good idea to have a MessageId. This could also be used as a Primary
Key should messages need to be persisted to a database. Also, since different
instances of an application could receive pipe messages that weren't intended
specifically for them, we have an Originator and a Recipient property that they
can check. The MessageDateTime is obvious. Finally we have a Type property that
tells the receiver what Type the Payload property represents. It could be a string
message, a DataSet, or anything else. As long as we can serialize it to a byte
array (e.g. BinaryFormatter or your favorite alternate type serializer) we can
store the instance in the Payload property of the Message class.
I then implemented a GenericMessage class extending the IMessage interface:
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
namespace Messages
{
[Serializable]
public class GenericMessage :AsyncPipes.IMessage
{
#region IMessage Members
private Guid _messageId;
private string _originator;
private string _recipient;
private DateTime _messageDateTime;
private Type _messageType;
private byte[] _payload;
public Guid MessageId
{
get
{
return _messageId;
}
set
{
_messageId = value;
}
}
public string Originator
{
get
{
return _originator;
}
set
{
_originator = value;
}
}
public string Recipient
{
get
{
return _recipient;
}
set
{
_recipient = value;
}
}
public DateTime MessageDateTime
{
get
{
return _messageDateTime;
}
set
{
_messageDateTime = value;
}
}
public Type MessageType
{
get
{
return _messageType;
}
set
{
_messageType = value;
}
}
public byte[] Payload
{
get
{
return _payload;
}
set
{
_payload = value;
}
}
#endregion
public GenericMessage()
{
}
public GenericMessage(Guid messageId,string originator,string recipient,DateTime messageDateTime, Type messageType,byte[] payload )
{
this._messageId = messageId;
this._originator = originator;
this._recipient = recipient;
this._messageDateTime = messageDateTime;
this._messageType = messageType;
this._payload = payload;
}
}
}
So the Message class is what we actually serialize and send over the pipe after assigning
the serialized Payload and setting the properties. You could also create specialized
Message classes that implement IMessage and one or more additional interfaces.
For example, you might want a Message class that implements the Command pattern
and sports an Execute method. The final missing pieces of the puzzle are utility
methods to serialize and deserialize the types used, and so I implemented a MessageSerializers
class with static utility methods that is included in the AsyncPipes library
project:
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
namespace AsyncPipes
{
public static class MessageSerializers
{
public static byte[] SerializeMessage(IMessage message)
{
BinaryFormatter formatter = new BinaryFormatter();
MemoryStream ms = new MemoryStream();
formatter.Serialize(ms, message);
return ms.ToArray();
}
public static IMessage DeserializeMessage(byte[] bMessage)
{
BinaryFormatter formatter = new BinaryFormatter();
MemoryStream ms = new MemoryStream(bMessage);
Object message= formatter.Deserialize(ms);
return (IMessage) message;
}
public static byte[] SerializeObject( object item)
{
BinaryFormatter formatter = new BinaryFormatter();
MemoryStream ms = new MemoryStream();
formatter.Serialize(ms, item);
return ms.ToArray();
}
public static object DeserializeObject(byte[] bObject)
{
BinaryFormatter formatter = new BinaryFormatter();
MemoryStream ms = new MemoryStream(bObject);
Object message = formatter.Deserialize(ms);
return message;
}
}
}
The rest of the prototype / demo implementation is to have two separate Windows Forms
applications each of which is pretty much a mirror image of the other:

Each of these Apps has textboxes for a message to send, and to display a received
message, as well as a DataGridView to display a DataTable from a DataSet. There
is a dropdown that enables the user to select what kind of message they want
to experiment with - either a string, or a DataSet. The Create DataSet button
creates a DataSet and binds the single DataTable to the grid. When you press
the Send button on either application, a GenericMessage object is created, the
DataSet (or the string message) is assigned to the Payload property, the message
is serialized to a byte array, and sent over the pipe. It will then appear in
the other application - in the lower TextBox if it is a string message, or it
populates the DataGridView if it was a DataSet message.
This is easy to "hook up" to your own apps because all they need is a reference
to the AsyncPipes assembly. To prepare any application to use the library, this
is all you need:
private AsyncPipes.NamedPipeStreamServer pipeServer = new NamedPipeStreamServer("test2");
private AsyncPipes.NamedPipeStreamClient pipeClient = new NamedPipeStreamClient("test");
private void Form1_Load(object sender, EventArgs e)
{
pipeServer.MessageReceived += new MessageEventHandler(pipeServer_MessageReceived);
pipeClient.MessageReceived += new MessageEventHandler(pipeClient_MessageReceived);
}
The pipe names on the other participating application would, of course, be reversed.
You can see the full implementation code in the downloadable solution. You can
use this AsyncPipes Library as the basis for most any type of IPC (Inter-Process
Communication) need you may have. It is fast, easy - to - use, and efficient.
NOTE: To connect a client to a server on ANOTHER MACHINE, use the following syntax:
private AsyncPipes.NamedPipeStreamClient pipeClient = new NamedPipeStreamClient("\\othercomputername\\pipename");
My code will automatically parse the two parts out using the "\\" as a
delimiter, and make the correct pipe connection.
As with all code samples published on eggheadcafe.com, this code comes under the
Dr. Dotnetsky license: "Do whatever you want with it, but you can't sue
me". Enjoy!
You can download the full Visual Studio 2008 solution here.