Inter-Process Communication in C# Using Shared Memory
MemoryMappedFile in .NET
The MemoryMappedFile class serves as the foundation for shared memory operations in .NET. Creating a shared memory segment requires specifying a unique name and size allocation.
// Initialize shared memory segment
MemoryMappedFile mappedFile = MemoryMappedFile.CreateNew(memoryName, capacity);
Writing data involves creating a view accessor and writing byte arrays at specific offsets:
using (var viewAccessor = mappedFile.CreateViewAccessor(0, totalCapacity))
{
viewAccessor.WriteArray(byteOffset, sourceData, 0, sourceData.Length);
}
Reading follows a similar pattern:
using (var viewAccessor = mappedFile.CreateViewAccessor(0, totalCapacity))
{
viewAccessor.ReadArray(byteOffset, destinationBuffer, 0, destinationBuffer.Length);
}
Encapsulated Shared Memory Implementation
SharedMemory Wrapper Class
public class SharedMemorySegment : IDisposable
{
public string Identifier { get; private set; }
public long Capacity { get; private set; }
public static SharedMemorySegment Initialize(
string identifier,
ISharedMemoryMappingContract mappingContract);
public void Terminate();
public void Dispose();
}
Mapping Contract Interface
Classes implementing this interface can map their properties direct to shared memory regions. Supported property types include primitives, structs, and byte arrays.
public interface ISharedMemoryMappingContract
{
event Action<object propertychangeeventdata=""> OnPropertyUpdated;
event Func<string, object> OnPropertyRequested;
}
public class PropertyChangeEventData
{
public object NewValue { get; set; }
public string PropertyIdentifier { get; set; }
}
public abstract class SharedMemoryMappingBase : ISharedMemoryMappingContract
{
protected void SignalPropertyChange(object newValue,
[System.Runtime.CompilerServices.CallerMemberName] string propertyName = "");
protected object RetrievePropertyValue(object defaultValue,
[System.Runtime.CompilerServices.CallerMemberName] string propertyName = "");
public event Action<object, PropertyChangeEventData> OnPropertyUpdated;
public event Func<string, object> OnPropertyRequested;
}</object>
Implementation Guide
Defining the Data Contract
Inherit from SharedMemoryMappingBase and define properties with the required getter/setter pattern:
public class PlaybackStateData : SharedMemoryMappingBase
{
public double Timestamp
{
set { SignalPropertyChange(value); }
get { return (double)RetrievePropertyValue(0.0); }
}
public double TotalDuration
{
set { SignalPropertyChange(value); }
get { return (double)RetrievePropertyValue(0.0); }
}
public int PausedState
{
set { SignalPropertyChange(value); }
get { return (int)RetrievePropertyValue(0); }
}
public int MuteState
{
set { SignalPropertyChange(value); }
get { return (int)RetrievePropertyValue(0); }
}
public int HeartbeatSignal
{
set { SignalPropertyChange(value); }
get { return (int)RetrievePropertyValue(0); }
}
public int ControlFlags
{
set { SignalPropertyChange(value); }
get { return (int)RetrievePropertyValue(0); }
}
public int DataLength
{
set { SignalPropertyChange(value); }
get { return (int)RetrievePropertyValue(0); }
}
[MemoryRegion(BufferSize = 1024)]
public byte[] InputBuffer
{
set { SignalPropertyChange(value); }
get { return (byte[])RetrievePropertyValue(null); }
}
[MemoryRegion(BufferSize = 1024)]
public byte[] OutputBuffer
{
set { SignalPropertyChange(value); }
get { return (byte[])RetrievePropertyValue(null); }
}
}
Important constraints:
- Property types must be value types (primitives or structs). Reference types are limited to byte arrays with explicit size declaration.
- Property declaration order determines memory layout order.
- Current implementation uses 1-byte alignment; C++ counterparts require
#pragma pack(1).
Equivalent C++ structure definition:
#pragma pack(1)
typedef struct {
double Timestamp;
double TotalDuration;
int PausedState;
int MuteState;
int HeartbeatSignal;
int ControlFlags;
int DataLength;
char InputBuffer[1024];
char OutputBuffer[1024];
} PlaybackStateData;
Creating the Shared Memory Segment
var playbackData = new PlaybackStateData();
var memorySegment = SharedMemorySegment.Initialize("GlobalPlaybackState", playbackData);
Cleanup
memorySegment.Dispose();
Practical Example
The following example demonstrates communication between a C# parent process and a C++ child process using a mutex for synchronization.
C# Parent Process
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
namespace SharedMemoryDemo
{
public class ProcessControllerData : SharedMemoryMappingBase
{
public double Timestamp
{
set { SignalPropertyChange(value); }
get { return (double)RetrievePropertyValue(0.0); }
}
public double TotalDuration
{
set { SignalPropertyChange(value); }
get { return (double)RetrievePropertyValue(0.0); }
}
public int PausedState
{
set { SignalPropertyChange(value); }
get { return (int)RetrievePropertyValue(0); }
}
public int MuteState
{
set { SignalPropertyChange(value); }
get { return (int)RetrievePropertyValue(0); }
}
public int HeartbeatSignal
{
set { SignalPropertyChange(value); }
get { return (int)RetrievePropertyValue(0); }
}
public int ControlFlags
{
set { SignalPropertyChange(value); }
get { return (int)RetrievePropertyValue(0); }
}
public int DataLength
{
set { SignalPropertyChange(value); }
get { return (int)RetrievePropertyValue(0); }
}
[MemoryRegion(BufferSize = 1024)]
public byte[] MessageBuffer
{
set { SignalPropertyChange(value); }
get { return (byte[])RetrievePropertyValue(null); }
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("[Parent] Process started");
var controllerData = new ProcessControllerData();
var memorySegment = SharedMemorySegment.Initialize("IpcDemoChannel", controllerData);
using var syncMutex = new Mutex(false, "IpcDemoMutex");
Console.WriteLine("[Parent] Sending: hello world!");
syncMutex.WaitOne();
controllerData.MessageBuffer = Encoding.ASCII.GetBytes("hello world!");
syncMutex.ReleaseMutex();
var childProcess = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "ChildProcess.exe",
Arguments = "IpcDemoMutex"
}
};
childProcess.Start();
Thread.Sleep(2000);
Console.WriteLine("[Parent] Sending: terminate");
syncMutex.WaitOne();
controllerData.MessageBuffer = Encoding.ASCII.GetBytes("terminate\0");
syncMutex.ReleaseMutex();
childProcess.WaitForExit();
memorySegment.Dispose();
Console.WriteLine("[Parent] Process terminated");
}
}
}
C++ Child Process
#include <iostream>
#include <Windows.h>
#pragma pack(1)
typedef struct _ControllerData {
double Timestamp;
double TotalDuration;
int PausedState;
int MuteState;
int HeartbeatSignal;
int ControlFlags;
int DataLength;
char MessageBuffer[1024];
} ControllerData;
int main(int argc, char** argv)
{
if (argc < 2)
return -1;
printf("[Child] Process started\n");
HANDLE hMutex = CreateMutexA(NULL, FALSE, argv[1]);
HANDLE hMapping = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, "IpcDemoChannel");
if (hMapping == NULL)
{
MessageBoxA(NULL, "Failed to open shared memory", "Error", MB_OK);
return -1;
}
ControllerData* pData = (ControllerData*)MapViewOfFile(
hMapping, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(ControllerData));
WaitForSingleObject(hMutex, INFINITE);
printf("[Child] Received: %s\n", pData->MessageBuffer);
ReleaseMutex(hMutex);
while (true)
{
Sleep(30);
WaitForSingleObject(hMutex, INFINITE);
if (strcmp(pData->MessageBuffer, "terminate") == 0)
{
printf("[Child] Received: %s\n", pData->MessageBuffer);
ReleaseMutex(hMutex);
break;
}
ReleaseMutex(hMutex);
}
UnmapViewOfFile(pData);
CloseHandle(hMapping);
CloseHandle(hMutex);
printf("[Child] Process terminated\n");
return 0;
}