Using Named Pipes to Communicate






Using Named Pipes to Communicate

Problem

You need a way to use named pipes to communicate with another application across the network.

Solution

Create a P/Invoke wrapper class for the named-pipe APIs in Kernel32.dll. You can then create a managed client and managed server class to work with named pipes.

Figure shows the named-pipe interop wrappers in a class called NamedPipeInterop.

NamedPipeInterop class

namespace NamedPipes
{ 
    /// <summary> 
    /// Imported named-pipe entry points for P/Invoke into native code 
    /// </summary> 
    public class NamedPipeInterop 
    {
        // #defines related to named-pipe processing 
        public const int PIPE_ACCESS_OUTBOUND = 0x00000002; 
        public const int PIPE_ACCESS_DUPLEX = 0x00000003; 
        public const int PIPE_ACCESS_INBOUND = 0x00000001;

        public const int PIPE_WAIT = 0x00000000; 
        public const int PIPE_NOWAIT = 0x00000001; 
        public const int PIPE_READMODE_BYTE = 0x00000000; 
        public const int PIPE_READMODE_MESSAGE = 0x00000002; 
        public const int PIPE_TYPE_BYTE = 0x00000000; 
        public const int PIPE_TYPE_MESSAGE = 0x00000004;

        public const int PIPE_CLIENT_END = 0x00000000; 
        public const int PIPE_SERVER_END = 0x00000001;

        public const int PIPE_UNLIMITED_INSTANCES = 255;

        public const uint NMPWAIT_WAIT_FOREVER = 0xffffffff; 
        public const uint NMPWAIT_NOWAIT = 0x00000001; 
        public const uint NMPWAIT_USE_DEFAULT_WAIT = 0x00000000;

        public const uint GENERIC_READ = (0x80000000); 
        public const uint GENERIC_WRITE = (0x40000000); 
        public const uint GENERIC_EXECUTE = (0x20000000); 
        public const uint GENERIC_ALL = (0x10000000);

        public const int CREATE_NEW = 1;
        public const int CREATE_ALWAYS = 2;
        public const int OPEN_EXISTING = 3;
        public const int OPEN_ALWAYS = 4;
        public const int TRUNCATE_EXISTING = 5;

        public static IntPtr INVALID_HANDLE_VALUE = (IntPtr)(-1);
        public const int ERROR_PIPE_BUSY = 231;
        public const int ERROR_NO_DATA = 232;
        public const int ERROR_PIPE_NOT_CONNECTED = 233;
        public const int ERROR_MORE_DATA = 234;
        public const int ERROR_PIPE_CONNECTED = 535;
        public const int ERROR_PIPE_LISTENING = 536;

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool CallNamedPipe(
            string lpNamedPipeName,
            byte[] lpInBuffer,
            uint nInBufferSize,
            byte[] lpOutBuffer,
            uint nOutBufferSize,
            byte[] lpBytesRead,
            uint nTimeOut);

        [DllImport("kernel32.dll", SetLastError = true)] 
        public static extern bool CloseHandle(SafeFileHandle hObject);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool ConnectNamedPipe( 
            SafeFileHandle hNamedPipe,// Handle to named pipe 
            IntPtr lpOverlapped // Overlapped structure 
            );

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern SafeFileHandle CreateNamedPipe( 
            String lpName,       // Pipe name 
            uint dwOpenMode,     // Pipe open mode 
            uint dwPipeMode,     // Pipe-specific modes 
            uint nMaxInstances,  // Maximum number of instances 
            uint nOutBufferSize,     // Output buffer size 
            uint nInBufferSize,  // Input buffer size 
            uint nDefaultTimeOut,    // Time-out interval 
            //SecurityAttributes attr 
            IntPtr pipeSecurityDescriptor  // Security descriptor 
            );

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern SafeFileHandle CreatePipe(
            SafeFileHandle hReadPipe,
            SafeFileHandle hWritePipe,
            IntPtr lpPipeAttributes,
            uint nSize);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern SafeFileHandle CreateFile( 
            String lpFileName,// File name 
            uint dwDesiredAccess,// Access mode 
            uint dwShareMode,// Share mode 
            IntPtr attr,     // Security descriptor 
            uint dwCreationDisposition,  // How to create 
            uint dwFlagsAndAttributes,   // File attributes 
            uint hTemplateFile);// Handle to template file

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool DisconnectNamedPipe(SafeFileHandle hNamedPipe);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool FlushFileBuffers(SafeFileHandle hFile);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool GetNamedPipeHandleState( 
            SafeFileHandle hNamedPipe, 
            IntPtr lpState, 
            IntPtr lpCurInstances, 
            IntPtr lpMaxCollectionCount, 
            IntPtr lpCollectDataTimeout, 
            string lpUserName, 
            uint nMaxUserNameSize);

