Inter-Process Communication using MemoryMappingFile

In an operating system, a Memory Mapping file are virtual storage place which has direct byte to byte correlation between the Virtual Address Space and the corresponding physical storage. So when we access the Virtual Address space via a memory mapping file we are directly communicating with the kernel space where the file is actually loaded. The portion of calculation between the physical storage and logical storage is hence avoided.

Memory Mapping files allows application to access files in the same way as memory. Generally as address between the physical storage and virtual memory address space, we cannot access the physical address directly. But using Memory Mapping Files, the process loads a specific range of address within the process address space with which the storage of memory into the file can be done by just assigning value to a dereferenced pointers. The IO operation on a MemoryMapping file is so fast that from the programmers point of view it seems to be like accessing the memory rather than actual physical storage. To increase performance memory mapping files are not actually stored to the disk file as well, but rather it will be stored automatically in background when FlushViewOfFile is executed or paging file is written. To Read more about memory mapping files, read here.

Memory Mapping file inside .NET Framework

.NET introduces a separate namespace to handle Memory Mapping files. Previously, we needed to do this using unmanaged Api's but with the introduction of managed API into the .NET framework library, it becomes very easy to handle MemoryMapping file directly from .NET library.

As memory mapping files are loaded into memory on a separate range of address space, two process can easily share the same page file instance and thus interprocess communication can be made with fast access to memory. It is recommended to back data with an actual disk file when large data is loaded into memory, so that there is no memory leak on the system when there is large memory pressure.



MemoryMapping with backup Paging file (For large data communication)

We generally use backed up memory file in the hard disk when the memory that needs to be shared between the process goes out of physical memory available on the pc or when you think that the data that needs to be loaded can be very expensive and sensitive to remain only in memory.

const string FILENAME = "LargeFile.dat";
private MemoryMappedFile GetMemoryMapFile(FileMode mode)
{
   string path = Path.Combine(defaultPath, FILENAME);
   var mmf = MemoryMappedFile.CreateFromFile(path, mode, "INTERPROC", this.length, MemoryMappedFileAccess.ReadWriteExecute);
  return mmf;
}

The code above gets the reference of MemoryMappedFile using a disk backup paging file. The CreateFromFile static method of MemoryMappedFile exposes an option to specify path of the actual file location where the memory mapping file content needs to be mapped. By opening the file you can load the views of memory mapping file as you are accessing the file content from memory. You can also use OpenExisting or CreateNew methods if you are sure of the specific requirement of usage.

In the above code, you can see I have specified the length of the file as well as a fixed name "INTERPROC" which indicates the name of the shared memory location which other process can call to access. The length of the file needs to be a multiple of 1024 always, hence if you are not using the content of the file, the paging location will still be occupied with null content. This ensures the mapping file always remains in same context as the loaded memory. The file based Memory Mapping files are not loaded totally when you try to access, but rather it provides views in terms of what data you are currently accessing.

Once the file is opened, you can read the content of it using :

var mf = this.GetMemoryMapFile(FileMode.OpenOrCreate);
   using(mf)
   {
        int dsize = Marshal.SizeOf(typeof(T));
        T dData;
        int offset = dsize * index;
        using (var accessor = mf.CreateViewAccessor(0, length))
         {
             accessor.Read(offset, out dData);
             return dData;
         }
  }

Here once the MemoryMappedFile is loaded into memory, you can use CreateViewAccessor to create a separate view to see the content of the data. Here I have loaded the entire data at a time, but it is better to create View content as soon as you want to read its content. Once the data is loaded you can use Read method to get data from the memory.

var mf = this.GetMemoryMapFile(FileMode.OpenOrCreate);
using (mf)
{
int dsize = Marshal.SizeOf(typeof(T));
int offset = dsize * index;
using (var accessor = mf.CreateViewAccessor(0, length))
{
    accessor.Write(offset, ref dData);
}
}

Almost identical to it, you can use write method to write content into the memory.

