Recipe 16.5. 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. Example 16-3 shows the named-pipe interop wrappers in a class called NamedPipeInterop. Example 16-3. 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 Example 16-4. Example 16-4. 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 Example 16-5. Example 16-5. 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 Example 16-6. Example 16-6. 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 Example 16-7. Example 16-7. 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 Table 16-1 with a description for each. Table 16-1. NamedPipeClient methodsMethod | 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 Table 16-2 with a description for each as well. Table 16-2. NamedPipeServer methodsMethod | 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: 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()) { 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(); 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; } } 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); } } In WaitForMessage, shown in Example 16-8, 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. Example 16-8. 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; } |
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); } 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. |