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 .



C# Cookbook
C# 3.0 Cookbook
ISBN: 059651610X
EAN: 2147483647
Year: 2003
Pages: 315

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net