Introducing Pipes [Justin Van Patten]

The Orcas October Community Technology Preview (CTP) includes new types that make it easy for developers to use pipes from managed code.  Pipes are used for inter-process communication (IPC) between processes running on the same machine, or processes running on any other Windows machine within a network.  We've added support for both anonymous and named pipes.

The new pipe types can be found in System.Core.dll within the System.IO namespace.  Note that after the October CTP went out, we moved the pipe types from System.IO to System.IO.Pipes, so the types will be in the new namespace in all future CTPs.

Anonymous Pipes

Anonymous pipes are character-based and are half-duplex.  They cannot communicate over the network and support only a single server instance.  These pipes are most useful for communication between threads or between parent and child processes where the pipe handles can be easily passed when the child process is created.

Example 1: Anonymous Pipes

The following example demonstrates sending a string from a parent process to a child process.  A line is read from the console of the parent and sent to the child process.  The child process then writes the string that it received from the parent process to the console.

Parent Process

Process process = new Process();

process.StartInfo.FileName = "child.exe";

 

using (AnonymousPipeServerStream pipeStream =

    new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.Inheritable)) {

 

    process.StartInfo.Arguments = pipeStream.GetClientHandleAsString();

    process.StartInfo.UseShellExecute = false;

    process.Start();

 

    pipeStream.DisposeLocalCopyOfClientHandle();

 

    using (StreamWriter sw = new StreamWriter(pipeStream)) {

        sw.AutoFlush = true;

        sw.WriteLine(Console.ReadLine());

    }

}

 

process.WaitForExit();

process.Close();

Child Process

using (StreamReader sr = new StreamReader(

    new AnonymousPipeClientStream(PipeDirection.In, args[0]))) {

    string line;

    while ((line = sr.ReadLine()) != null) {

        Console.WriteLine("Echo: {0}", line);

    }

}

Example output:

Anonymous pipes are sweet!

Echo: Anonymous pipes are sweet!

Named Pipes

Named pipes are far more powerful than anonymous pipes.  They can be duplex, over the network, and can support multiple server instances of a single name, making them ideal for quick-to-build, and easy to connect to, multithreaded servers.  Furthermore, they support message based communication so that a reading process can read varying-length messages precisely as sent by the writing process.  Finally, named pipes support impersonation allowing connecting processes to use their own set of permissions on remote servers.

Example 2: Named Pipes

In cases where the second process cannot inherit the pipe handle, named pipes can be used.  The following example demonstrates sending strings from the client process to the server process.

Server Process

using (NamedPipeServerStream pipeStream = new NamedPipeServerStream("testpipe")) {

    pipeStream.WaitForConnection();

 

    using (StreamReader sr = new StreamReader(pipeStream)) {

        string temp;

        while ((temp = sr.ReadLine()) != null) {

            Console.WriteLine("{0}: {1}", DateTime.Now, temp);

        }

    }

}

Client Process

using (NamedPipeClientStream pipeStream = new NamedPipeClientStream("testpipe")) {

    pipeStream.Connect();

 

    using (StreamWriter sw = new StreamWriter(pipeStream)) {

        sw.AutoFlush = true;

        string temp;

        while ((temp = Console.ReadLine()) != null) {

            sw.WriteLine(temp);

        }

    }

}

Example 3: Named Pipes with Messaging

Named pipes also support message-based communication.  This allows a reading process to read varying-length messages precisely as sent by the writing process.  The following example displays how such messages are sent and read.

Process 1

UTF8Encoding encoding = new UTF8Encoding();

 

string message1 = "Named Pipe Message Example.";

string message2 = "Another Named Pipe Message Example.";

Byte[] bytes;

 

using (NamedPipeServerStream pipeStream = new

        NamedPipeServerStream("messagepipe", PipeDirection.InOut, 1,

        PipeTransmissionMode.Message, PipeOptions.None)) {

    pipeStream.WaitForConnection();

 

    // Let’s send two messages.

    bytes = encoding.GetBytes(message1);

    pipeStream.Write(bytes, 0, bytes.Length);

 

    bytes = encoding.GetBytes(message2);

    pipeStream.Write(bytes, 0, bytes.Length);

}

Process 2

Decoder decoder = Encoding.UTF8.GetDecoder();

Byte[] bytes = new Byte[10];

Char[] chars = new Char[10];

 

using (NamedPipeClientStream pipeStream =

        new NamedPipeClientStream("messagepipe")) {

    pipeStream.Connect();

    pipeStream.ReadMode = PipeTransmissionMode.Message;

 

    int numBytes;

    do {

        string message = "";

        do {

            numBytes = pipeStream.Read(bytes, 0, bytes.Length);

            int numChars = decoder.GetChars(bytes, 0, numBytes, chars, 0);

            message += new String(chars, 0, numChars);

 

        } while (!pipeStream.IsMessageComplete);

 

        decoder.Reset();

 

        Console.WriteLine(message);

 

    } while (numBytes != 0);

}

The above three examples show how easy it is to achieve IPC using the new pipe types we're introducing in the Orcas release.  These types expose nearly all the pipe functionality provided by Windows.

In a future blog post we'll talk about some of the more advanced functionality that these types provide, such as multithreaded named pipes with impersonation and asynchronous IO.  In the meantime, we'd love to hear your feedback on the pipe functionality we're exposing.