Interprocess Communication using Named Pipes in C#
| 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);
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);
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
}
}
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);
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();
}
{
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);
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!");
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 |

September 4th, 2007 at 8:08 pm
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.
September 4th, 2007 at 8:55 pm
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.
September 5th, 2007 at 3:30 pm
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
September 8th, 2007 at 5:39 am
I have implemented and consulted with many people, and I have the same problem as shil.
September 12th, 2007 at 12:19 pm
Me too …
September 12th, 2007 at 1:08 pm
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);
November 7th, 2007 at 11:53 pm
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
November 8th, 2007 at 3:25 pm
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?
November 11th, 2007 at 8:49 pm
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!
November 11th, 2007 at 9:03 pm
@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.
November 22nd, 2007 at 10:33 pm
Many thanks.
November 26th, 2007 at 6:05 am
It seems, that you forgot to call the DisconnectNamedPipe function. Cause when you close your server window, the listen Thread won’t end.
November 26th, 2007 at 10:16 am
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.
December 6th, 2007 at 5:13 pm
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
December 6th, 2007 at 8:23 pm
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.
December 7th, 2007 at 3:42 pm
Thank you very much for the answer. It make sense.
December 7th, 2007 at 4:01 pm
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?
December 7th, 2007 at 4:12 pm
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.
January 10th, 2008 at 10:31 am
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?
January 10th, 2008 at 11:00 am
Forget my last question. It was stupid
January 10th, 2008 at 11:45 am
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.
January 23rd, 2008 at 5:56 pm
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?
January 24th, 2008 at 11:41 am
@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.
April 15th, 2008 at 12:36 pm
Do you know how to use named pipes over a network? This would be extremely useful for a project I’m working on.
April 15th, 2008 at 12:44 pm
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′.
May 21st, 2008 at 4:54 pm
Does anyone know if this runs in .NET CF?
June 5th, 2008 at 8:38 am
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.
June 18th, 2008 at 4:34 pm
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?
June 18th, 2008 at 4:49 pm
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.
June 19th, 2008 at 2:50 am
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
June 20th, 2008 at 6:41 pm
2 Patrick ? Nair
Just set IsBackground property at true on all threads.
June 23rd, 2008 at 6:41 am
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…
August 1st, 2008 at 3:50 am
Some modification is indeed needed for termination. Hope to see it soon.
August 18th, 2008 at 7:52 am
Don’t forget .NET Framework 3.0 supports pipes directly. Use namespace System.IO.Pipes for it.
September 3rd, 2008 at 11:37 am
How can one be notified (using the excellent code example you’ve provided) for when a client ‘leaves’ or disconnects?
thanks
Greg
September 3rd, 2008 at 1:07 pm
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.
September 9th, 2008 at 5:31 am
THANKTH!!!!!!!!!!
September 10th, 2008 at 5:05 am
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
September 22nd, 2008 at 6:12 am
I want to change the ACLs of the Pipe which is already created in the system. How can I do this?