In this example project, you will see how to make using AnonymousPipes class as easy and intuitive as I believe they should be in the first place, and use serialization to pass data between your client and server applications.
Introduction
Sometimes, you just need to be able to talk to your application.
It should be that easy, shouldn't it? Let's say you have a few applications running as part of your solution, and you just want your main application to be able to tell the others to shut down. Or maybe you want to start one up, have it run in the background, and then pass back some useful information. Or maybe you have a Windows Service running on the same machine as a desktop application, and you need them to be able to communicate with each other, and you really don't want to (and shouldn't) resort to using TCP/IP.
Anonymous pipes is the solution to these problems. Implementing them can be a little tricky though. The aim of the AnonymousPipes
class in this example project is to make using them as easy and intuitive as I believe they should be in the first place.
Background
I've built several TCP/IP communications libraries, and I find myself going to them a lot to solve problems with communication between different applications in my solutions. I sometimes find myself tempted to communicate with different applications on the same machine through these libraries when I shouldn't, because it would be easy... But there really is no reason to go through the network card and drop your message on the network, when it will only be picked up by the same network card and computer but for another application. It's a network security problem as well as needless network bandwidth usage.
The right way to go here is IPC (Interprocess Communication) - Anonymous Pipes in particular (as opposed to NAMED pipes, which I won't discuss here), because we only want communication between running applications on the same PC or server.
There is a bit of setup involved in using Anonymous Pipes, and there is also the niggling limitation that an Anonymous Pipe is a one way communication channel... so if you want TWO way communication between your main and child applications, you need to set up two pipes.
This seemed like a perfect opportunity to write a wrapper class that handles all this setup and detail for me... and when I was done, it occurred to me that I could probably have found what I was looking for here on CodeProject. I was surprised to discover that there isn't (or my search didn't bring one up) an example of Anonymous Pipe usage here for C#.
Using the Code
In the example project, I am simply checking to see if there are any command line arguments when the Windows Forms application starts. If there aren't, then I am assuming that this instance is going to be the pipe SERVER, so I immediately start another instance of the application and pass the two pipe handles (input and output pipes) to the new instance as command line args.
The second instance starts up and discovers that it does indeed have some command line args, so it assumes it is a pipe CLIENT. It reads the first argument which is the two handles of the pipes created by our first instance, and connects to the two pipes.
Below is an example of instantiating the AnonymousPipes
wrapper class. I think that may be a little confusing to look at, so I'm going to go into a little detail about this instantiation:
pipe = new AnonymousPipes("Server end of the pipe.", (object msg) => {
UI(() => lbTextIn.Items.Add("Object received: " + msg.GetType().ToString() + " (" + msg.ToString() + ")")));
}, ()=> {
UI(() => tsbStatus.Text = "Client Connected");
UI(() => tsbStartClient.Enabled = false);
}, () => {
UI(() => lbTextIn.Items.Add("Client disconnected at " + DateTime.Now.ToString()));
pipe.Close();
if(!tsbStartServer.Visible) StartAnonymousPipesServer();
}, out handles);
The AnonymousPipes
class is instantiated differently depending on who is instantiating it: the pipe SERVER, or the pipe CLIENT.
If the pipe SERVER is instantiating it, then the AnonymousPipes
wrapper class has some initial setup to do - including starting the child application and passing it the command line argument containing the handles of the two pipes.
Because it's a wrapper class, and I wanted to make it as reusable as possible, I'm using delegates to get incoming text from the wrapper class.
The AnonymousPipes
constructor that is used by the pipe server application looks like this:
public AnonymousPipes(String pipeName, CallBack callback,
ConnectionEvent connectionEvent, ConnectionEvent disconnectEvent, out Handles handles)
{
}
The parameters are fairly obvious, I think: pipeName
is a name you give this pipe so you can keep track of it if you were to add the AnonymousPipes
object to a list. You get the name of this object by calling .GetPipeName()
.
The callback delegate has the following signature:
public delegate void CallBack(object msg);
This is used to pass incoming text from the pipe to YOU. Any incoming text will arrive this way. You should use a function with the proper signature (something like private void YourFunction(String msg) { }
), or an anonymous inline delegate the way I did. In this example project, I'm simply shoving everything that comes in into a listbox so you can see it.
connectionEvent
and disconnectEvent
are also delegates. They will trigger if your child application disconnect and connection events from the wrapper class. Use this to do any clean up you need to in the eventuality that your child disconnects or shuts down.
In the client application, the wrapper class is instantiated like this:
pipe = new AnonymousPipes("Client end of the pipe.");
if (!pipe.ConnectToPipe(startArgs, (object msg) => {
UI(() => lbTextIn.Items.Add("Object received:" + msg.GetType().ToString() + " (" + msg.ToString() + ")"));
}, () => {
UI(() => tsbStatus.Text = "Disconnected");
UI(() => tsbConnectToPipe.Enabled = false);
pipe.Close();
})) {
UI(() => tsbConnectToPipe.Enabled = false);
UI(() => tsbStatus.Text = "Connection failed: The current handles are invalid!");
return;
}
When instantiating the wrapper class in the client, just pass the name. Then call ConnectToPipe()
, as seen above. The delegates should be familiar, and work exactly the same way they did in the pipe server instantiation.
Serialization
In this exaple project, I'm once again using the binary formatter for serialization. Using it is easy and streight forward - just drop any .net serializable object into the Send() function. To send your own classes / objects, make sure they exist in both your server and client applications, that they are identical, and that they are both marked as [Serializable].
History
- 24th March, 2016: Initial version
- 2/27/22 - Serialization added.