Switch On The Code RSS Button - Click to Subscribe
Jul
25

Interprocess Communication using Named Pipes in C#

Named pipes are a great way to communicate between multiple processes. What's makes them even better is that the processes don't have to share the same language. If you'd like detailed information about named pipes, MSDN has a fairly in depth article. This tutorial is going to demonstrate how to implement communication over named pipes using C# and Interop Services.

In Windows, named pipes work almost identically to files. In C++, the functions CreateFile(), ReadFile(), and WriteFile() are all used to communicate over named pipes. In C#, the process got a little more difficult. The first thing we need to understand is that named pipes work on a client/server basis. One process will work as the server and other processes will connect to it. That being said, we'll need code to do both. Let's start with the server.


Instead of jumping through hoops getting C# to natively support named pipes, why don't we just call the Windows API functions directly? Thanks to Interop services, that's exactly what we're going to do. In order to call functions from the Windows API, you'll need to include System.Runtime.InteropServices.

The first function we'll need to call is CreateNamedPipe(). This function will be creating an instance of our named pipe and return a handle to the file for subsequent operations. So, using DLLImport, let's get this function into our C# application.

[DllImport("kernel32.dll", SetLastError = true)]
public static extern SafeFileHandle CreateNamedPipe(
   String pipeName,
   uint dwOpenMode,
   uint dwPipeMode,
   uint nMaxInstances,
   uint nOutBufferSize,
   uint nInBufferSize,
   uint nDefaultTimeOut,
   IntPtr lpSecurityAttributes);

It's not important to understand all the parameters just yet. When we start calling the function, I'll explain what each one does. CreateNamedPipe only initializes the named pipe. Now we need a way to listen for client connections. That's where ConnectNamedPipe() comes in. This function will wait until a client connection has been established.

[DllImport("kernel32.dll", SetLastError = true)]
public static extern int ConnectNamedPipe(
   SafeFileHandle hNamedPipe,
   IntPtr lpOverlapped);

Now that we have our basic functions defined, we can start building our server. Let's start by creating a function that listens for client connections.

public const uint DUPLEX = (0x00000003);
public const uint FILE_FLAG_OVERLAPPED = (0x40000000);

public const string PIPE_NAME = "\\\\.\\pipe\\myNamedPipe";
public const uint BUFFER_SIZE = 4096;

private void Listen()
{
   SafeFileHandle clientPipeHandle;
   while (true)
   {
      clientPipeHandle = CreateNamedPipe(
         PIPE_NAME,
         DUPLEX | FILE_FLAG_OVERLAPPED,
         0,
         255,
         BUFFER_SIZE,
         BUFFER_SIZE,
         0,
         IntPtr.Zero);

      //failed to create named pipe
      if (clientPipeHandle.IsInvalid)
         break;

      int success = ConnectNamedPipe(
         clientPipeHandle,
         IntPtr.Zero);

      //failed to connect client pipe
      if (success != 1)
         break;

      //client connection successfull
      //handle client communication
   }
}

This function constantly waits for clients to connect to our named pipe. It does this by first calling CreateNamedPipe to create an instance of the named pipe. You can refer to MSDN's documentation on CreateNamedPipe for detailed information on the parameters. Let's go through the parameters and the values I chose for each.

pipeName: The name of the named pipe we want to create. In this case, I set it to a constant I defined called PIPE_NAME.
dwOpenMode: This tells the named pipe how we want to communicate over it. You can set this to duplex (both read and write), read only, or write only. I want the server to both read and write to the named pipe, so I set it to PIPE_ACCESS_DUPLEX (3). I also had to set the mode to overlapped, so the pipe could be both read from and written to at the same time.
dwPipeMode: This parameter describes how data will be written to the named pipe. Data can be written as either a stream of bytes or a stream of messages. I chose to write it as a stream of bytes - PIPE_TYPE_BYTE (0).
nMaxInstances: This parameter limits the number of clients that can be simultaneously connected to our named pipe. I didn't want any limit, so I set this value to PIPE_UNLIMITED_INSTANCES (255).
nOutBufferSize and nInBufferSize: These two parameters set the number of bytes to reserve for the input and output buffers. I set them to BUFFER_SIZE which is 4096 bytes.
nDefaultTimeOut: This is how long the code should wait while attempting to create the named pipe. Setting this to 0 means it will wait indefinitely.
lpSecurityAttributes: This parameters defines various security settings for the named pipe. I set this to IntPtr.Zero, which to the Windows API is NULL, so the default security attributes will be used.

