In this
Post I face a new argument: Memory Mapped Files.
Why this choice?
Simply
because I need a shared memory between processes where to store data, and MMF
is a really good way to do this. I have a software that needs to read and write
data to and from external devices, and the devices registries are global
variables in my programs.
This
solution guaranties a fast and easy storage, no database mechanisms, between
data and applications (logic layer). I don’t pretend to be understood but
simply share my studies about MMF.
Just to be
clear, I’m not an expert of MMF but only a new user.
Window
offers three groups of functions for managing memory in applications:
memory-mapped file functions, heap memory functions, and virtual-memory
functions.
Memory Mapped Files
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. Starting with the .NET Framework 4, you can use managed code to access memory-mapped files in the same way that native Windows functions access memory-mapped files, as described in Managing Memory-Mapped Files in Win32 in the MSDN Library.
A good article is also: Managing Memory-Mapped Fileds (https://msdn.microsoft.com/en-us/library/ms810613.aspx).
There are two types of memory-mapped files:
- Persisted memory-mapped files
- Non-persisted memory-mapped files
This and more can be read in MSDN (https://msdn.microsoft.com/en-us/library/dd997372.aspxhttps://msdn.microsoft.com/en-us/library/dd997372.aspx).
There are two types of views:
• Stream access view
• Random access view.
Use stream access views for sequential access to a file; this is recommended for non-persisted files and IPC. Random access views are preferred for working with persisted files.
Functions
Memory-mapped file functions can be thought of as second cousins to the virtual-memory management functions in Windows. Like the virtual-memory functions, these functions directly affect a process's address space and pages of physical memory. No overhead is required to manage the file views, other than the basic virtual-memory management that exists for all processes. These functions deal in reserved pages of memory and committed addresses in a process. The entire set of memory-mapped file functions are not here discussed but only what I need for my example.
Example Project
In my example I will use a Random access view because I need to move up and down the variables list. I defined a class named MemoryManager which encapsulate all the code usefull for the MMF management.
public class MemoryManager { string msMutexName = "map_mutex"; string msName = ""; //Memory Area Name MemoryMappedFile moMem = null; MemoryMappedViewAccessor moAncestor = null; public string Name { get { return msName; } set { msName = value; } } int miObjSize = 0; int miCount = 0; //number of elements\objects public MemoryManager() {
I image to have a list of objects to map in a MMF, and a list of methods to query and writes values in this list.
No Generics or Classes
public void Create
because T or Objects can be nullable! So a Struct is the right choice. In My example I called it MyStruct.
////// In order to use MMF it is neccessary using not nullable types (16Byte) /// public struct MyStruct { public int Key; //Primary Key public int ValueInt; //Current In Value public float ValueFloat; //Current Float Value public int EditInt; //New Int value to write public float EditFloat; //New Float value to write public int Edit; //There is at least one value to write: Int or Float or both public MyStruct(int key, int value_int = 0, float value_float = 0, int edit_int = 0, float edit_float = 0, int edit = 0) { Key = key; ValueInt = value_int; ValueFloat = value_float; EditInt = edit_int; EditFloat = edit_float; Edit = edit; } }}
public void Create(ListlListOfObjects) { if (lListOfObjects == null) return; bool mutexCreated; miObjSize = Marshal.SizeOf(typeof(MyStruct)); miCount = lListOfObjects.Count; int iBufferSize = miObjSize * miCount; moMem = MemoryMappedFile.CreateNew(msName, miObjSize * miCount); moAncestor = moMem.CreateViewAccessor(0, iBufferSize); Mutex mutex = new Mutex(true, msMutexName, out mutexCreated); int iIndex = 0; for (int i = 0; i < miCount; i++) { MyStruct o = lListOfObjects[i]; moAncestor.Write(iIndex, ref o); iIndex += miObjSize; } mutex.ReleaseMutex(); }
The method Create allocate a memory map with CreateNew(), assigning a name and a size to a map. While CreateViewAnchestor() create a random access view. The for section initialize the memory with the list.
From this moment ahead this memory space is available from other process that wants read and write into it.
public string Name { get { return msName; } set { msName = value; } public bool Open(int iCount) { try { miCount = iCount; moMem = MemoryMappedFile.OpenExisting(msName); return true; } catch (FileNotFoundException) { return false; } } public MyStruct Read(int iIndex) { if (moMem == null) return new MyStruct(-1); miObjSize = Marshal.SizeOf(typeof(MyStruct)); if (moAncestor == null) { int iBufferSize = miObjSize * miCount; moAncestor = moMem.CreateViewAccessor(0, iBufferSize); } int iBufferIndex = iIndex * miObjSize; MyStruct oRet; moAncestor.Read(iBufferIndex, out oRet); return oRet; } public void Write(int iIndex, MyStruct oValue) { if (moMem == null) return; bool mutexCreated; Mutex mutex = new Mutex(true, msMutexName, out mutexCreated); mutex.WaitOne(); miObjSize = Marshal.SizeOf(typeof(MyStruct)); if (moAncestor == null) { int iBufferSize = miObjSize * miCount; moAncestor = moMem.CreateViewAccessor(0, iBufferSize); } int iBufferIndex = iIndex * miObjSize; if (moAncestor.CanWrite) { moAncestor.Write(iBufferIndex, ref oValue); Debug.Print(string.Format("Write index: {0} -> {1}, {2}, {3}", oValue.Key, oValue.ValueInt, oValue.ValueFloat, oValue.Edit)); } mutex.ReleaseMutex(); } Shortcut methods public int ReadInt(int iIndex) { MyStruct oValue = Read(iIndex); return oValue.ValueInt; } public void WriteInt(int iIndex, int iValue) { MyStruct oValue = Read(iIndex); oValue.ValueInt = iValue; Write(iIndex, oValue); }download the example