        [DllImport("KERNEL32.DLL", SetLastError = true)]
        public static extern bool GetNamedPipeInfo( 
            SafeFileHandle hNamedPipe, 
            out uint lpFlags, 
            out uint lpOutBufferSize, 
            out uint lpInBufferSize, 
            out uint lpMaxInstances);

        [DllImport("KERNEL32.DLL", SetLastError = true)]
        public static extern bool PeekNamedPipe( 
            SafeFileHandle hNamedPipe, 
            byte[] lpBuffer, 
            uint nBufferSize, 
            byte[] lpBytesRead, 
            out uint lpTotalBytesAvail, 
            out uint lpBytesLeftThisMessage);

        [DllImport("KERNEL32.DLL", SetLastError = true)]
        public static extern bool SetNamedPipeHandleState( 
            SafeFileHandle hNamedPipe, 
            ref int lpMode, 
            IntPtr lpMaxCollectionCount, 
            IntPtr lpCollectDataTimeout);

        [DllImport("KERNEL32.DLL", SetLastError = true)]
        public static extern bool TransactNamedPipe( 
            SafeFileHandle hNamedPipe, 
            byte[] lpInBuffer, 
            uint nInBufferSize, 
            [Out] byte[] lpOutBuffer, 
            uint nOutBufferSize, 
            IntPtr lpBytesRead, 
            IntPtr lpOverlapped);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool WaitNamedPipe( 
            string name, 
            uint timeout);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool ReadFile( 
            SafeFileHandle hFile,           // Handle to file 
            byte[] lpBuffer,        // Data buffer 
            uint nNumberOfBytesToRead,  // Number of bytes to read 
            byte[] lpNumberOfBytesRead, // Number of bytes read 
            uint lpOverlapped       // Overlapped buffer 
            );

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool WriteFile(
            SafeFileHandle hFile,              // Handle to file
            byte[] lpBuffer,           // Data buffer
            uint nNumberOfBytesToWrite,    // Number of bytes to write
            byte[] lpNumberOfBytesWritten,  // Number of bytes written
            uint lpOverlapped         // Overlapped buffer
            );
    }

Now, using the interop wrappers, you can create a named-pipe client class named NamedPipeClient, as shown in Figure.

NamedPipeClient class

using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Runtime.InteropServices; 
using System.Diagnostics; 
using System.ComponentModel; 
using System.IO; 
using System.Threading; 
using Microsoft.Win32.SafeHandles;

namespace NamedPipes
{ 
    /// <summary> 
    /// NamedPipeClient - An implementation of a synchronous, message-based, 
    /// named pipe client 
    /// 
    /// </summary> 
    public class NamedPipeClient : IDisposable 
    {
        #region Private Members
        /// <summary>
        ///    The full name of the pipe being connected to
        /// </summary>
        private string _pipeName = "";

        /// <summary>
        /// The pipe handle once connected
        /// </summary>
        private SafeFileHandle _handle =
                new SafeFileHandle(NamedPipeInterop.INVALID_HANDLE_VALUE,true);
        /// <summary>
        /// Default response buffer size (1K)
        /// </summary>
        private int _responseBufferSize = 1024;

        /// <summary>
        /// Track if dispose has been called
        /// </summary>
        private bool disposed = false;

        /// <summary>
        /// Timeout for the retry after first failed connect
        /// </summary>
        private int _retryTimeout = 20000;

