Interprocess Communications Using Shared Memory
Thursday, January 21, 2010 at 9:16PM In this article I cover Interprocess Communication through the use of shared memory. Shared Memory facility in Windows is implemented using the page file and I have seen it used for integrating with legacy applications. A typical case is a legacy application that is at the end of its lifecycle and is in the process to be replaced or a real-time application that consumes or produces data.
The following figure shows two processes, Process 1 write to shared memory and Process 2 read from shared memory. The data written to shared memory can be of any specified format (e.g. serialized objects, specific protocol or known format understood by both applications).

The following figure illustrates two Processes communicating using two shared memory objects, one for writing to and one for reading from.

Now let me go over the most important code snippets of the c# shared memory implementation. First thing I did was to PInvoke the Win32 API functions needed for allocating shared memory. Most of the PInvoke implementation came from PInvoke.net, a great resource for invoke related tasks.
1: [StructLayout(LayoutKind.Sequential)]
2: public struct MEMORY_BASIC_INFORMATION
3: {
4: public UIntPtr BaseAddress;
5: public UIntPtr AllocationBase;
6: public uint AllocationProtect;
7: public uint RegionSize;
8: public uint State;
9: public uint Protect;
10: public uint Type;
11: }
12:
13: [DllImport("kernel32.dll")]
14: internal static extern int VirtualQuery(ref IntPtr lpAddress,
15: ref MEMORY_BASIC_INFORMATION lpBuffer,
16: int dwLength
17: );
18:
19:
20: [DllImport ("kernel32.dll", SetLastError = true)]
21: internal static extern IntPtr CreateFileMapping (IntPtr hFile,
22: int lpAttributes,
23: FileMapProtection flProtect,
24: uint dwMaximumSizeHigh,
25: uint dwMaximumSizeLow,
26: string lpName);
27:
28:
29: [DllImport("kernel32.dll", SetLastError = true)]
30: internal static extern IntPtr OpenFileMapping(uint dwDesiredAccess, bool bInheritHandle, string lpName);
31:
32: [Flags]
33: internal enum FileMapProtection : uint
34: {
35:
36: PageReadonly = 0x02,
37: PageReadWrite = 0x04,
38: PageWriteCopy = 0x08,
39: PageExecuteRead = 0x20,
40: PageExecuteReadWrite = 0x40,
41: SectionImage = 0x1000000,
42: SectionNoCache = 0x10000000,
43: SectionReserve = 0x4000000,
44: }
45:
46:
47: [DllImport("kernel32.dll", SetLastError = true)]
48: [return: MarshalAs(UnmanagedType.Bool)]
49: internal static extern bool CloseHandle(IntPtr hObject);
50:
51:
52: [DllImport("kernel32.dll", SetLastError = true)]
53: internal static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);
54:
55:
56: [DllImport("kernel32.dll", SetLastError = true)]
57: internal static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject,
58: FileMapAccess dwDesiredAccess,
59: uint dwFileOffsetHigh,
60: uint dwFileOffsetLow,
61: uint dwNumberOfBytesToMap
62: );
The next snippet of code creates or open a File Mapping of size Size and of name SharedMemoryName. Then a shared memory handle is created.
1:
2: private void CreateOrOpen()
3: {
4: IntPtr hHandle = CreateFileMapping(new IntPtr(-1),
5: 0,
6: FileMapProtection.PageReadWrite,
7: 0,
8: Size,
9: SharedMemoryName);
10: _sharedMemoryHandle = new SharedMemoryHandle(hHandle);
11: if (_sharedMemoryHandle.IsInvalid)
12: {
13: throw new InvalidOperationException("Failed to create section object");
14: }
15: }
The following code creates a handle to an operating system Event synchronization object. This synchronization object is going to be used to synchronize access to the shared memory. This synchronization method will support one producer and multiple consumers.
1: internal Stream ShareMemoryStream
2: {
3: get
4: {
5: return _stream;
6: }
7: }
8:
9: public SharedMemory(string sharedMemoryName, uint numberOfBytes)
10: {
11: _eventWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset, sharedMemoryName + "Event");
12: Size = numberOfBytes;
13: SharedMemoryName = sharedMemoryName.Trim();
14: CreateOrOpen();
15: MapView();
16: }
17:
18: private void MapView()
19: {
20: IntPtr ptr = MapViewOfFile(_sharedMemoryHandle.GetHandle(),
21: FileMapAccess.FileMapRead | FileMapAccess.FileMapWrite,
22: (uint)0,
23: 0,
24: Size);
25: if (ptr == IntPtr.Zero)
26: {
27: throw new InvalidOperationException("Invalid Handle. Filed to map view");
28: }
29: if (Size == 0)
30: {
31: MEMORY_BASIC_INFORMATION info = new MEMORY_BASIC_INFORMATION();
32:
33: VirtualQuery(ref ptr, ref info, (int)System.Runtime.InteropServices.Marshal.SizeOf(info));
34: Size = info.RegionSize;
35: }
36:
37: _stream = new SharedMemoryStream(ptr, Size);
38: }
39:
40: public void Write(byte[] buffer)
41: {
42: try
43: {
44: ShareMemoryStream.Position = 0;
45: ShareMemoryStream.Write(buffer, 0, buffer.Length);
46: if (!_eventWaitHandle.SafeWaitHandle.IsClosed)
47: {
48: _eventWaitHandle.Set();
49: }
50: }
51: catch (Exception ex)
52: {
53: System.Diagnostics.Trace.WriteLine(ex.Message);
54: }
55: }
Now the following code snippet is interesting. On line 2 I declare a private event of type ReceivedEventHandler. When data is written to the shared memory this event calls the registered handler with a byte array containing the data. The handler responsibility is to desirialize the data per protocol format agreed upon by the participating processes. On line 5 is where the event property is implemented and on line 7 the handler is subscribed using += operator. On lines 10 through 14 a thread is created using the thread pool. The thread function for this thread is defined on line 27, this function waits for an operating system Event object to be signaled, once the Event object is signaled it proceeds to read the contents stored in shared memory. It then resets the handle to the Event object and continues to line 33 to do it over again. The thread exits when the event handle is closed.
1:
2: private event ReceivedEventHandler<byte[]> _receiveHandler;
3:
4:
5: public event ReceivedEventHandler<byte[]> ReceiveHandler
6: {
7: add
8: {
9: _receiveHandler += value;
10: if (!_isThreadCreated)
11: {
12: _isThreadCreated = true;
13: ThreadPool.QueueUserWorkItem(ReceiverCallback);
14: }
15: }
16: remove
17: {
18: _receiveHandler -= value;
19: if (_receiveHandler == null)
20: {
21: _isThreadCreated = false;
22: }
23: }
24: }
25:
26:
27: private void ReceiverCallback(object obj)
28: {
29: while (_isThreadCreated)
30: {
31: try
32: {
33: _eventWaitHandle.WaitOne();
34:
35: if (_receiveHandler != null)
36: {
37: byte[] buffer = new byte[Size];
38: ShareMemoryStream.Position = 0;
39: ShareMemoryStream.Read(buffer, 0, buffer.Length);
40: _receiveHandler(buffer);
41: }
42: }
43: catch (Exception ex)
44: {
45: System.Diagnostics.Trace.WriteLine(ex.Message);
46: }
47: if (!_eventWaitHandle.SafeWaitHandle.IsClosed)
48: {
49: _eventWaitHandle.Reset();
50: }
51: }
52: }
The dispose method will sets the _receiveHandler to null, set the event object, close the event object handle, close the stream object and close the shared memory handle .
1: private void Dispose(bool disposing)
2: {
3: if (!_disposed)
4: {
5: if (disposing)
6: {
7: _receiveHandler = null;
8: _isThreadCreated = false;
9: if (!_eventWaitHandle.SafeWaitHandle.IsClosed)
10: {
11: _eventWaitHandle.Set();
12: }
13: System.Threading.Thread.Sleep(100);
14: _eventWaitHandle.Close();
15: _stream.Close();
16: _sharedMemoryHandle.Close();
17: }
18: }
19: _disposed = true;
20: }
21:
22: private bool _disposed = false;
23:
24: public void Dispose()
25: {
26: Dispose(true);
27: GC.SuppressFinalize(this);
28: }
29:
30:
31: ~SharedMemory()
32: {
33: Dispose(false);
34: }
The following figure show the output of the test applications.

Hope this helps. The code can be downloaded here.
.Net,
c#,
shared memory in
interprocess communication,
ipc,
messaging 
Reader Comments (1)
Thank for your shared