CreateNamedPipe returns a handle to the named pipe instance. We need to check and make sure the handle was successfully created. If clientPipeHandle is invalid, then the server failed to create the named pipe. If the handle is anything else, we go on and wait for the client connection. ConnectNamedPipe blocks until a client connection has been established or an error has occurred.

Now that we have client connections, let's see how to read and write to the named pipe. Since named pipes are nearly identical to files, you'd think you could use .NET's FileStream object to communicate over it. If you were to try to create a FileStream object by passing the name of the named pipe as a parameter to the constructor, you'd receive the following exception:

FileStream will not open Win32 devices such as disk partitions and tape drives. Avoid use of "\\.\" in the path.

This is because the .NET framework explicitly restricts access to physical disks through device names using the FileStream object. We can, however, use the SafeFileHandle returned from the CreateNamedPipe call to create our FileStream.

FileStream fStream =
   new FileStream(clientPipeHandle, FileAccess.ReadWrite,
     BUFFER_SIZE, true);

The first three arguments to the FileStream constructor should be obvious. The last boolean is there to allow asynchronous communication over the pipe. If this were set to false, you would not be able to read and write to the named pipe at the same time. Once you have the FileStream object, you can read and write to the named pipe like you would any other file. Let's see how this is done.

private void Read()
{
   byte[] buffer = new byte[BUFFER_SIZE];
   ASCIIEncoding encoder = new ASCIIEncoding();

   while (true)
   {
      int bytesRead = fStream.Read(buffer, 0, BUFFER_SIZE);

      //could not read from file stream
      if (bytesRead == 0)
         break;

      System.Diagnostics.Debug.WriteLine(
         encoder.GetString(buffer));
    }
}

private void Write(string message)
{
   ASCIIEncoding encoder = new ASCIIEncoding();
   byte[] sendBuffer = encoder.GetBytes(message);
   fStream.Write(sendBuffer, 0, sendBuffer.Length);
   fStream.Flush();
}

The Read function sits in a loop waiting for messages from the client - ideally in its own thread. When a message is received, it simply prints the message to the debug console. The Write function takes a string and converts it to a byte array which is sent to the client.

That's all the code required for the server portion, so how does a client form a connection to the named pipe? Again, we're going to create a FileStream from the named pipe. We're going to need to use another Windows API function, CreateFile, to get our file handle.

[DllImport("kernel32.dll", SetLastError = true)]
public static extern SafeFileHandle CreateFile(
   String pipeName,
   uint dwDesiredAccess,
   uint dwShareMode,
   IntPtr lpSecurityAttributes,
   uint dwCreationDisposition,
   uint dwFlagsAndAttributes,
   IntPtr hTemplate);

Just like ConnectNamedPipe, this function will return a SafeFileHandle that can be passed into the constructor of our FileStream.

uint GENERIC_READ = (0x80000000);
uint GENERIC_WRITE = (0x40000000);
uint OPEN_EXISTING = 3;
uint FILE_FLAG_OVERLAPPED = (0x40000000);

SafeFileHandle pipeHandle =
   CreateFile(
      PIPE_NAME,
      GENERIC_READ | GENERIC_WRITE,
      0,
      IntPtr.Zero,
      OPEN_EXISTING,
      FILE_FLAG_OVERLAPPED,
      IntPtr.Zero);

//could not get a handle to the named pipe
if (pipeHandle.IsInvalid)
   return;

FileStream fStream =
   new FileStream(pipeHandle, FileAccess.ReadWrite,
      BUFFER_SIZE, true);
Write("Hello Server!");

The above code is all that's required to connect a client to the server and send the message "Hello Server!". You can refer to the MSDN documentation on CreateFile for details on the arguments.

If you have any questions regarding this tutorial, please feel free to leave them in a comment and I will answer them as best I can.

Update: November 11th, 2007
Quite a few people have found the bugs in the original post. It's nice to see an active community finding and fixing each other's code. I left out some pretty important flags that are required for overlapped IO, which many people have discovered causes the program not to run. I have fixed the code snippets above and provided complete working code which you can download and view.

PipeClient.zip and PipeServer.zip

These zip files each contain a Visual Studio 2005 solution with working code and a GUI for testing communication between the client and the server. Hopefully this will help everyone out there and we'll try our hardest not to let broken code slip out like this again.



Posted in C#, All Tutorials by The Reddest |