        /// <summary>
        /// Number of times to retry connecting
        /// </summary>
        private int _retryConnect = 5;
        #endregion

        #region Construction / Cleanup
        /// <summary>
        /// CTOR
        /// </summary>
        /// <param name="pipeName">name of the pipe</param>
        public NamedPipeClient(string pipeName)
        {
            _pipeName = pipeName; 
            Trace.WriteLine("NamedPipeClient using pipe name of " + _pipeName);
        }

        /// <summary>
        /// Finalizer
        /// </summary>
        ~NamedPipeClient()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        { 
            // Check to see if Dispose has already been called. 
            if (!this.disposed) 
            {
                ClosePipe();
            }
            disposed = true;
        }

        private void ClosePipe()
        {
            if (!_handle.IsInvalid)
            {
                _handle.Close();
            }
        }

        /// <summary>
        /// Close - because it is more intuitive than Dispose…
        /// </summary>
        public void Close()
        {
            ClosePipe();
        }
        #endregion

        #region Properties 
        /// <summary> 
        /// ResponseBufferSize Property - the size used to create response buffers 
        /// for messages written using WriteMessage 
        /// </summary> 
        public int ResponseBufferSize 
        {
            get
            {
                return _responseBufferSize;
            }
            set
            {
                 _responseBufferSize = value;
            }
        }

