Named Pipes IO for Inter-process Communication
Introduction
Use the .NET 3.5 Named Pipes IO for Inter-process communication by implementing a pipe listener on a separate thread. The first sample is a WPF project. It can also be done just as easily in Windows forms.
Description
The sample demonstrates how to pass string data from clients to a pipe server application that contains a pipe listener that runs on a separate thread within the server application. The main requirement here is that the clients are collecting string data and the other application wants to use the string data too, but it needs to collect the information in a background thread.
The code snippet below shows the main window class that starts the Pipeserver thread in the Window_Loaded event handler. In Forms, it is the Form1_Load event.
I like to package the thread-owner attributes into a class of all static members. Then it is easy for this class to operate on owner objects.
I create an Invoker object for WPF. In Windows Forms this is already implemented for each Form.
namespace PipeServer
{
/// <summary>
/// PipeServer creates a listener thread and waits for messages from clients.
/// Received messages are displayed in Textbox
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
tbox.Text = "";
Pipeserver.pipeName = "testpipe";
Pipeserver.owner = this;
Pipeserver.ownerInvoker = new Invoker(this);
ThreadStart pipeThread = new ThreadStart(Pipeserver.createPipeServer);
Thread listenerThread = new Thread(pipeThread);
listenerThread.SetApartmentState(ApartmentState.STA);
listenerThread.IsBackground = true;
listenerThread.Start();
}
}
Pipeserver.createPipeServer is the static thread method that is attached to the pipeThread delegate. Once the thread starts, createPipeServer runs in a continuous while loop which waits for a connection and processes data coming in on the pipe's stream.
The NamedPipeServerStream class has a complex set of constructors and properties. I'm using the 6th constructor and setting all the properties in this constructor. Here are the details on the class: http://msdn.microsoft.com/en-us/library/system.io.pipes.namedpipeserverstream.aspx
SetTextbox is the delegate that gets invoked on the main thread to update Textbox control.
I'm using a low-level read hear for flexibility. You could just as well be processing binary data (such as images) with it instead of text.
Note that it is important to Disconnect the PipeServerStream after processing each incomming message. Otherwise an error will be thrown when the process loops back to wait for the next connection.
After all of the bytes are collected as chars in the StringBuilder, the message is posted as a string using the Invoker class. Invoker has an Action<string> delegate (sDel) that runs a method delegate that takes a string parameter and is set to SetTexbox. In Windows Forms the doSetTextBox delegate object does the callback to run SetTexbox on the owner thread.
public class Pipeserver
{
public static Window1 owner;
public static Invoker ownerInvoker;
public static string pipeName;
private static NamedPipeServerStream pipeServer;
private static readonly int BufferSize = 256;
private static void SetTextbox(String text)
{
owner.tbox.Text = String.Concat(owner.tbox.Text, text);
if (owner.tbox.ExtentHeight > owner.tbox.ViewportHeight)
{
owner.tbox.ScrollToEnd();
}
}
public static void createPipeServer()
{
Decoder decoder = Encoding.Default.GetDecoder();
Byte[] bytes = new Byte[BufferSize];
char[] chars = new char[BufferSize];
int numBytes = 0;
StringBuilder msg = new StringBuilder();
ownerInvoker.sDel = SetTextbox;
try
{
pipeServer = new NamedPipeServerStream(pipeName, PipeDirection.In, 1,
PipeTransmissionMode.Message,
PipeOptions.Asynchronous);
while (true)
{
pipeServer.WaitForConnection();
do
{
msg.Length = 0;
do
{
numBytes = pipeServer.Read(bytes, 0, BufferSize);
if (numBytes > 0)
{
int numChars = decoder.GetCharCount(bytes, 0, numBytes);
decoder.GetChars(bytes, 0, numBytes, chars, 0, false);
msg.Append(chars, 0, numChars);
}
} while (numBytes > 0 && !pipeServer.IsMessageComplete);
decoder.Reset();
if (numBytes > 0)
{
ownerInvoker.Invoke(msg.ToString());
}
} while (numBytes != 0);
pipeServer.Disconnect();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
PipeClient
Here is the button1_Click code snippet from the client project. All of the work in the demo client is done on the button click. A StreamWriter is simply connected to the NamedPipeClientStream, and the contents of the Textblock are written to the pipe stream for each button click.
private void button1_Click(object sender, RoutedEventArgs e)
{
using (NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", "testpipe",
PipeDirection.Out,
PipeOptions.Asynchronous))
{
tbStatus.Text = "Attempting to connect to pipe...";
try
{
pipeClient.Connect(2000);
}
catch
{
MessageBox.Show("The Pipe server must be started in order to send data to it.");
return;
}
tbStatus.Text = "Connected to pipe.";
using (StreamWriter sw = new StreamWriter(pipeClient))
{
sw.WriteLine(tbClientText.Text);
}
}
tbStatus.Text = "";
}
For More Information
MSDN Code Samples Source Code
NamedPipeServerStream Class
NamedPipeClientStream Class