Interprocess Communication: Part 5

January 8, 2015

Blog | Development | Interprocess Communication: Part 5
Interprocess Communication: Part 5

Welcome to the final part in the IPC series! Today, we’ll be taking a look at Shared Memory using Memory Mapped Files.

Memory within a single process is normally not shared directly with other processes. In fact, attempting to access a memory address not allocated to the process will raise a critical fault by the operating system. However, the .Net framework provides a mechanism to allow multiple processes to be able to safely read and write a piece of data into a shareable memory location using the MemoryMappedFile class. While the name may suggest that a backing file is used, the MSDN description reads as follows:

A memory-mapped file contains the contents of a file in virtual memory. This mapping between a file and memory space enables an application, including multiple processes, to modify the file by reading and writing directly to the memory.

When dealing with memory directly, any data shared must be deconstructed into its most basic component prior to being written, then reconstructed when read. In this case, a data structure would be converted into its appropriate bytes, written to the shared memory location, and read back to be re-converted into the data structure by the reader.

Because this teardown & buildup process is a fundamental requirement to leverage the shared memory feature of the .Net framework, another class exists which helps abstract this conversion from the developer. The MemoryMappedViewAccessor class provides support for reading and writing a given Structure into a MemoryMappedFile without worrying about byte buffers or array lengths.

Another useful feature allows a single MemoryMappedFile to be defined with a certain size, while several View Accessors access distinct areas of the mapped memory using different MemoryMappedViewAccessor instances, to minimize potential overlap.

Memory Mapped File 

Let’s look at an implementation example using a single MemoryMappedFile with a single MemoryMappedViewAccessor, transmitting a numeric value as well as the memory location where the next piece of data will be written. This shows how even a single ViewAccessor can be used to write to different areas of the memory location. 

public struct SharedMemoryData
{
    public long Value;
    public long NextLocation;
}

...

using (var sharedMemory = MemoryMappedFile.OpenExisting(ShareName))
{
    using (var accessor = sharedMemory.CreateViewAccessor(0, ShareSize))
    {
        var location = 0L;
        var doLoop = true;
        do
        {
            Console.WriteLine("Press any key to read the location.");
            Console.ReadKey();

            SharedMemoryData data;
            accessor.Read(location, out data);
            Console.WriteLine("From location: {0}, Value: {1}, Next location: {2}", location, data.Value, data.NextLocation);
            if (data.Value != int.MaxValue)
                location = data.NextLocation;
             else
                doLoop = false;
        } while (doLoop);
    }
}

...

using (var sharedMemory = MemoryMappedFile.CreateNew(ShareName, ShareSize))
{
    using (var accessor = sharedMemory.CreateViewAccessor(0, ShareSize))
    {
        var currentLocation = 0L;
        var doLoop = true;
        do
        {
            Console.Write("Value to write (-1 to quit): ");
            var valueString = Console.ReadLine();
            var value = long.Parse(valueString);

            Console.Write("Next location to write to (-1 to exit): ");
            var nextLocationString = Console.ReadLine();
            var nextLocation = long.Parse(nextLocationString);

            SharedMemoryData data;
            if (value == -1 || nextLocation == -1)
            {
                // Write out the exit flag of int.MaxValue
                data = new SharedMemoryData()
                       {
                           Value = int.MaxValue,
                           NextLocation = 0,
                       };
                doLoop = false;
            }
            else
            {
                // Write out the value specified.
                data = new SharedMemoryData()
                       {
                           Value = value,
                           NextLocation = nextLocation,
                       };
            }

            accessor.Write(currentLocation, ref data);
            currentLocation = data.NextLocation;

        } while (doLoop);
    }
}

It’s worth noting that, in a real multi-process situation, a MemoryMappedFile will likely be insufficient to meet your interprocess communication needs. At best, the memory location can serve as a way to transmit data between processes, but will fall short when attemptting to communicate with the opposing process(es) where data has, in fact, been written and is ready to be read! To achieve this, one of the other IPC mechanisms described in Part1, Part2, Part3, or Part4 may fill this gap.

This brings us to a close for the IPC Series. The full source code for the above example, as well as all previous examples within this series can be found here.

Phil Azzi, Developer, GeekHive

Phil Azzi

Technical Lead
Tags
  • Patterns & Practices
  • Tutorial

Recent Work

Check out what else we've been working on