Remember
One thing that you need to remember, when you Dispose the memory content from one process that means all the content that are loaded into view needs to be rewritten to the disk file, such that the other process needs to reopen and recreate the view again. If you want to access some specific view without writing to the page file often after using it, tweak the behavior by not disposing the MemoryMappedFile (removing the using block from here) and the operating system will automatically manage the destruction of View.

MemoryMapping without backup Paging file (For small data communication)

When the load of memory file is small, it would not be always good to have a large paging file created in background and wasting large amount of memory of the system. Sometimes we need to share content only between the process and when no process is accessing the shared memory, the operating system needs to dispose the content. Regards to this requirement, you can use MemoryMappedFiles to be stored only into memory and be called using the name of Memory created.

private MemoryMappedFile GetMemoryMapFile()
{

    var security = new MemoryMappedFileSecurity();
    security.SetAccessRule(
        new System.Security.AccessControl.AccessRule<MemoryMappedFileRights>("EVERYONE",
            MemoryMappedFileRights.ReadWriteExecute, System.Security.AccessControl.AccessControlType.Allow));

    var mmf = MemoryMappedFile.CreateOrOpen("InterPROC",
                    this.length,
                    MemoryMappedFileAccess.ReadWriteExecute,
                    MemoryMappedFileOptions.None,
                    security,
                    HandleInheritability.Inheritable);

    return mmf;

}

In the code above, I have created a MemoryMappedFile using CreateOrOpen and named the actual memory being created as "InterProc". Now this will create a file into the memory without any reference to it into disk file.  The HandleInheritability states that the child processes can also access the memory file.

You can also specify security on the memory using MemoryMappedFileSecurity and add Access rule to the security blocks.

public T ReadEntry<T>(int index) where T : struct
{
    var mf = this.GetMemoryMapFile();

    int dsize = Marshal.SizeOf(typeof(T));
    T dData;
    int offset = dsize * index;
    using (var accessor = mf.CreateViewAccessor(0, length))
    {
        accessor.Read(offset, out dData);
        return dData;
    }
}

public void WriteEntry<T>(T dData, int index) where T : struct
{
    var mf = this.GetMemoryMapFile();
    int dsize = Marshal.SizeOf(typeof(T));
    int offset = dsize * index;
    using (var accessor = mf.CreateViewAccessor(0, this.length))
    {
        accessor.Write(offset, ref dData);
    }
}

You can read from and write to the file in the same way as you did for the file based shared memory, but one thing you need to remember again, you cannot close/dispose the MemoryMappedFile as this will  erase all the content that you write into the View.

Here you can see, I didnt use using block or even wrote the Dispose of MemoryMappedFile as it is important.

Download the Source code

Using the code

In the sample application that I have provided with this article, there is an interface that you can use to handle both Large and small Communicators. Ideally these classes needs to be used based on the requirement on real world .

 public interface ICommunicator
    {
        T ReadEntry<T>(int index) where T : struct;
        void WriteEntry<T>(T dData, int index) where T : struct;
    }

int index = 0;
ICommunicator lcom = new SmallCommunicator();//new LargeCommunicator("D:\\");

// My Try
while (true)
{
    DummyData dData = lcom.ReadEntry<DummyData>(index);
    Console.WriteLine("Existing Data at 0 position : {0}", dData);

    Console.WriteLine("Write Data Id");
    dData.Id = Convert.ToInt32(Console.ReadLine());
    Console.WriteLine("Write new Data");
    dData.Data = Convert.ToInt32(Console.ReadLine());

    lcom.WriteEntry<DummyData>(dData, index);

    Console.ReadKey(true);
}
Thus it will open a SmallCommunicator (or LargeCommunicator) and read/Write data into it.

Hope that helps!

Happy Coding.
Shout it Submit this story to DotNetKicks Bookmark and Share
Read Disclaimer Notice

0 comments:

Post a Comment

Please make sure that the question you ask is somehow related to the post you choose. Otherwise you post your general question in Forum section.

Author's new book

Abhishek authored one of the best selling book of .NET. It covers ASP.NET, WPF, Windows 8, Threading, Memory Management, Internals, Visual Studio, HTML5, JQuery and many more...
Grab it now !!!