Inter-Process Communication (IPC) Introduction and Sample Code (.net) Inter-Process Communication (IPC) Introduction and Sample Code (.net)
#39 mailslot code messages channel remoting register net
Inter-Process Communication (IPC) is a set of techniques for the exchange of data among multiple threads in one or more processes. Processes may be running on one or more computers connected by a network. IPC techniques include Named Pipes, File Mapping, Mailslot, Remote Procedure Calls (RPC), etc.
In All-In-One Code Framework, we have already implemented samples (C++ and C#) for Named Pipes, File Mapping, Mail Slot, and Remoting. We are going to add more techniques like: Clickbord, Winsock, etc. You can download the latest code from http://cfx.codeplex.com/.
All-In-One Code Framework (short as AIO) delineates the framework and skeleton of most Microsoft development techniques (e.g., COM, Data Access, IPC) using typical sample codes in different programming languages (e.g., Visual C#, VB.NET, Visual C++).
Using the Code
Find samples by following the steps below:
- Download the zip file and unzip it.
- Open the folder [Visual Studio 2008].
- Open the solution file IPC.sln. You must pre-install Visual Studio 2008 on the machine.
- In the Solution Explorer, open the [Process] \ [IPC and RPC] folder.
Named pipes is a mechanism for one-way or bi-directional inter-process communication between a pipe server and one or more pipe clients in the local machine or across computers in an intranet:
[b]PIPE_ACCESS_INBOUND:[/b] Client (GENERIC_WRITE) ---> Server (GENERIC_READ) [b]PIPE_ACCESS_OUTBOUND:[/b] Client (GENERIC_READ) <--- Server (GENERIC_WRITE) [b]PIPE_ACCESS_DUPLEX:[/b] Client (GENERIC_READ or GENERIC_WRITE, or both) <--> Server (GENERIC_READ and GENERIC_WRITE)
This sample demonstrates a named pipe server, \\.\pipe\HelloWorld, that supports PIPE_ACCESS_DUPLEX. It first creates such a named pipe, then it listens to the client's connection. When a client is connected, the server attempts to read the client's requests from the pipe and writes a response.
A named pipe client attempts to connect to the pipe server, \\.\pipe\HelloWorld, with the GENERIC_READ and GENERIC_WRITE permissions. The client writes a message to the pipe server and receives its response.
- Create a named pipe. (CreateNamedPipe)
- Wait for the client to connect. (ConnectNamedPipe)
- Read client requests from the pipe and write the response. (ReadFile, WriteFile)
- Disconnect the pipe, and close the handle. (DisconnectNamedPipe, CloseHandle)
- Try to open a named pipe. (CreateFile)
- Set the read mode and the blocking mode of the specified named pipe. (SetNamedPipeHandleState)
- Send a message to the pipe server and receive its response. (WriteFile, ReadFile)
- Close the pipe. (CloseHandle)
// Create the named pipe. HANDLE hPipe = CreateNamedPipe( strPipeName, // The unique pipe name. This string must // have the form of \\.\pipe\pipename PIPE_ACCESS_DUPLEX, // The pipe is bi-directional; both // server and client processes can read // from and write to the pipe PIPE_TYPE_MESSAGE | // Message type pipe PIPE_READMODE_MESSAGE | // Message-read mode PIPE_WAIT, // Blocking mode is enabled PIPE_UNLIMITED_INSTANCES, // Max. instances // These two buffer sizes have nothing to do with the buffers that // are used to read from or write to the messages. The input and // output buffer sizes are advisory. The actual buffer size reserved // for each end of the named pipe is either the system default, the // system minimum or maximum, or the specified size rounded up to the // next allocation boundary. The buffer size specified should be // small enough that your process will not run out of nonpaged pool, // but large enough to accommodate typical requests. BUFFER_SIZE, // Output buffer size in bytes BUFFER_SIZE, // Input buffer size in bytes NMPWAIT_USE_DEFAULT_WAIT, // Time-out interval &sa // Security attributes )
For more code samples, please download AIO source code.
Security Attribute for Named Pipes
If lpSecurityAttributes of CreateNamedPipe is NULL, the named pipe gets a default security descriptor and the handle cannot be inherited. The ACLs in the default security descriptor for a named pipe grants full control to the LocalSystem account, administrators, and the creator owner. They also grant read access to members of the Everyone group and the anonymous account. In other words, with NULL as the security attribute, the named pipe cannot be connected with WRITE permission across the network, or from a local client running as a lower integrity level. Here, we fill the security attributes to grant EVERYONE all access (not just the connect access) to the server. This solves the cross-network and cross-IL issues, but it creates a security hole right there: the clients have WRITE_OWNER access and then the server just loses the control of the pipe object.
Code - Security Attributes (C++)
SECURITY_ATTRIBUTES sa; sa.lpSecurityDescriptor = (PSECURITY_DESCRIPTOR)malloc(SECURITY_DESCRIPTOR_MIN_LENGTH); InitializeSecurityDescriptor(sa.lpSecurityDescriptor, SECURITY_DESCRIPTOR_REVISION); // ACL is set as NULL in order to allow all access to the object. SetSecurityDescriptorDacl(sa.lpSecurityDescriptor, TRUE, NULL, FALSE); sa.nLength = sizeof(sa); sa.bInheritHandle = TRUE;
.NET Named Pipe
.NET supports named pipes in two ways:
- P/Invoke the native APIs.
By P/Invoke-ing the native APIs from .NET, we can mimic the code logic in CppNamedPipeServer to create the named pipe server, \\.\pipe\HelloWorld, that supports PIPE_ACCESS_DUPLEX.
PInvokeNativePipeServer first creates such a named pipe, then it listens to the client's connection. When a client is connected, the server attempts to read the client's requests from the pipe and write a response.
- System.IO.Pipes namespace
In .NET Framework 3.5, the namespace System.IO.Pipes and a set of classes (e.g., PipeStream, NamedPipeServerStream) are added to the .NET BCL. These classes make the programming of named pipes in .NET much easier and safer than P/Invoke-ing the native APIs directly.
BCLSystemIOPipeServer first creates such a named pipe, then it listens to the client's connection. When a client is connected, the server attempts to read the client's requests from the pipe and write a response.
// Prepare the security attributes // Granting everyone the full control of the pipe is just for // demo purpose, though it creates a security hole. PipeSecurity pipeSa = new PipeSecurity(); pipeSa.SetAccessRule(new PipeAccessRule("Everyone", PipeAccessRights.ReadWrite, AccessControlType.Allow)); // Create the named pipe pipeServer = new NamedPipeServerStream( strPipeName, // The unique pipe name. PipeDirection.InOut, // The pipe is bi-directional NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Message, // Message type pipe PipeOptions.None, // No additional parameters BUFFER_SIZE, // Input buffer size BUFFER_SIZE, // Output buffer size pipeSa, // Pipe security attributes HandleInheritability.None // Not inheritable );
File mapping is a mechanism for one-way or bi-directional inter-process communication among two or more processes in the local machine. To share a file or memory, all of the processes must use the name or the handle of the same file mapping object.
To share a file, the first process creates or opens a file by using the CreateFile function. Next, it creates a file mapping object by using the CreateFileMapping function, specifying the file handle and a name for the file mapping object. The names of events, semaphores, mutexes, waitable timers, jobs, and file mapping objects share the same namespace. Therefore, the CreateFileMapping and OpenFileMapping functions fail if they specify a name that is in use by an object of another type.
To share memory that is not associated with a file, a process must use the CreateFileMapping function and specify INVALID_HANDLE_VALUE as the hFile parameter instead of an existing file handle. The corresponding file mapping object accesses memory backed by the system paging file. You must specify a size greater than zero when you specify an hFile of INVALID_HANDLE_VALUE in a call to CreateFileMapping.
Processes that share files or memory must create file views by using the MapViewOfFile or MapViewOfFileEx functions. They must coordinate their access using semaphores, mutexes, events, or some other mutual exclusion techniques.
This example demonstrates a named shared memory server, Local\HelloWorld, that creates the file mapping object with INVALID_HANDLE_VALUE. By using the PAGE_READWRITE flag, the process has read/write permission to the memory through any file view that is created.
The named shared memory client, Local\HelloWorld, can access the string written to the shared memory by the first process. The console displays the message "Message from the first process" that is read from the file mapping created by the first process.
- Create a file mapping. (CreateFileMapping)
- Map the view of the file mapping into the address space of the current process. (MapViewOfFile)
- Write message to the file view. (CopyMemory)
- Unmap the file view and close the file mapping objects. (UnmapViewOfFile, CloseHandle)
- Try to open a named file mapping. (OpenFileMapping)
- Maps the view of the file mapping into the address space of the current process. (MapViewOfFile)
- Read message from the view of the shared memory.
- Unmap the file view and close the file mapping objects. (UnmapViewOfFile, CloseHandle)
// In terminal services: The name can have a "Global\" or "Local\" prefix // to explicitly create the object in the global or session namespace. // The remainder of the name can contain any character except the // backslash character (\). For details, please refer to: // http://msdn.microsoft.com/en-us/library/aa366537.aspx TCHAR szMapFileName = _T("Local\\HelloWorld"); // Create the file mapping object HANDLE hMapFile = CreateFileMapping( INVALID_HANDLE_VALUE, // Use paging file instead of existing file. // Pass file handle to share in a file. NULL, // Default security PAGE_READWRITE, // Read/write access , // Max. object size BUFFER_SIZE, // Buffer size szMapFileName // Name of mapping object );
.NET only supports P/Invoke native APIs currently. By P/Invoke, .NET can simulate similar behaviors as native code.
Sample Code 4 (C# - P/Invoke)
/// <</span>summary> /// Creates or opens a named or unnamed file mapping object for /// a specified file. /// <</span>/summary> /// <</span>param name="hFile">A handle to the file from which to create /// a file mapping object.<</span>/param> /// <</span>param name="lpAttributes">A pointer to a SECURITY_ATTRIBUTES /// structure that determines whether a returned handle can be /// inherited by child processes.<</span>/param> /// <</span>param name="flProtect">Specifies the page protection of the /// file mapping object. All mapped views of the object must be /// compatible with this protection.<</span>/param> /// <</span>param name="dwMaximumSizeHigh">The high-order DWORD of the /// maximum size of the file mapping object.<</span>/param> /// <</span>param name="dwMaximumSizeLow">The low-order DWORD of the /// maximum size of the file mapping object.<</span>/param> /// <</span>param name="lpName">The name of the file mapping object. /// <</span>/param> /// <</span>returns>If the function succeeds, the return value is a /// handle to the newly created file mapping object.<</span>/returns> [DllImport("Kernel32.dll", SetLastError = true)] public static extern IntPtr CreateFileMapping( IntPtr hFile, // Handle to the file IntPtr lpAttributes, // Security Attributes FileProtection flProtect, // File protection uint dwMaximumSizeHigh, // High-order DWORD of size uint dwMaximumSizeLow, // Low-order DWORD of size string lpName // File mapping object name );
Mailslot is a mechanism for one-way inter-process communication in the local machine or across computers in the intranet. Any client can store messages in a mailslot. The creator of the slot, i.e., the server, retrieves the messages that are stored there:
Client (GENERIC_WRITE) ---> Server (GENERIC_READ)
This sample demonstrates a mailslot server, \\.\mailslot\HelloWorld. It first creates such a mailslot, then it reads the new messages in the slot every five seconds. Then, a mailslot client connects and writes to the mailslot \\.\mailslot\HelloWorld.
- Create a mailslot. (CreateMailslot)
- Check messages in the mailslot. (ReadMailslot)
- Check for the number of messages in the mailslot. (GetMailslotInfo)
- Retrieve the messages one by one from the mailslot. While reading, update the number of messages that are left in the mailslot. (ReadFile, GetMailslotInfo)
- Close the handle of the mailslot instance. (CloseHandle)
- Open the mailslot. (CreateFile)
- Write messages to the mailslot. (WriteMailslot, WriteFile)
- Close the slot. (CloseHandle)
///////////////////////////////////////////////////////////////////////// // Check for the number of messages in the mailslot. //bResult = GetMailslotInfo( hMailslot, // Handle of the mailslot NULL, // No maximum message size &cbMessageBytes, // Size of next message &cMessages, // Number of messages NULL); // No read time-out
Code - CreateMailslot (C# - P/Invoke)
/// <</span>summary> /// Creates an instance of a mailslot and returns a handle for subsequent /// operations. /// <</span>/summary> /// <</span>param name="lpName">mailslot name<</span>/param> /// <</span>param name="nMaxMessageSize">The maximum size of a single message /// <</span>/param> /// <</span>param name="lReadTimeout">The time a read operation can wait for a /// message<</span>/param> /// <</span>param name="lpSecurityAttributes">Security attributes<</span>/param> /// <</span>returns>If the function succeeds, the return value is a handle to /// the server end of a mailslot instance.<</span>/returns> [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr CreateMailslot( string lpName, // Mailslot name uint nMaxMessageSize, // Max size of a single message in bytes int lReadTimeout, // Timeout of a read operation IntPtr lpSecurityAttributes // Security attributes );
.NET Remoting is a mechanism for one-way inter-process communication and RPC between .NET applications in the local machine or across computers in the intranet and internet.
.NET Remoting allows an application to make a remotable object available across remoting boundaries, which includes different appdomains, processes, or even different computers connected by a network. .NET Remoting makes a reference of a remotable object available to a client application, which then instantiates and uses a remotable object as if it were a local object. However, the actual code execution happens at the server-side. All requests to the remotable object are proxied by the .NET Remoting runtime over Channel objects that encapsulate the actual transport mode, including TCP streams, HTTP streams, and named pipes. As a result, by instantiating proper Channel objects, a .NET Remoting application can be made to support different communication protocols without recompiling the application. The runtime itself manages the act of serialization and marshalling of objects across the client and server appdomains.
Code - Create and Register a Channel (C#)
///////////////////////////////////////////////////////////////////// // Create and register a channel (TCP channel in this example) that // is used to transport messages across the remoting boundary. //// Properties of the channel IDictionary props = new Hashtable(); props["port"] = 6100; // Port of the TCP channel props["typeFilterLevel"] = TypeFilterLevel.Full; // Formatters of the messages for delivery BinaryClientFormatterSinkProvider clientProvider = null; BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider(); serverProvider.TypeFilterLevel = TypeFilterLevel.Full; // Create a TCP channel TcpChannel tcpChannel = new TcpChannel(props, clientProvider, serverProvider); // Register the TCP channel ChannelServices.RegisterChannel(tcpChannel, true);
Code - Register Remotable Types (VB.NET)
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Register the remotable types on the service end as ' server-activated types (aka well-known types) or client-activated ' types. ' Register RemotingShared.SingleCallObject as a SingleCall server- ' activated type. RemotingConfiguration.RegisterWellKnownServiceType(GetType(RemotingShared.SingleCallObject), _ "SingleCallService", WellKnownObjectMode.SingleCall) ' Register RemotingShared.SingletonObject as a Singleton server- ' activated type. RemotingConfiguration.RegisterWellKnownServiceType(GetType(RemotingShared.SingletonObject), _ "SingletonService", WellKnownObjectMode.Singleton) ' Register RemotingShared.ClientActivatedObject as a client- ' activated type. RemotingConfiguration.ApplicationName = "RemotingService" RemotingConfiguration.RegisterActivatedServiceType(_ GetType(Global.RemotingShared.ClientActivatedObject))
Points of Interest
In the pilot phase of the AIO project, we focus on five techniques: COM, Library, IPC, Office, and Data Access. There has been 42 code examples in the project. The collection currently grows at a rate of seven examples per week.
This article was created on 3/12/2009.
About the Author
Microsoft All-In-One Code Framework delineates the framework and skeleton of Microsoft development techniques through typical sample codes in three popular programming languages (Visual C#, VB.NET, Visual C++). Each sample is elaborately selected, composed, and documented to demonstrate one frequently-asked, tested or used coding scenario based on our support experience in MSDN newsgroups and forums. If you are a software developer, you can fill the skeleton with blood, muscle and soul. If you are a software tester or a support engineer like us, you may extend the sample codes a little to fit your specific test scenario or refer your customer to this project if the customer's question coincides with what we collected.
This article was authored by All-In-One Code Framework and reproduced for the benefit of our viewers under the terms of the Ms-PL license.