(Print this page)

Using System.IO.MemoryMappedFiles
Published date: Friday, September 9, 2011
On: Moer and Éric Moreau's web site

Every so often, we see people on newsgroups asking how to share data between 2 applications running on the same computer (aka inter-process communication).

The most common answer is to save the data to a file on disk or to a database but this comes with an overhead.

Of course, it has always been possible to use mechanism such as PInvoke to dig into memory to reach some parcel of data but this method is not very easy (and it wasn’t using managed code).

Recently, I discovered a new namespace of the .Net Framework 4.0 that solves this problem.

The demo application

In fact this month I have 2 applications. The 2 demo applications have been created using Visual Studio 2010 because this namespace is only available from the .Net Framework 4.0.

The first application (the server) is written in VB and is the application that creates the shared zone in memory and fills it with whatever is in the textbox control.

Figure 1: The server application

The second application (the client) is written in C#. This application just tries to read the shared memory zone and display it in a textbox.

Figure 2: The client application

Writing to the shared zone

The first thing you will need to do is to create a name that will be known by both applications. This name has to be unique among object on the system has it becomes the key to the memory zone (much like a file name).

Notice that there is no Flush method to call. You don’t really have the control over when the data is really made available to other processes but since the resources are only memory bits, it is usually almost instantaneously.

If you look at figure 1, you see that the server application offers a very simple UI. Only a Label control and a TextBox control (named txtTextToShare).

The complete code of this server application reads like this:

Option Strict On

Imports System.IO.MemoryMappedFiles

Public Class Form1

    Private mobjMMF As MemoryMappedFile
    Private mobjWriter As MemoryMappedViewAccessor

    Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        'initialize the MemoryMappedFile object
        mobjMMF = MemoryMappedFile.CreateNew("EMoreauSharedDemo", 8096)
        'and create a writer
        mobjWriter = mobjMMF.CreateViewAccessor
    End Sub

    Private Sub txtTextToShare_TextChanged(sender As Object, e As System.EventArgs) Handles txtTextToShare.TextChanged
        'whenever the text changes
        Dim c() As Char = txtTextToShare.Text.ToCharArray
        'write the length of data
        mobjWriter.Write(0, c.Length)
        'write the data with an offset of 10 
        mobjWriter.WriteArray(Of Char)(10, c, 0, c.Length)
    End Sub

    Private Sub Form1_FormClosed(sender As Object, e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
        'free resources
        mobjWriter.Dispose()
        mobjMMF.Dispose()
    End Sub

End Class

You can first see at the top, the Imports statement. There is no additional reference required in your project. This class is already into the System.IO namespace.

Then you can find the declaration of 2 variables (mobjMMF and mobjWriter) because we want to reuse them without having to re-instantiate them every time we want to write a character to memory file.

The form’s Load event follows just instantiating the memory zone. Two things here are worth noticing: the name of the memory zone (EMoreauSharedDemo) and the allocated size (8096). Because I am the creator of both server and client applications, I have the full control over them and I need to use the same value in both applications.

For the purposes of my demo application, I have decided to refresh the shared memory zone every time the content of the textbox is modified. This is why the TextChanged event of the textbox control is used. The content of the textbox is first copied to an array of characters. The length is then written to the shared memory zone using the writer object at offset 0 using one of the many overloads of the Write method. Finally, the WriteArray method writes the array starting at offset 10 (I know I am losing a couple bytes here).

The last thing this application does is to free the resources in the Form’s FormClosed event.

Reading from the shared zone

Now, if you look at figure 2, you will find 2 controls. The first one is a Button (named btnGetSharedText) and the second control is a read-only TextBox (named txtSharedText).

All the code required by this client application is written into the button’s Click event:

private void btnGetSharedText_Click(object sender, EventArgs e)
{
    //clear the textbox
    txtSharedText.Clear();
    try
    {
        //initialize the MemoryMappedFile object
        using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting("EMoreauSharedDemo"))
        {
            //open a reader
            MemoryMappedViewAccessor objReader = mmf.CreateViewAccessor(0, 8096);

            //read the length of data to process
            int iLength;
            objReader.Read<int>(0, out iLength);
            txtSharedText.AppendText(iLength.ToString() + " characters to read." + Environment.NewLine);
            txtSharedText.AppendText("------------------------------" + Environment.NewLine);

            if (iLength > 0)
            {
                //read the content of the stream into a char array (don't forget the offset of 10)
                char[] buffer = new char[iLength];
                objReader.ReadArray<char>(10, buffer, 0, buffer.Length);
                //output the buffer
                for (long i = 0; i < iLength; i += 1)
                {
                    txtSharedText.AppendText(buffer[i].ToString());
                }
            }
        }
    }
    catch (FileNotFoundException)
    {
        txtSharedText.Text = "Memory-mapped file does not exist. Run Process A first.";
    }
}

You can find that the same unique name (EMoreauSharedDemo) is used to access the shared memory zone. The following line creates an accessor using the length of 8096 which is set by the server application.

Once the stream is opened, the length stored in the first bytes of the memory zone is read and stored into the iLength variable.

Once we know the real size of the data to process, we read it into an array of characters starting with an offset of 10.

Limitations

If you are running on a 32-bit computer, be aware that the shared memory zone is limited to 2 GB of data. If you need more, you can create multiple zones.

Security

Sometime, you may have to protect the data you put in a MemoryMappedFile object. You have the full control over which objects can read the memory zone. For more information on this topic, follow this link to the MemoryMappedFileSecurity class.

Conclusion

This is not a complex mechanism to implement into your applications. If your system administrators don’t have an inventory containing this information, I would suggest that you start collecting this information into a database right now. When you will want to upgrade your old applications, you will be able to quickly find if the requirements are met by all computers.


(Print this page)