Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Inter-Process Communication (IPC) Introduction and Sample Code

4.91/5 (57 votes)
19 Dec 2009Ms-PL8 min read 254.5K   12.3K  
This article will cover general IPC technologies in All-In-One Code Framework. The IPC technologies include Named Pipes, File Mapping, MailSlot, etc.

Introduction

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

Background

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:

  1. Download the zip file and unzip it.
  2. Open the folder [Visual Studio 2008].
  3. Open the solution file IPC.sln. You must pre-install Visual Studio 2008 on the machine.
  4. In the Solution Explorer, open the [Process] \ [IPC and RPC] folder.

Samples Structure and Relationship

Structure_of_AIO_2008.jpg

Named Pipe

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:

PIPE_ACCESS_INBOUND:

Client (GENERIC_WRITE) ---> Server (GENERIC_READ)


PIPE_ACCESS_OUTBOUND:

Client (GENERIC_READ) <--- Server (GENERIC_WRITE)


PIPE_ACCESS_DUPLEX:

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.

Code Logic

Server-side logic:

  1. Create a named pipe. (CreateNamedPipe)
  2. Wait for the client to connect. (ConnectNamedPipe)
  3. Read client requests from the pipe and write the response. (ReadFile, WriteFile)
  4. Disconnect the pipe, and close the handle. (DisconnectNamedPipe, CloseHandle)

Client-side logic:

  1. Try to open a named pipe. (CreateFile)
  2. Set the read mode and the blocking mode of the specified named pipe. (SetNamedPipeHandleState)
  3. Send a message to the pipe server and receive its response. (WriteFile, ReadFile)
  4. Close the pipe. (CloseHandle)

Code - CreateNamedPipe (C++)

C++
// 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++)

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:

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

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

Code - Create Named Pipe (C#)

C#
// 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

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.

Code Logic

Service-side logic:

  1. Create a file mapping. (CreateFileMapping)
  2. Map the view of the file mapping into the address space of the current process. (MapViewOfFile)
  3. Write message to the file view. (CopyMemory)
  4. Unmap the file view and close the file mapping objects. (UnmapViewOfFile, CloseHandle)

Client-side logic:

  1. Try to open a named file mapping. (OpenFileMapping)
  2. Maps the view of the file mapping into the address space of the current process. (MapViewOfFile)
  3. Read message from the view of the shared memory.
  4. Unmap the file view and close the file mapping objects. (UnmapViewOfFile, CloseHandle)

Code - CreateFileMapping (C++)

C++
// 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 
       0,                         // 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)

C#
/// <summary> 
/// Creates or opens a named or unnamed file mapping object for 
/// a specified file. 
/// </summary> 
/// <param name="hFile">A handle to the file from which to create 
/// a file mapping object.</param> 
/// <param name="lpAttributes">A pointer to a SECURITY_ATTRIBUTES 
/// structure that determines whether a returned handle can be 
/// inherited by child processes.</param> 
/// <param name="flProtect">Specifies the page protection of the 
/// file mapping object. All mapped views of the object must be 
/// compatible with this protection.</param> 
/// <param name="dwMaximumSizeHigh">The high-order DWORD of the 
/// maximum size of the file mapping object.</param> 
/// <param name="dwMaximumSizeLow">The low-order DWORD of the 
/// maximum size of the file mapping object.</param> 
/// <param name="lpName">The name of the file mapping object. 
/// </param> 
/// <returns>If the function succeeds, the return value is a 
/// handle to the newly created file mapping object.</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

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.

Code Logic

Server-side logic:

  1. Create a mailslot. (CreateMailslot)
  2. Check messages in the mailslot. (ReadMailslot)
    1. Check for the number of messages in the mailslot. (GetMailslotInfo)
    2. Retrieve the messages one by one from the mailslot. While reading, update the number of messages that are left in the mailslot. (ReadFile, GetMailslotInfo)
  3. Close the handle of the mailslot instance. (CloseHandle)

Client-side logic:

  1. Open the mailslot. (CreateFile)
  2. Write messages to the mailslot. (WriteMailslot, WriteFile)
  3. Close the slot. (CloseHandle)

Code - GetMailslotInfo (C++)

C++
///////////////////////////////////////////////////////////////////////// 
// 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)

C#
/// <summary> 
/// Creates an instance of a mailslot and returns a handle for subsequent 
/// operations. 
/// </summary> 
/// <param name="lpName">mailslot name</param> 
/// <param name="nMaxMessageSize">The maximum size of a single message 
/// </param> 
/// <param name="lReadTimeout">The time a read operation can wait for a 
/// message</param> 
/// <param name="lpSecurityAttributes">Security attributes</param> 
/// <returns>If the function succeeds, the return value is a handle to 
/// the server end of a mailslot instance.</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 
);

Remoting

.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#)

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)

VB
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' 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.

History

This article was created on 3/12/2009.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)