Click here to Skip to main content
15,886,199 members
Articles / Programming Languages / C#

Anonymous Pipes Made Easy

Rate me:
Please Sign up or sign in to vote.
4.97/5 (19 votes)
28 Feb 2022CPOL5 min read 51K   2.2K   34   35
A simple and convenient wrapper class for Anonymous Pipes in C# using serialization.
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:

C#
pipe = new AnonymousPipes("Server end of the pipe.", (object msg) => {
   // Text in:
   UI(() => lbTextIn.Items.Add("Object received: " + msg.GetType().ToString() + " (" + msg.ToString() + ")")));
}, ()=> {
   // A client has connected!
   UI(() => tsbStatus.Text = "Client Connected");
   UI(() => tsbStartClient.Enabled = false);
}, () => {
   // A client disconnected!
   UI(() => lbTextIn.Items.Add("Client disconnected at " + DateTime.Now.ToString()));

   // Shut down this AnonymousPipes server:
   pipe.Close();

   // And start it listening again
   // after the disconnection:
   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:

C#
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:

C#
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:

C#
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() + ")"));
}, () => {
   // We're disconnected!
   UI(() => tsbStatus.Text = "Disconnected");
   UI(() => tsbConnectToPipe.Enabled = false);

   pipe.Close();
})) {
   // Connection failed!
   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.

License

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


Written By
President Doxtader Industries LLC
United States United States
I've been in IT for the last 25 years in one capacity or another - always as either a network engineer or a developer... or both. At the moment I have an IT consultancy in Long Island, NY offering software development and network engineer services.

Comments and Discussions

 
QuestionOS? Pin
pggcoding16-Jul-22 22:18
pggcoding16-Jul-22 22:18 
AnswerRe: OS? Pin
pdoxtader27-Jul-22 9:18
professionalpdoxtader27-Jul-22 9:18 
QuestionPerformance Pin
aeastham1-Mar-22 2:54
aeastham1-Mar-22 2:54 
AnswerRe: Performance Pin
pdoxtader1-Mar-22 4:56
professionalpdoxtader1-Mar-22 4:56 
QuestionCool stuff, thank you for! Any chance to exchange objects? Pin
LightTempler25-Feb-22 5:29
LightTempler25-Feb-22 5:29 
AnswerRe: Cool stuff, thank you for! Any chance to exchange objects? Pin
pdoxtader25-Feb-22 7:40
professionalpdoxtader25-Feb-22 7:40 
GeneralRe: Cool stuff, thank you for! Any chance to exchange objects? Pin
LightTempler25-Feb-22 11:38
LightTempler25-Feb-22 11:38 
GeneralRe: Cool stuff, thank you for! Any chance to exchange objects? Pin
pdoxtader27-Feb-22 8:56
professionalpdoxtader27-Feb-22 8:56 
GeneralRe: Cool stuff, thank you for! Any chance to exchange objects? Pin
LightTempler28-Feb-22 4:30
LightTempler28-Feb-22 4:30 
QuestionAnother limitation I just found Pin
Dave S24-Feb-22 2:40
Dave S24-Feb-22 2:40 
AnswerRe: Another limitation I just found Pin
pdoxtader24-Feb-22 3:32
professionalpdoxtader24-Feb-22 3:32 
QuestionCommunication between a Windows Service and a Winforms app Pin
Jaime Stuardo - Chile21-Feb-22 3:03
Jaime Stuardo - Chile21-Feb-22 3:03 
AnswerRe: Communication between a Windows Service and a Winforms app Pin
pdoxtader22-Feb-22 15:54
professionalpdoxtader22-Feb-22 15:54 
QuestionGreat Work! Pin
marjaan30-Jun-20 2:44
marjaan30-Jun-20 2:44 
AnswerRe: Great Work! Pin
pdoxtader22-Jul-20 6:36
professionalpdoxtader22-Jul-20 6:36 
PraiseSimple and straightforward Pin
Z-User17-Oct-18 1:36
Z-User17-Oct-18 1:36 
GeneralRe: Simple and straightforward Pin
pdoxtader28-May-19 4:59
professionalpdoxtader28-May-19 4:59 
QuestionClient/Server Projects Pin
opti9991-Jun-16 7:43
opti9991-Jun-16 7:43 
AnswerRe: Client/Server Projects Pin
pdoxtader1-Jun-16 7:52
professionalpdoxtader1-Jun-16 7:52 
GeneralRe: Client/Server Projects Pin
opti9991-Jun-16 8:05
opti9991-Jun-16 8:05 
GeneralRe: Client/Server Projects Pin
pdoxtader1-Jun-16 9:05
professionalpdoxtader1-Jun-16 9:05 
Ok.

I'm going to have to explain some things first though, and I'll try to be as clear and thorough as I can but this is a vast subject, and I definitely can't cover every nuance of it here.

A Delegate, in it's simplest form, is a pointer to a function. It's a way to pass a function as a parameter in another function. Over the years, delegates have become so much more and acquired uses that I think the original designers probably didn't dream of, but for our purposes here - so that you understand it at it's most basic level, it is a pointer to a function.

The nice thing about delegates is that once you have them configured properly, and they point at the function you want, you can pass that delegate as a parameter to another function in some other class, and the class can take it and go off and do some work on another thread, and when it's done doing it's work, it can call your delegate and pass back to you any data or information you intended.

This simple setup is called a CALLBACK, and the concept of asynchronous programming that has become so popular today is built around it. These days, we are never supposed to Wait or Sleep() our threads - we are supposed to do all multi-threaded operations asynchronously, using a callback system that Microsoft has built into many of it's libraries.

A NAMED delegate will look something like this:
C#
delegate void SomeDelegatePrototype(String s);
private SomeDelegatePrototype sd = SomeFunction;

private static void SomeFunction(String s)
{
    // your code here.
}

Looking above, you'll see that named delegates must have a PROTOTYPE. It's the signature that any function we are going to point our delegate AT must have. any function we point our delegate at must have one parameter - a string.

The reason I made the function static is because it's pointing to it in the intializer - and until a class is created, onle it's static members will be available. the functions you point your delegate at don't have to be static, but you need to point your delegate at them in a class constructor once the class is created in that case.

To pass a named delegate as a parameter, you use it's prototype, like this:
C#
private void AnotherFunction(SomeDelegatePrototype passedDelegate)
{
    passedDelegate("Hello");
}

When we call passedDelegate() from inside Anotherfunction(), your code in SomeFunction() will run.

This is a simple named delegate callback setup. Another kind of delegate is the Anonymous delegate.

Basically, anonymous delegates work without having a function to point to. Instead, you simply pass a block of code. Your delegate must still have the expected signature, but instead of creating a function to point to, you just write the code right in the function as a parameter.

Using an anonymous delegate, I could have (and did) do away with names delegate from the prototype and assigning it to a function. Instead, I just passed the block of code that was IN the function.

Basically, it would look something like this:
C#
AnotherFunction(delegate(String s)
{
    // Any code here that I want to run when passedDelegate is 
    // called from within AnotherFunction.
});

See how simple and easy that makes working with delegates? I used this a lot throughout this project.

About Methodinvoker and accessing UI controls from a background thread:

A windows control (and a window itself) can only be created and accessed on the thread that created them - the User Interface thread. If you try to do something innocuous seeming from a background thread like changing the text in a textbox for instance, .net will throw a cross thread access exception, and your application will crash.

The way to get around this is by using the .Invoke() method available in all windows forms controls. You load the .invoke() method with a delegate, and you must specify the signature of the delegate when you do. An easy way around this is using an anonymous delegate, and casting it to (MethodInvoker).

This takes a lot of the hassle out of updating form controls from a background thread, and cleans up your code. I do it a lot in this example project.

I hope this helps, and if there's anything I missed please feel free to ask.
  • pete

GeneralRe: Client/Server Projects Pin
pdoxtader1-Jun-16 9:17
professionalpdoxtader1-Jun-16 9:17 
Questionreacting to text received messages Pin
opti9991-Jun-16 2:44
opti9991-Jun-16 2:44 
AnswerRe: reacting to text received messages Pin
pdoxtader1-Jun-16 2:49
professionalpdoxtader1-Jun-16 2:49 
GeneralRe: reacting to text received messages Pin
opti9991-Jun-16 3:12
opti9991-Jun-16 3:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.