DekGenius.com
[ Team LiB ] Previous Section Next Section

Recipe 13.13 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 to allow for managed access, and then create a managed client and managed server class to work with named pipes.

Here are the named pipe interop wrappers in a class called NamedPipeInterop:

namespace NamedPipes
{
/// <summary>
/// Imported namedpipe entry points for p/invoke into native code.
/// </summary>
[SuppressUnmanagedCodeSecurity]
public class NamedPipeInterop 
{
    // #defines related to named pipe processing
    public const uint PIPE_ACCESS_OUTBOUND = 0x00000002;
    public const uint PIPE_ACCESS_DUPLEX = 0x00000003;
    public const uint PIPE_ACCESS_INBOUND = 0x00000001;

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

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

    public const uint 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 uint CREATE_NEW        = 1;
    public const uint CREATE_ALWAYS     = 2;
    public const uint OPEN_EXISTING     = 3;
    public const uint OPEN_ALWAYS       = 4;
    public const uint TRUNCATE_EXISTING = 5;

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

    public static int GetLastError( )
    {
        return Marshal.GetLastWin32Error( );
    }

    [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(int hObject);        

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

    [DllImport("kernel32.dll", SetLastError=true)]
    public static extern int 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 int CreatePipe(
        int hReadPipe,
        int hWritePipe,
        IntPtr lpPipeAttributes,
        uint nSize);

    [DllImport("kernel32.dll", SetLastError=true)]
    public static extern int CreateFile(
        String lpFileName,         // filename
        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(int hNamedPipe);

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

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

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

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

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

    [DllImport("KERNEL32.DLL", SetLastError=true)]
    public static extern bool TransactNamedPipe(
        int 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(
        int 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(
        int 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
        );
}

} // end namespace NamedPipes

Now, using the interop wrappers, we can create a named pipe client class named NamedPipeClient:

namespace NamedPipes
{
    /// <summary>
    /// NamedPipeClient - An implementation of a synchronous,
    /// message-based, named pipe client
    ///
    ///</summary>

public class NamedPipeClient : IDisposable
{
    /// <summary>
    ///    the full name of the pipe being connected to  
    /// </summary>
    string _pipeName = "";

    /// <summary>
    /// the pipe handle once connected
    /// </summary>
    int _handle = NamedPipeInterop.INVALID_HANDLE_VALUE;

    /// <summary>
    /// default response buffer size (1K) 
    /// </summary>
    uint _responseBufferSize = 1024;

    /// <summary>
    /// indicates if this has been closed once which calls
    /// for us to re-register for finalization on subsequent
    /// connect calls
    /// </summary>
    bool disposedOnce = false;

    /// <summary>
    /// WriteMessageResponseDelegate - callback for when a response 
    /// to when a WriteMessage returns from the server
    ///
    /// </summary>
    public delegate void WriteMessageResponseDelegate(MemoryStream responseStream); 

    /// <summary>
    /// CTOR 
    /// </summary>
    /// <param name="pipeName">name of the pipe</param>
    public NamedPipeClient(string pipeName)
    {
        _pipeName = pipeName;
    }

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

    /// <summary>
    /// Dispose
    /// </summary>
    public void Dispose( )
    {
        if(_handle != NamedPipeInterop.INVALID_HANDLE_VALUE)
        {
            NamedPipeInterop.CloseHandle(_handle); 
            _handle = NamedPipeInterop.INVALID_HANDLE_VALUE;
        }
        // Suppress Finalization since we have now cleaned up our 
        // handle
        System.GC.SuppressFinalize(this);
        // indicate we have disposed at least once
        if(disposedOnce == false)
            disposedOnce = true;
    }

    /// <summary>
    /// Close - because it is more intuitive than Dispose... :)
    /// </summary>
    public void Close( )
    {
        Dispose( );
    }

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

    /// <summary>
    /// Connect - connect to an existing pipe 
    /// </summary>
    /// <returns>true if connected</returns>
    public bool Connect( )
    {
        if(disposedOnce == true)
            System.GC.ReRegisterForFinalize(this);

        if(_handle != NamedPipeInterop.INVALID_HANDLE_VALUE)
            throw new InvalidOperationException("Pipe is already connected!");        

        // keep trying to connect 
        while (true)
        {
            // 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 != NamedPipeInterop.INVALID_HANDLE_VALUE)
                break;
    
            // the pipe could not be opened as all instances are busy
            // any other error we bail for
            if(NamedPipeInterop.GetLastError( ) != 
                NamedPipeInterop.ERROR_PIPE_BUSY)
            {
                Debug.WriteLine("Could not open pipe: " + _pipeName);
                return false;
            }

            // if it was busy, see if we can wait it out for 20 seconds
            if(!NamedPipeInterop.WaitNamedPipe(_pipeName, 20000))
            {
                Debug.WriteLine("Specified pipe was over-burdened: " + 
                    _pipeName);
                return false;
            }
        }
        // indicate connection in debug
        Debug.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)
        {
            Debug.WriteLine("Could not change pipe mode to message," +
                " shutting client down.");
                                            Dispose( );
            return false;
        }
        return true;
    }

    /// <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>
    /// <param name="ResponseDelegate">callback with the message response</param>
    /// <returns>true if written successfully</returns>
    public bool WriteMessage(byte [] buffer,  // the write buffer
        uint bytesToWrite,  // number of bytes in the write buffer
        WriteMessageResponseDelegate ResponseDelegate) // callback for 
        // message responses
    {
        // buffer to get the number of bytes read/written back
        byte[] _numReadWritten = new byte[4]; 

        bool success = false;
        // Write the byte buffer to the pipe
        success = NamedPipeInterop.WriteFile(_handle,
            buffer,
            bytesToWrite,
            _numReadWritten,
            0);
    
        if(true == success)
        {
            byte[] responseBuffer = new byte[_responseBufferSize];
            int size = Convert.ToInt32(_responseBufferSize);
            MemoryStream fullBuffer = new MemoryStream(size);
            do 
            { 
                // Read the response from the pipe. 
                success = NamedPipeInterop.ReadFile( 
                    _handle,    // pipe handle 
                    responseBuffer,    // buffer to receive reply 
                    _responseBufferSize,      // size of buffer 
                    _numReadWritten,  // number of bytes read 
                    0);    // not overlapped 

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

                // concat response to stream
                fullBuffer.Write(responseBuffer,
                    0,
                    responseBuffer.Length);

            } while (! success);  // repeat loop if ERROR_MORE_DATA 

            // Callback the caller with this response buffer
            if(ResponseDelegate != null)
                ResponseDelegate(fullBuffer);
        } 
        return success;
    }
}
} // end namespace NamedPipes

Then we need to create a server class for testing, which we will call NamedPipeServer:

namespace NamedPipes
{
/// <summary>
/// NamedPipeServer - An implementation of a synchronous, message-based, 
/// named pipe server
///
/// </summary>
public class NamedPipeServer : IDisposable
{
    /// <summary>
    /// the pipe handle 
    /// </summary>
    int _handle = NamedPipeInterop.INVALID_HANDLE_VALUE;

    /// <summary>
    /// the name of the pipe 
    /// </summary>
    string _pipeName = "";

    /// <summary>
    /// the name of the machine the server pipe is on 
    /// </summary>
    string _machineName = "";

    /// <summary>
    /// default size of message buffer to read 
    /// </summary>
    uint _receiveBufferSize = 1024; 

    /// <summary>
    /// indicates if this has been closed once, which calls
    /// for us to re-register for finalization on subsequent
    /// connect calls
    /// </summary>
    bool disposedOnce = false;

    /// <summary>
    /// the internal delegate holder for the callback on message receipt 
    /// from clients 
    /// </summary>
    MessageReceivedDelegate _messageReceivedDelegate;

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

    /// <summary>
    /// MessageReceivedDelegate - callback for message received from 
    /// client
    ///
    /// </summary>
    public delegate void MessageReceivedDelegate(MemoryStream message,
        out MemoryStream response);

    /// <summary>
    /// CTOR 
    /// </summary>
    /// <param name="machineName">name of the machine the pipe is on, 
    /// use null for local machine</param>
    /// <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 machineName, 
        string pipeBaseName,
        MessageReceivedDelegate msgReceivedDelegate)
    {
        // hook up the delegate
        _messageReceivedDelegate =  msgReceivedDelegate;

        if(machineName == null)
            _machineName = ".";
        else
            _machineName = machineName;

        // assemble the pipe name
        _pipeName = "\\\\" + _machineName + "\\PIPE\\" + pipeBaseName;
    }

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

    /// <summary>
    /// Dispose - clean up handle 
    /// </summary>
    public void Dispose( )
    {
        // if we have a pipe handle, disconnect and clean up
        if(_handle > 0)
        {
            NamedPipeInterop.DisconnectNamedPipe(_handle);
            NamedPipeInterop.CloseHandle(_handle);
            _handle = 0;
        }
        // Suppress Finalization since we have now cleaned up our 
        // handle
        System.GC.SuppressFinalize(this);
        // indicate we have disposed at least once
        if(disposedOnce == false)
            disposedOnce = true;
    }

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

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

    /// <summary>
    /// CreatePipe - create the named pipe 
    /// </summary>
    /// <returns>true is pipe created</returns>
    public bool CreatePipe( )
    {
        if(disposedOnce == true)
            System.GC.ReRegisterForFinalize(this);

        // 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 == NamedPipeInterop.INVALID_HANDLE_VALUE) 
        {
            Debug.WriteLine("Could not create the pipe (" + 
                _pipeName + ") - os returned " + 
                NamedPipeInterop.GetLastError( ));

            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( )
    {
        bool success = false;
        // wait for someone to talk to us
        success = NamedPipeInterop.ConnectNamedPipe(_handle,IntPtr.Zero);
        if(true == success)
        {
            // process the first message
            while ( WaitForMessage( ) );
        }
        return success;
    }

    /// <summary>
    /// WaitForMessage - have the server wait for a message 
    /// </summary>
    /// <returns>true if got a message, false if timed out</returns>
    public bool WaitForMessage( )
    {
        bool success = false;
        // they want to talk to us, read their messages and write 
        // replies
        int size = Convert.ToInt32(_receiveBufferSize);
        MemoryStream fullMessageStream = new MemoryStream(size);
        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. 
            success = NamedPipeInterop.ReadFile( 
                _handle,    // pipe handle 
                buffer,    // buffer to receive reply 
                _receiveBufferSize,      // size of buffer 
                _numReadWritten,  // number of bytes read 
                0);    // not overlapped 

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

            // concat the message bytes to the stream
            fullMessageStream.Write(buffer,0,buffer.Length);

        } while (! success);  // repeat loop if ERROR_MORE_DATA 

        // we read a message from a client
        if(true == success)
        {
            // call delegate if connected for message processing
            MemoryStream responseStream;
            if(_messageReceivedDelegate != null)
            {
                // call delegate
                _messageReceivedDelegate(fullMessageStream,
                    out responseStream);

                if(responseStream != null)
                {
                    // get raw byte array from stream
                    byte [] responseBytes = 
                        responseStream.ToArray( );
                    uint len = 
                        Convert.ToUInt32(responseBytes.Length);
                    // write the response message provided 
                    // by the delegate
                    NamedPipeInterop.WriteFile(_handle,
                        responseBytes,
                        len,
                        _numReadWritten,
                        0);
                }
            }
        }
        return success;
    }
}
} // end namespace NamedPipes

In order to use the NamedPipeClient class, we need some code like the following:

using System;
using System.Diagnostics;
using System.Text;
using System.IO;

namespace NamedPipes
{
    class NamedPipesClientTest
    {
        static void Main(string[] args)
        {
            // create our pipe client
            NamedPipeClient _pc = 
                      new NamedPipeClient("\\\\.\\PIPE\\mypipe");

            if(_pc != null)
            {
              // connect to the server
              if(true == _pc.Connect( ))
              {
                  // set up a dummy message
                string testString = "This is my message!";
                UnicodeEncoding UEncoder = new UnicodeEncoding( );
              
                // turn it into a byte array
                byte[] writebuffer = UEncoder.GetBytes(testString);
                uint len = Convert.ToUInt32(writebuffer.Length);

                // write the message ten times
                for(int i=0;i<10;i++)
                {
                  if(false == _pc.WriteMessage(writebuffer,
                                             len,
            new NamedPipeClient.WriteMessageResponseDelegate(WriteMessageResponse)))
                 {
                    Debug.Assert(false,
                                         "Failed to write message!");
                }
                  }
                // close up shop
                _pc.Close( );
              }
            }
            Console.WriteLine("Press Enter to exit...");
            Console.ReadLine( );
        }

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

Then, to set up a server for the client to talk to, we would use the NamedPipeServer class, like this:

namespace NamedPipes
{
    class NamedPipesServerTest
    {
      //
      // MessageReceived - This is the method used in the delegate for the server
      // that gets called after every message is received and before it is replied to
      //
      static void MessageReceived(MemoryStream message,out MemoryStream response)
      {
         // get the bytes of the message from the stream
        byte [] msgBytes = message.ToArray( );
        string messageText;

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

        // write out our string message from the client
        Console.WriteLine(messageText);

        // now set up response with a polite response using the same 
          // Unicode string protocol
        string reply = "Thanks for the message!";
        msgBytes = UEncoder.GetBytes(reply);
        response = new MemoryStream(msgBytes,0,msgBytes.Length);
      }

      //
      // Main - nuff said
      //
      static void Main(string[] args)
      {
        // create pipe server
        NamedPipeServer _ps = 
           new NamedPipeServer(null,
                        "mypipe",
                new NamedPipeServer.MessageReceivedDelegate(MessageReceived)
                                );

        // create pipe
        if(true == _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 too so for now I'm just hard-coding it in the 
            // client so we can ignore it :)
          string pipeName = _ps.PipeName( );

          // wait for clients to connect and process the first message
          if(true == _ps.WaitForClientConnect( ))
          {
            // process messages until the read fails 
                  // (client goes away...)
            bool success = true;
            while(success)
            {
                success = _ps.WaitForMessage( );
            }
          }
          // done; bail and clean up the server
          _ps.Close( );
        }
        // 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. As of v1.1, the .NET Framework has not provided managed access to named pipes, so the first thing we need to do is to wrap the functions in Kernel32.dll for direct access from managed code in our NamedPipesInterop class.

Once we have this foundation, we can then build a client for using named pipes to talk to a server, exposing a pipe that we did in the NamedPipeClient class. The methods on the NamedPipeClient are listed here with a description:

Method

Description

NamedPipeClient

Constructor for the named pipe client.

~NamedPipeClient

Finalizer for the named pipe client. This ensures the used pipe handle is freed.

Dispose

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

Close

Close method which calls down to the Dispose method.

Connect

Used to connect to a named pipe server.

WriteMessage

Writes a message to the connected server.

WriteMessageResponseDelegate

A delegate to let clients see the server's response if they wish to.

We then create the NamedPipeServer class to be able to have something for the NamedPipeClient to connect to. The methods on the NamedPipeServer are listed here with a description as well:

Method

Description

NamedPipeServer

Constructor for the named pipe server.

~NamedPipeServer

Finalizer for the named pipe server. This ensures the used pipe handles are freed.

Dispose

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

Close

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

PipeName

Returns the composed pipe name.

CreatePipe

Creates a listener pipe on the server.

WaitForClientConnect

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

WaitForMessage

Have the server wait for a message from the client.

MessageReceivedDelegate

A delegate to notify users of the server that a message has been received.

Finally we created some code to use NamedPipeClient and NamedPipeServer. The interaction between these two goes like this:

  • 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.

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

  • 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 messages is written to the pipe by the client.

  • The client process then writes a message to the server process using WriteMessage.

  • The server process sees the message, processes it, and notifies anyone who signed up for notification via the MessageReceivedDelegate, then writes a response to the client, and then starts to wait again.

  • When the client process receives the response from the server, it notifies anyone who signed up for the WriteMessageResponseDelegate, closes the NamedPipeClient that closes the pipe connection on the client side, and waits to go away when the user presses Enter.

  • The server process notes the closing of the pipe connection by the client via the failed NamedPipesInterop.ReadFile call in WaitForMessage and calls Close on the server to clean up and wait for the user to press Enter to terminate the server process.

See Also

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

    [ Team LiB ] Previous Section Next Section