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:
- 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.
Samples Structure and Relationship
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:
- 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
)
Client-side logic:
- 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
)
Code - CreateNamedPipe (C++)
HANDLE hPipe = CreateNamedPipe(
strPipeName, PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES,
BUFFER_SIZE, BUFFER_SIZE, NMPWAIT_USE_DEFAULT_WAIT, &sa )
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);
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.
Code - Create Named Pipe (C#)
PipeSecurity pipeSa = new PipeSecurity();
pipeSa.SetAccessRule(new PipeAccessRule("Everyone",
PipeAccessRights.ReadWrite, AccessControlType.Allow));
pipeServer = new NamedPipeServerStream(
strPipeName,
PipeDirection.InOut,
NamedPipeServerStream.MaxAllowedServerInstances,
PipeTransmissionMode.Message,
PipeOptions.None,
BUFFER_SIZE,
BUFFER_SIZE,
pipeSa,
HandleInheritability.None
);
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:
- 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
)
Client-side logic:
- 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
)
Code - CreateFileMapping (C++)
TCHAR szMapFileName[] = _T("Local\\HelloWorld");
HANDLE hMapFile = CreateFileMapping(
INVALID_HANDLE_VALUE,
NULL, PAGE_READWRITE, 0, BUFFER_SIZE, szMapFileName );
.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)
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern IntPtr CreateFileMapping(
IntPtr hFile,
IntPtr lpAttributes,
FileProtection flProtect,
uint dwMaximumSizeHigh,
uint dwMaximumSizeLow,
string lpName
);
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:
- 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
)
Client-side logic:
- Open the mailslot. (
CreateFile
) - Write messages to the mailslot. (
WriteMailslot
, WriteFile
) - Close the slot. (
CloseHandle
)
Code - GetMailslotInfo (C++)
bResult = GetMailslotInfo(
hMailslot, NULL, &cbMessageBytes, &cMessages, NULL);
Code - CreateMailslot (C# - P/Invoke)
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr CreateMailslot(
string lpName,
uint nMaxMessageSize,
int lReadTimeout,
IntPtr lpSecurityAttributes
);
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#)
IDictionary props = new Hashtable();
props["port"] = 6100;
props["typeFilterLevel"] = TypeFilterLevel.Full;
BinaryClientFormatterSinkProvider clientProvider = null;
BinaryServerFormatterSinkProvider serverProvider =
new BinaryServerFormatterSinkProvider();
serverProvider.TypeFilterLevel = TypeFilterLevel.Full;
TcpChannel tcpChannel = new TcpChannel(props, clientProvider, serverProvider);
ChannelServices.RegisterChannel(tcpChannel, true);
Code - Register Remotable Types (VB.NET)
RemotingConfiguration.RegisterWellKnownServiceType(GetType(RemotingShared.SingleCallObject), _
"SingleCallService", WellKnownObjectMode.SingleCall)
RemotingConfiguration.RegisterWellKnownServiceType(GetType(RemotingShared.SingletonObject), _
"SingletonService", WellKnownObjectMode.Singleton)
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.