        /// <summary>
        /// The number of milliseconds to wait when attempting to retry a connection
        /// </summary>
        public int RetryConnectCount
        {
            get
            {
                return _retryConnect;
            }
            set
            {
                _retryConnect = value;
            }
        }
        /// <summary>
        /// The number of milliseconds to wait when attempting to retry a connection
        /// </summary>
        public int RetryConnectTimeout
        {
            get
            {
                return _retryTimeout;
            }
            set
            {
                _retryTimeout = value;
            }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Connect - connect to an existing pipe
        /// </summary>
        /// <returns>true if connected</returns>
        public void Connect()
        {
            if (!_handle.IsInvalid) 
                throw new InvalidOperationException("Pipe is already connected!");

            string errMsg = "";
            int errCode = 0;
            int retryAttempts = _retryConnect;
            // Keep trying to connect
            while (retryAttempts > 0)
            {
                // Mark off one attempt
                retryAttempts--;

                // Connect to existing pipe
                _handle = NamedPipeInterop.CreateFile(_pipeName, 
                    NamedPipeInterop.GENERIC_READ | 
                    NamedPipeInterop.GENERIC_WRITE, 
                    0, 
                    IntPtr.Zero, 
                    NamedPipeInterop.OPEN_EXISTING, 
                    0, 
                    0);

                // Check to see if we connected
                if (!_handle.IsInvalid)
                    break;

                // The pipe could not be opened as all instances are busy. 
                // Any other error we bail for.
                errCode = Marshal.GetLastWin32Error(); 
                if (errCode != 
                    NamedPipeInterop.ERROR_PIPE_BUSY) 
                { 
                    errMsg = string.Format("Could not open pipe {0} with error {1}" 
pipeName,errCode);
                    Trace.WriteLine(errMsg);
                    throw new Win32Exception(errCode, errMsg);
                }
                // If it was busy, see if we can wait it out
                else if (!NamedPipeInterop.WaitNamedPipe(_pipeName, (uint)_retryTimeout))
                {
                    errCode = Marshal.GetLastWin32Error(); 
                    errMsg = 
                        string.Format("Wait for pipe {0} timed out after {1} milliseconds 
with error code {2}.",
                                   _pipeName, _retryTimeout,errCode); 
                    Trace.WriteLine(errMsg); 
                    throw new Win32Exception(errCode, errMsg);
                }
            }
            // Indicate connection in debug
            Trace.WriteLine("Connected to pipe: " + _pipeName);

            // The pipe connected; change to message-read mode
            bool success = false;
            int mode = (int)NamedPipeInterop.PIPE_READMODE_MESSAGE;

            // Set to message mode
            success = NamedPipeInterop.SetNamedPipeHandleState(
                _handle,    // Pipe handle
                ref mode,  // New pipe mode
                IntPtr.Zero,     // Don't set maximum bytes
                IntPtr.Zero);   // Don't set maximum time

            // Currently implemented for just synchronous, message-based pipes
            // so bail if we couldn't set the client up properly
            if (false == success)
            {
                errCode = Marshal.GetLastWin32Error(); 
                errMsg = 
                    string.Format("Could not change pipe mode to message with error code 
                      {0}",
                        errCode);
                Trace.WriteLine(errMsg); 
                Dispose(); 
                throw new Win32Exception(errCode, errMsg);
            }
        }

        /// <summary>
        /// WriteMessage - write an array of bytes and return the response from the
        /// server
        /// </summary>
        /// <param name="buffer">bytes to write</param>
        /// <param name="bytesToWrite">number of bytes to write</param>
        /// <returns>true if written successfully</returns>
        public MemoryStream WriteMessage(byte[] buffer, // the write buffer
        uint bytesToWrite) // Number of bytes in the write buffer
        // Message responses
        {
            // Buffer to get the number of bytes read/written back
            byte[] _numReadWritten = new byte[4];
            MemoryStream responseStream = null;

            bool success = false;
            // Write the byte buffer to the pipe
            success = NamedPipeInterop.WriteFile(_handle,
                buffer,
                bytesToWrite,
                _numReadWritten,
                0);

            if (success)
            { 
                byte[] responseBuffer = new byte[_responseBufferSize]; 
                responseStream = new MemoryStream(_responseBufferSize); 
                {
                    do
                    { 
                        // Read the response from the pipe. 
                        success = NamedPipeInterop.ReadFile(
                            _handle,    // Pipe handle 
                            responseBuffer,   // Buffer to receive reply 
                            (uint)_responseBufferSize,// Size of buffer 
                            _numReadWritten, // Number of bytes read 
                            0); // Not overlapped

                        // Failed, not just more data to come
                        if (!success && Marshal.GetLastWin32Error() != NamedPipeInterop
                ERROR_MORE_DATA) 
                           break;

                       // Concat response to stream
                       responseStream.Write(responseBuffer, 
                           0,
                           responseBuffer.Length);
                   } while (!success);
              }
          }
          return responseStream;
        }
        #endregion
    } 
}

Then you need to create a server class for testing, which you can call NamedPipeServer, as shown in Figure.

NamedPipeServer class

using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Runtime.InteropServices; 
using System.Diagnostics; 
using System.ComponentModel; 
using System.IO; 
using System.Threading; 
using Microsoft.Win32.SafeHandles;

namespace NamedPipes
{ 
    /// <summary> 
    /// NamedPipeServer - An implementation of a synchronous, message-based, 
    /// named-pipe server 
    /// 
    /// </summary> 
    public class NamedPipeServer : IDisposable 
    {
        #region Private Members
        /// <summary>
        /// The pipe handle
        /// </summary>
        private SafeFileHandle _handle = new SafeFileHandle(NamedPipeInterop.
INVALID_HANDLE_VALUE, true);
        /// <summary>
        /// The name of the pipe
        /// </summary>
        private string _pipeName = "";

        /// <summary>
        /// Default size of message buffer to read
        /// </summary>
        private int _receiveBufferSize = 1024;

        /// <summary>
        /// Track if dispose has been called
        /// </summary>
        private bool disposed = false;

        /// <summary>
        /// PIPE_SERVER_BUFFER_SIZE set to 8192 by default
        /// </summary>
        private const int PIPE_SERVER_BUFFER_SIZE = 8192;
        #endregion

        #region Construction / Cleanup
        /// <summary>
        /// CTOR
        /// </summary>
        /// <param name="pipeBaseName">the base name of the pipe</param>
        /// <param name="msgReceivedDelegate">delegate to be notified when
        /// a message is received</param>
        public NamedPipeServer(string pipeBaseName)
        {
            // Assemble the pipe name
            _pipeName = "\\\\.\\PIPE\\" + pipeBaseName;
            Trace.WriteLine("NamedPipeServer using pipe name " + _pipeName);
        }

