Introduction
Microsoft extended the .NET 4.0 API to include Memory Mapped Files (MMF), significantly improving .NET interoperability with other Windows architectures. Good articles and C# examples are provided by Microsoft and elsewhere demonstrating binary (byte array) I/O for inter-process communication, but little-to-nothing exists for practical use, such as, reading/writing anything other than simple data types. Reading/writing binary .NET managed objects was the challenge.
This article provides the basic framework for reading/writing complex .NET object variables using the using .NET API found in the System.IO.MemoryMappedFiles
namespace.
Using the code
This code demonstrates simple MMF I/O, including binary serialization between MMF byte arrays and managed .NET objects.
So why not use XML Serializers and write ASCII? There is a time and a place for everything.
First and foremost, examples below assume:
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.Serialization.Formatters.Binary;
Writing to MMF
Let's begin with a .NET managed
class used as a data container. Everything that should be serialized to MMF must be tagged as Serializable. In this case, here is the structure for a simple hiking database containing GPS tracks.
[Serializable]
public class HikingDatabase
{
public string Description;
public Hike[] hikes;
}
[Serializable]public class Hike
{
public string Date;
public string Description;
public Coord[] GPSTrack;
}
[Serializable]
public class Coord
{
public double x;
public double y;
public double z;
}
Next, let's assume data has been collected. We now need a simple way to write the managed object to a MMF as binary to be shared with other users of this Memory Mapped File:
WriteObjectToMMF("C:\\TEMP\\TEST.MMF", hikingData);
public void WriteObjectToMMF(string mmfFile, object objectData)
{
byte[] buffer = ObjectToByteArray(objectData);
using (MemoryMappedFile mmf =
MemoryMappedFile.CreateFromFile(mmfFile, FileMode.Create, null, buffer.Length))
{
using (MemoryMappedViewAccessor mmfWriter = mmf.CreateViewAccessor(0, buffer.Length))
{
mmfWriter.WriteArray<byte>(0, buffer, 0, buffer.Length);
}
}
}
The missing step in most MMF examples is the "ObjectToByteArray
" function that converts a
managed object into its binary form.
public byte[] ObjectToByteArray(object inputObject)
{
BinaryFormatter binaryFormatter = new BinaryFormatter(); MemoryStream memoryStream = new MemoryStream(); binaryFormatter.Serialize(memoryStream, inputObject); return memoryStream.ToArray(); }
Of course, you will want to wrap all these things with a try/catch before putting code into production.
Reading from MMF
You now have a file persisting managed objects to be consumed by other processes. These other processes will need to read these same objects. Very simply, this is done by opening the MMF, reading the byte array into a buffer, and converting that buffer back into a managed object:
HikingDatabase hikingData = ReadObjectFromMMF("C:\\TEMP\\TEST.MMF") as HikingDatabase;
public object ReadObjectFromMMF(string mmfFile)
{
using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(mmfFile, FileMode.Open))
{
using (MemoryMappedViewAccessor mmfReader = mmf.CreateViewAccessor())
{
byte[] buffer = new byte[mmfReader.Capacity];
mmfReader.ReadArray<byte>(0, buffer, 0, buffer.Length);
return ByteArrayToObject(buffer);
}
}
}
Again, below is the companion binary-to-managed ByteArrayToObject
function.
public object ByteArrayToObject(byte[] buffer)
{
BinaryFormatter binaryFormatter = new BinaryFormatter(); MemoryStream memoryStream = new MemoryStream(buffer); return binaryFormatter.Deserialize(memoryStream); }
As stated before, you will want to wrap these with try/catch
.
Points of Interest
I purposefully used API and avoided offsets into MMFs which support random
access to multiple memory buffers. While random access is good for persisting
multiple objects, it doesn't perform so well for whole-file I/O. When
reading/writing whole files, I suggest using CreateViewStream
in
place of CreateViewAccessor
.
When multiple processes share an open file, then it's wise to create a named mutex to manage concurrency. Failure to do so can result in unwanted behavior that can be very difficult to debug.
For more information, I suggest starting with the following articles from CodeProject and MSDN:
There are many, many other good articles. All you need do is search.
History
- 2012-10-02 - Initial posting.