40 Responses

  1. Michael Says:

    Thanks for publishing this. But I have a question.

    Should the CreateFile call be made from both the client and server? With the same parameters? When I make the call on the client it works fine. But when I try the same call, after the blocking return, on the server it returns an invalid pipehandle (ie, pipeHandle.IsInvalid == true). Because of that I started playing around with Win32 Reads and Writes, but FileStream objects are much easier to deal with. But without a valid handle I cannot get a filestream object.

    Thanks again.

  2. The Reddest Says:

    On the server, you want to pass the file handle returned from ConnectNamedPipe to the FileStream. I’ve modified the DLLImports and the code snippets in this tutorial to illustrate that. I apologize for the errors.

  3. shil Says:

    Can you please upload the entire code? I have typed in the exact same code, but at the client side, it gives a “Access to the path denied” exception (at read). I have no idea whats going on. Please help

  4. strzala Says:

    I have implemented and consulted with many people, and I have the same problem as shil.

  5. Nico Says:

    Me too …

  6. Nico Says:

    Ok, It works with:

    const uint GENERIC_READ = 0×80000000;
    const uint GENERIC_WRITE = 0×40000000;
    const uint GENERIC_EXECUTE = 0×20000000;
    const uint GENERIC_ALL = 0×10000000;
    const uint OPEN_EXISTING = 0×00000003;
    const uint FILE_ATTRIBUTE_NORMAL = 0×00000080;

    m_pipe = CreateFile(PIPE_NAME, GENERIC_READ | GENERIC_WRITE, 0, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);

  7. Pieter Breed Says:

    Hi Brandon,

    This article is very informative, thank you very much for writing it.

    I have a question about the behavior of the FileStream: Can you write TO the filestream before you’ve read out all of the data? IE, do you run the risk of overwriting incoming data when you write to the stream when there is unread data still in it?

    Regards,
    Pieter

  8. AJ Says:

    I tried the code as it is, and the only thing that doesn’t work is the FileStream with the given handle. I get the following when it tries to read/write (whichever comes first):

    “Handle does not support asynchronous operations. The parameters to the FileStream constructor may need to be changed to indicate that the handle was opened synchronously (that is, it was not opened for overlapped I/O).”

    What’s going on?

  9. The Reddest Says:

    I have posted an update, fixed the code snippets, and provided links to working code that everyone can download. Thanks everyone for helping me find the mistakes!

  10. The Reddest Says:

    @Pieter
    I believe the read and write buffers are separate for a FileStream object, so there’s no worry of overwriting the read buffer by writing to it.

  11. Keith Hutchison Says:

    Many thanks.

  12. Patrick Says:

    It seems, that you forgot to call the DisconnectNamedPipe function. Cause when you close your server window, the listen Thread won’t end.

  13. The Reddest Says:

    You’re correct. The example apps are just to provide the basic functionality of connecting and sending data - so some error handling and cleanup was left out. I’ll modify them soon so the threads shut down when the app is closed.

  14. Nair Says:

    Quick question, I ran both client and server, both connected and was able to send messages, everything is ok. But when I close the client (VS didn’t actually close, it kept showing running) and try to send the message from server, VS Studio threw exception at client side. 2 questions
    1. Is there a way server would know if a client is diconnected? How can we do that?
    2. Why client code doesn’t close?

    Thanks and it is a very useful article.

    Thanks again
    nair

  15. The Reddest Says:

    Yep, as I said in a previous comment, the threads don’t shut down on the example apps. You can tell when a client disconnects because the server’s fStream.Read call will unblock and read 0 bytes.

    The client app doesn’t close down because simply clicking the X doesn’t stop the read thread. You just need to call fStream.Close() when you close the app - then catch the exception that is thrown when fStream attempts to read.

    The server doesn’t close because the thread listening for client connections isn’t stopped.

    I haven’t gotten around to fixing the example apps, but I’ll try to do it soon. Thanks for the comment.

  16. Nair Says:

    Thank you very much for the answer. It make sense.

  17. Nair Says:

    Based on my understanding this is what I did and tell me if this is correct
    Client:
    internal void Disconnect()
    {
    try
    {
    this.stream.Close();
    }
    catch (Exception e1)
    {
    Console.WriteLine(e1.ToString());
    }
    }
    Questiion, is there a better way of handling this?

    Server:
    internal void Disconnect()
    {
    Server.DisconnectNamedPipe(clientHandle);
    }

    This comment alone fixed my close process to close properly. Do I still need to stop the thread?

  18. Nair Says:

    Sorry for overposting, but, why do we need to close fstream when it is already closed in Read method at client?
    I changed the code at client to
    internal void Disconnect()
    {
    if (stream != null)
    this.stream.Close();
    }
    this also worked. When I stepped through, I noticed the stream was null and it didn’t execute the close. So I am little confused on why would you need this statement.
    Thanks again for the help.

  19. Christoph Says:

    Thx for this simple tutorial.

    But how can the client determine that the pipe is empty.
    Eg.
    The server puts some data every 2 seconds in the pipe.
    But the Client polls the type every second.
    So there are polls without data.

    How can the client see this?

  20. Christoph Says:

    Forget my last question. It was stupid

  21. The Reddest Says:

    Consider it forgotten, but I do want to respond to it just in case other people have the same issue.

    First off, the client doesn’t poll for data. The pipe’s read call is blocking, which means execution will remain there until data is present. The read call returns the number of bytes read after data is received. If the number of bytes returned is zero, then a pipe error has occurred and the connection is finished.

  22. Peter F Says:

    You’ve got a nice explanation of interprocess communication for C#.

    My goal is to do this in .net 2.0 compact framework on CE 5.0. Any suggestions or comments?

  23. The Reddest Says:

    @Peter F,
    Unfortunately, I haven’t programmed under the .NET Compact Framework in a couple of years now and I don’t have any experience using CE 5.0. I would assume there is named pipe support on CE, but I can’t offer any insight into how to access it.

  24. Dave Says:

    Do you know how to use named pipes over a network? This would be extremely useful for a project I’m working on.

  25. The Reddest Says:

    Named pipes over a network is very simple. Just modify the pipe name to include the computer name or ip address of the computer you wish to connect to.

    “\\\\.\\pipe\\myNamedPipe” connects to a pipe on the same machine.

    “\\\\remoteComputer\\pipe\\myNamedPipe” connects to a pipe on the computer with the name ‘remoteComputer’.

    “\\\\192.168.1.101\\pipe\\myNamedPipe” connect to a pipe on the computer with the ip address ‘192.168.1.101′.

  26. Felipe Roos Says:

    Does anyone know if this runs in .NET CF?

  27. Chris Says:

    I’m getting a strange IOException on the FileStream.Read operation in the try/catch block of the client code. If the amount of data the server sends to the client is exactly the same size of the buffer in the client or less, the FileStream.Read operation works without issue. If I change the server code to send 1 byte or more data to the client than the client’s buffer size, the FileStream.Read operation in the client code throws a IOException. Does anybody know why this is? I would expect to get a filled buffer on the first read, then loop and get the rest of the data on the second read.

  28. Frode Says:

    Hi,

    I’m having trouble to get the communication to work between machines. Have tried both using IP and machine name, but the handle is invalid so no connection.

    Are there any special services or something like that which have to be turned on?

    Anybody who have gotten this to work?

  29. The Reddest Says:

    I’ve successfully done this without any special setup. I don’t have Window’s firewall turned on, so you might want to check that.

  30. Noodles Says:

    Hi all,
    I got the same problem to get the communication between two machines. I allready deactivated the firewall, tried to use IP or MachineName but nothing works.

    I’m using XP SP3 on both machines, .NET 3.5

    Any help is welcome.

    thx

  31. Anonymous Says:

    2 Patrick ? Nair

    Just set IsBackground property at true on all threads.

  32. Stephen S Says:

    Interested to hear how you got on Noodles - did you get the machine-machine communication working in the end? Also, I’m curious about how pipes equate with port numbers - how do they get assigned etc… ’spose I can be less lazt and just Google further… ;)

  33. anona Says:

    Some modification is indeed needed for termination. Hope to see it soon.

  34. VDev Says:

    Don’t forget .NET Framework 3.0 supports pipes directly. Use namespace System.IO.Pipes for it.

  35. Greg Says:

    How can one be notified (using the excellent code example you’ve provided) for when a client ‘leaves’ or disconnects?

    thanks

    Greg

  36. The Reddest Says:

    When a client leaves or disconnects, the fStream.Read call will unblock with either an exception (which isn’t caught in the post’s code) or it will read 0 bytes (which is handled in the post’s code). So you just have to watch for those two scenarios, and when one occurs, the client has been disconnected from the server.

  37. Alexey Says:

    THANKTH!!!!!!!!!!

  38. Warlord0 Says:

    After successfully connecting and sending/receiving messages I found I couldn’t close the windows application. The form disappeared but the debugger stayed active.

    I eventually added a Disconnect method with this.handle.close() and this.stream.close() which then allowed the program to exit when I called Disconnect during formclosing.

    I even got the demo client/servers to behave the same way. Once the client was connected it wouldn’t exit.

    PipeName was \\.\pipe\doofus\mypipetest

  39. Swamy Says:

    I want to change the ACLs of the Pipe which is already created in the system. How can I do this?

Tracebacks / Pingbacks


  1. Named pipe sample


Leave a Comment

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.

Powered by WP Hashcash