        /// <summary>
        /// Finalizer
        /// </summary>
        ~NamedPipeServer()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called. 
            if (!this.disposed) 
            {
                ClosePipe();
            }
            disposed = true;
        }

        private void ClosePipe() 
        { 
            Trace.WriteLine("NamedPipeServer closing pipe");

            if (!_handle.IsInvalid)
            {
                _handle.Close();
            }
        }
        /// <summary>
        /// Close - because it is more intuitive than Dispose…
        /// </summary>
        public void Close()
        {
            ClosePipe();
        }
        #endregion

        #region Properties
        /// <summary>
        /// PipeName
        /// </summary>
        /// <returns>the composed pipe name</returns>
        public string PipeName
        {
            get
            {
                return _pipeName;
            }
        }

        /// <summary> 
        /// ReceiveBufferSize Property - the size used to create receive buffers 
        /// for messages received using WaitForMessage 
        /// </summary> 
        public int ReceiveBufferSize 
        {
            get
            {
                return _receiveBufferSize;
            }
            set
            {
                _receiveBufferSize = value;
            }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// CreatePipe - create the named pipe
        /// </summary>
        /// <returns>true is pipe created</returns>
        public bool CreatePipe()
        {
            // Make a named pipe in message mode
            _handle = NamedPipeInterop.CreateNamedPipe(_pipeName, 
                NamedPipeInterop.PIPE_ACCESS_DUPLEX, 
                NamedPipeInterop.PIPE_TYPE_MESSAGE | NamedPipeInterop.PIPE_READMODE_
            MESSAGE | 
                NamedPipeInterop.PIPE_WAIT, 
                NamedPipeInterop.PIPE_UNLIMITED_INSTANCES, 
                PIPE_SERVER_BUFFER_SIZE, 
                PIPE_SERVER_BUFFER_SIZE, 
                NamedPipeInterop.NMPWAIT_WAIT_FOREVER, 
                IntPtr.Zero);
            // Make sure we got a good one
            if (_handle.IsInvalid)
            {
                Debug.WriteLine("Could not create the pipe (" + 
                    _pipeName + ") - os returned " +
                    Marshal.GetLastWin32Error());

                return false;
            }
            return true;
        }

        /// <summary>
        /// WaitForClientConnect - wait for a client to connect to this pipe
        /// </summary>
        /// <returns>true if connected, false if timed out</returns>
        public bool WaitForClientConnect()
        {
            // Wait for someone to talk to us. 
            return NamedPipeInterop.ConnectNamedPipe(_handle, IntPtr.Zero); 
        }

        /// <summary>
        /// WaitForMessage - have the server wait for a message
        /// </summary>
        /// <returns>a non-null MessageStream if it got a message, null if timed
 out or error 
        /// </returns>
        public MemoryStream WaitForMessage() 
        {
            bool fullyRead = false;
            string errMsg = "";
            int errCode = 0;
            // They want to talk to us, read their messages and write
            // replies
            MemoryStream receiveStream = new MemoryStream();
            byte[] buffer = new byte[_receiveBufferSize];
            byte[] _numReadWritten = new byte[4];

            // Need to read the whole message and put it in one message
            // byte buffer
            do
            {

                // Read the response from the pipe
                if (!NamedPipeInterop.ReadFile(
                    _handle,    // Pipe handle
                    buffer,   // Buffer to receive reply
                    (uint)_receiveBufferSize,     // Size of buffer
                    _numReadWritten,  // Number of bytes read
                    0)) // Not overlapped
                {
                    // Failed, not just more data to come
                    errCode = Marshal.GetLastWin32Error( );
                    if (errCode != NamedPipeInterop.ERROR_MORE_DATA)
                        break;
                    else
                    {
                        errMsg = string.Format("Could not read from pipe with error {0}",
errCode);
                        Trace.WriteLine(errMsg);
                        throw new Win32Exception(errCode, errMsg);
                    }
                }
                else
                {
                    // We succeeded and no more data is coming
                    fullyRead = true;
                }
                // Concat the message bytes to the stream
                receiveStream.Write(buffer, 0, buffer.Length);

            } while (!fullyRead);

            if (receiveStream.Length > 0)
            {
                // Now set up response with a polite response using the same
                // Unicode string protocol
                string reply = "Thanks for the message!";
                byte[] msgBytes = Encoding.Unicode.GetBytes(reply);

                uint len = (uint)msgBytes.Length;
                // Write the response message provided
                // by the delegate
                if (!NamedPipeInterop.WriteFile(_handle,
                    msgBytes,
                    len,
                    _numReadWritten,
                    0))
                {

                    errCode = Marshal.GetLastWin32Error( );
                    errMsg = string.Format("Could not write response with error {0}",
errCode);
                    Trace.WriteLine(errMsg);
                    throw new Win32Exception(errCode, errMsg);
                }
                // Return the message we received.
                return receiveStream;
            }
            else // Didn't receive anything
                return null;
        }
        #endregion
    }
}

In order to use the NamedPipeClient class, you need some code like that shown in Figure.

Using the NamedPipeClient class

using System; 
using System.Collections.Generic; 
using System.Text; 
using System.IO; 
using System.Diagnostics;

namespace NamedPipes
{ 
    class NamedPipeClientConsole 
    {
        static void Main(string[] args)
        { 
            // Client test code - commented out as it should go in a separate 
            // console test app

            // Create our pipe client
            NamedPipeClient _pc =
                new NamedPipeClient("\\\\.\\PIPE\\mypipe");

            if (_pc != null)
            {
                using (_pc)
                {
                    // Connect to the server
                    _pc.Connect();
                    // Set up a dummy message
                    string testString = "This is my message!";

                    // Turn it into a byte array
                    byte[] writebuffer = Encoding.Unicode.GetBytes(testString);
                    uint len = Convert.ToUInt32(writebuffer.Length);

                    // Write the message ten times
                    for (int i = 0; i < 10; i++)
                    {
                        MemoryStream response = _pc.WriteMessage(writebuffer, len);
                        if (response == null)
                        {
                            Debug.Assert(false,
                                "Failed to write message!");
                        }
                        else
                        {
                            WriteMessageResponse(response);
                        }
                    }
                }
            }
            Console.WriteLine("Press Enter to exit…");
            Console.ReadLine();
        }

        static void WriteMessageResponse(MemoryStream responseStream)
        {
            string response =
                Encoding.Unicode.GetString(responseStream.ToArray());
            Console.WriteLine("Received response: {0}", response);
        }
    }
}

Then, to set up a server for the client to talk to, you use the NamedPipeServer class as shown in Figure.

Setting up a server for the client

using System;
using System Collections.Generic;
using System.Text;
using System.IO;
using System.ComponentModel;

namespace NamedPipes
{
    class NamedPipeServerConsole
    {
        static void Main(string[] args)
        {
            // Server test code - commented out as it should go in a separate
            // console test app as shown in the book

            // Create pipe server
            using (NamedPipeServer _ps =
                new NamedPipeServer("mypipe"))
            {

                // Create pipe
                if (_ps.CreatePipe())
                {
                    // I get the name of the pipe here just to show you can
                    // Normally we would then have to get this name to the client
                    // so it knows the name of the pipe to open but hey, I wrote
                    // the client to so for now I'm just hardcoding it in the
                    // client so we can ignore it
                    string pipeName = _ps.PipeName;

                    // Wait for clients to connect
                    if (_ps.WaitForClientConnect())
                    {
                        // Process messages until the read fails
                        // (client goes away…)
                        bool success = true;
                        while (success)
                        {
                            try
                            {
                                // Wait for a message from the client
                                MemoryStream messageStream = _ps.WaitForMessage();
                                if (messageStream != null)
                                {
                                    // Get the bytes of the message from the stream
                                    byte[] msgBytes = messageStream.ToArray();
                                    string messageText;

                                    // I know in the client I used a Unicode encoding
                                    // for the string to turn it into a series of bytes
                                    // for transmission so just reverse that
                                    messageText = Encoding.Unicode.GetString(msgBytes);

                                    // Write out our string message from the client
                                    Console.WriteLine(messageText);
                              } else
                                    success = false;
                            }
                            catch (Win32Exception)
                            {
                                success = false;
                            }
                        }
                    }
                }
            }
            // Make our server hang around so you can see the messages sent
            Console.WriteLine("Press Enter to exit…");
            Console.ReadLine();
        }
    }
}

Discussion

Named pipes are a mechanism to allow interprocess or intermachine communications in Windows. The .NET Framework currently has not provided managed access to named pipes, so the first thing you need to do is to wrap the functions in Kernel32.dll for direct access from managed code in your NamedPipesInterop class.

Once you have this foundation, you can then build a client for using named pipes to talk to a server, as in the NamedPipeClient class. The methods on NamedPipeClient are listed in Figure with a description for each.

NamedPipeClient methods

Method

Description

Close

Close method, which calls the Dispose method.

Connect

Connects to a named-pipe server.

Dispose

Dispose method for the named-pipe client so that the pipe handle is not held any longer than necessary.

NamedPipeClient

Constructor for the named-pipe client.

~NamedPipeClient

Finalizer for the named-pipe client. This makes sure the pipe handle is closed.

WriteMessage

Writes a message to the connected server.


You then create the NamedPipeServer class to be able to have something for the NamedPipeClient to connect to. The methods on the NamedPipeServer are listed in Figure with a description for each as well.

NamedPipeServer methods

Method

Description

Close

Close method that calls the Dispose method. Many developers use Close, so it is provided for completeness.

CreatePipe

Creates a listener pipe on the server.

Dispose

Dispose method for the named-pipe server so that pipe handles are not held any longer than necessary.

NamedPipeServer

Constructor for the named-pipe server.

~NamedPipeServer

Finalizer for the named-pipe server. This makes sure the pipe handle is closed.

PipeName

Returns the composed pipe name.

WaitForClientConnect

Wait on the pipe handle for a client to talk to.

WaitForMessage

Have the server wait for a message from the client.


Finally, you create some code to use NamedPipeClient and NamedPipeServer. The interaction between these two goes like this:

  1. The server process is started; it fires up a NamedPipeServer, calls CreatePipe to make a pipe, then calls WaitForClientConnect to wait for the NamedPipeClient to connect:

    	using (NamedPipeServer _ps =
    	    new NamedPipeServer("mypipe"))
    	{
    	    // Create pipe
    	    if (_ps.CreatePipe())
    	    {
    	        // Wait for clients to connect
    	        if (_ps.WaitForClientConnect()) 
    	        {
    

  2. The client process is created; it fires up a NamedPipeClient, calls Connect, and connects to the server process:

    	// Create our pipe client
    	NamedPipeClient _pc =
    	    new NamedPipeClient("\\\\.\\PIPE\\mypipe");
    
    	if (_pc != null)
    	{
    	    using (_pc)
    	    {
    	        // Connect to the server
    	        _pc.Connect();
    

  3. The server process sees the connection from the client and then calls WaitForMessage in a loop. WaitForMessage starts reading the pipe, which blocks until a message is written to the pipe by the client.

    	    // Process messages until the read fails
    	    // (client goes away…)
    	    bool success = true;
    	    while (success)
    	    {
    	        try
    	        {
    	            // Wait for a message from the client
    	            MemoryStream messageStream = _ps.WaitForMessage();
    
    	            // More processing code in here…
    	        }
    	        catch (Win32Exception)
    	        {
    	            success = false;
    	        }
    	    }
    

  4. The client process then writes a number of messages to the server process using WriteMessage:

    	    // Set up a dummy message
    	    string testString = "This is my message!";
    	    // Turn it into a byte array
    	    byte[] writebuffer = Encoding.Unicode.GetBytes(testString);
    	    uint len = Convert.ToUInt32(writebuffer.Length);
    
    	    // Write the message ten times
    	    for (int i = 0; i < 10; i++)
    	    {
    	        MemoryStream response = _pc.WriteMessage(writebuffer, len);
    	        if (response == null)
    	        {
    	            Debug.Assert(false,
    	                "Failed to write message!");
    	        }
    	        else
    	        {
    	            WriteMessageResponse(response);
    	        }
    	    }
    

  5. In WaitForMessage, shown in Figure, the server process sees the message, processes it, writes a response to the client, returns a MemoryStream with the received message in it, then goes back to waiting.

    WaitForMessage method

    public MemoryStream WaitForMessage()
    {
        bool fullyRead = false;
        string errMsg = "";
        int errCode = 0;
        // They want to talk to us, read their messages and write
        // replies
        MemoryStream receiveStream = new MemoryStream();
        byte[] buffer = new byte[_receiveBufferSize];
        byte[] _numReadWritten = new byte[4];
    
        // Need to read the whole message and put it in one message
        // byte buffer
        do
        {
            // Read the response from the pipe
            if (!NamedPipeInterop.ReadFile(
                _handle,    // Pipe handle
                buffer,    // Buffer to receive reply
                (uint)_receiveBufferSize,      // Size of buffer
                _numReadWritten,  // Number of bytes read
                0)) // Not overlapped
            {
                // Failed, not just more data to come
                errCode = Marshal.GetLastWin32Error();
                if (errCode != NamedPipeInterop.ERROR_MORE_DATA)
                    break;
                else
                {
                    errMsg = string.Format("Could not read from pipe with error {0}",
    errCode);
                    Trace.WriteLine(errMsg);
                    throw new Win32Exception(errCode, errMsg);
                }
            }
            else
            {
                // We succeeded and no more data is coming
                fullyRead = true;
            }
            // Concat the message bytes to the stream
            receiveStream.Write(buffer, 0, buffer.Length);
    
        } while (!fullyRead);
    
        if (receiveStream.Length > 0)
        {
            // Now set up response with a polite response using the same
            // Unicode string protocol
            string reply = "Thanks for the message!";
            byte[] msgBytes = Encoding.Unicode.GetBytes(reply);
    
            uint len = (uint)msgBytes.Length;
            // Write the response message provided
            // by the delegate
            if (!NamedPipeInterop.WriteFile(_handle,
                msgBytes,
                len,
                _numReadWritten,
                0))
            {
                errCode = Marshal.GetLastWin32Error();
                errMsg = string.Format("Could not write response with error {0}",
                errCode);
                Trace.WriteLine(errMsg);
                throw new Win32Exception(errCode, errMsg);
            }
    
            // Return the message we received
            return receiveStream;
        }
        else // Didn't receive anything
            return null;
    }
    

  6. When the client process receives the response from the server, it returns a MemoryStream with the response for processing. If the message sending is complete, the NamedPipeClient goes out of the scope of the using statement and closes (thereby closing the connection on the client side) and then waits to go away when the user presses Enter.

    	                // Write a message and get a response stream
    	                MemoryStream response = _pc.WriteMessage(writebuffer, len);
    	                if (response == null)
    	                {
    	                    Debug.Assert(false,
    	                        "Failed to write message!");
    	                } else
    	                {
    	                    // Process response message to console
    	                    WriteMessageResponse(response);
    	                }
    
    	static void WriteMessageResponse(MemoryStream responseStream)
    	{
    	    string response =
    	        Encoding.Unicode.GetString(responseStream.ToArray());
    	    Console.WriteLine("Received response: {0}", response);
    	}
    

  7. The server process notes that the client has closed the pipe connection via the failed NamedPipesInterop.ReadFile call in WaitForMessage. It calls Close to clean up, then waits for the user to press Enter to terminate the process.

The client output looks like this:

	Received response: Thanks for the message!
	Received response: Thanks for the message!
	Received response: Thanks for the message!
	Received response: Thanks for the message!
	Received response: Thanks for the message!
	Received response: Thanks for the message!
	Received response: Thanks for the message!
	Received response: Thanks for the message!
	Received response: Thanks for the message!
	Received response: Thanks for the message!
	Press Enter to exit…

The server output looks like this:

	This is my message!
	This is my message!
	This is my message!
	This is my message!
	This is my message!
	This is my message!
	This is my message!
	This is my message!
	This is my message!
	This is my message!
	Press Enter to exit…

See Also

See the "Named Pipes," "DllImport Attribute," "IDisposable Interface," and "GC. SuppressFinalize Method" topics in the MSDN documentation.



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows