Tuesday, December 28, 2010

WPF - Interprocess Messaging using Memory Mapped Files

.Net framework 4.0 has a I/O based mechanism to exchange data between processes. It is Memory-mapped files. The idea is to create an in-memory file and use that file as a means of communication of messages between different processes.



Let us create a sample WPF application. This just writes the contents of user-entered data to a named memory-mapped file. This also reads data from the file and shows in a text block. Let us name this as WpfApplication_MemoryMappedFiles.

Open MainWindow.xaml and update it as follows:

<Window x:Class="WpfApplication_MemoryMappedFiles.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox Height="30" HorizontalAlignment="Left" Margin="12,22,0,0" Name="textBoxDataTo"
VerticalAlignment="Top" Width="337" />
<Button Content="Write Data" Height="28" HorizontalAlignment="Left" Margin="12,58,0,0"
Name="btnWriteData" VerticalAlignment="Top" Width="143" Click="btnWriteData_Click" />
<TextBlock Height="81" HorizontalAlignment="Left" Margin="12,92,0,0" Name="textBlockDataFrom"
Text="TextBlock For Data Read from Memory Mapped File" VerticalAlignment="Top" Width="337" />
<Button Content="Read Data" Height="29" HorizontalAlignment="Left" Margin="12,0,0,103"
Name="btnReadData" VerticalAlignment="Bottom" Width="142" Click="btnReadData_Click" />
</Grid>
</Window>

Let us update the code behind of the above window as follows:

public partial class MainWindow : Window
{
MemoryMappedFile memoryMappedFileMediator;
MemoryMappedViewAccessor memoryMappedFileView;

public MainWindow()
{
InitializeComponent();

memoryMappedFileMediator = MemoryMappedFile.CreateNew("MediatorMemoryMappedFile", 1000, MemoryMappedFileAccess.ReadWrite);
memoryMappedFileView = memoryMappedFileMediator.CreateViewAccessor();
}

private void btnWriteData_Click(object sender, RoutedEventArgs e)
{

byte[] message = Encoding.UTF8.GetBytes(this.textBoxDataTo.Text);
memoryMappedFileView.Write(0, message.Length);
memoryMappedFileView.WriteArray<byte>(4, message, 0, message.Length);
memoryMappedFileView.Flush();

}

private void btnReadData_Click(object sender, RoutedEventArgs e)
{
byte[] message = new byte[memoryMappedFileView.ReadInt32(0)];
memoryMappedFileView.ReadArray<byte>(4, message, 0, message.Length);
string tempMessage = Encoding.UTF8.GetString(message, 0, message.Length);


this.textBlockDataFrom.Text = tempMessage;
}
}

We need to add the namespace using System.IO.MemoryMappedFiles in order to build the above code. This namespace has the required memory mapped files related types defined. We are just writing the length of data in the file followed by the data starting from a specified position in the file. We would start reading the data from the same position when we would read the file. The length of data in the beginning avoids making unnecessary guess about the length of data. This is purely a choice how we want to define the length of our messages.

Let us run the project. We enter "WPF Rocks!!!" in the text box and hit "Write Data" button. As we click Read Data button, the data is read from the memory mapped file and updates the text box as follows:



Inter-process messaging:
In order to present the idea of communication of messages between different processes, let us create a new WPF application named MemoryMappedFile_MessageRecipient. Let us update the xaml of MainWindow as follows:

<Window x:Class="MemoryMappedFile_MessageRecipient.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBlock Height="196" HorizontalAlignment="Left" Margin="6,6,0,0" Name="textBlockMessage"
Background="Beige" VerticalAlignment="Top" Width="488" />
<Button Content="Get Data" Height="33" HorizontalAlignment="Left" Margin="8,210,0,0"
Name="btnReadMessage" VerticalAlignment="Top" Width="172" Click="btnReadMessage_Click" />
</Grid>
</Window>

This just has a text block and a button. Clicking the button should update the TextBlock with the message read from memory mapped file. The code behind is as follows:

public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private void btnReadMessage_Click(object sender, RoutedEventArgs e)
{
using (MemoryMappedFile memoryMappedFileMediator = MemoryMappedFile.OpenExisting("MediatorMemoryMappedFile"))
{
using (MemoryMappedViewAccessor memoryMappedFileView = memoryMappedFileMediator.CreateViewAccessor())
{
byte[] message = new byte[memoryMappedFileView.ReadInt32(0)];
memoryMappedFileView.ReadArray<byte>(4, message, 0, message.Length);
string tempMessage = Encoding.UTF8.GetString(message, 0, message.Length);


this.textBlockMessage.Text = tempMessage; //tempMessage.Substring(0, tempMessage.IndexOf("!!!") + 3);
}
}
}
}

Here we have used the same protocol as defined in previous example. We are reading the length of message from the beginning of the file. We are expecting the message afterwards.

Now let us run both application together. We first bring the first application up and write "Pakistan Zindabad!" to the memory mapped file. In order to verify that the message is written to the memory mapped file. We click the Read Data button. The form should read the contents of the memory mapped file and update the contents of the TextBlock.



Keeping this running, let us bring the second application (MemoryMappedFile_MessageRecipient). Click Read Data button. The contents of the window should be updated as follows:



Limitations:
Only value types can be used with a Memory-mapped file. It also supports arrays and structs of the value-types.

Length of Message:
When a recipient reads a message from a Memory-mapped file, it has to specify the length of data to read from this. The issue is how the recipient would know the length of data in advance. I have seen people using many techniques:

1. Write the length of message in the beginning. It specifies the length of characters to read from the memory mapped file.

2. Write only fixed length data to the memory mapped file. If the contents are smaller then append it with some known characters. Get rid of the appended data at the end of the message.

Securing Memory Mapped file:
As discussed, the memory mapped file is created in memory. Now this is the same memory shared by all the processes. You might be thinking about securing your message or some how tunnel it so, even if, someone peek into it, it just couldn't understand the message.

Is really a file created?
When we are creating memory-mapped files just for the purpose of sharing data between processes. Sometimes, it seems to be a misnomer in this case as we are not creating any physical file. It is rather setting aside a named memory space for sharing of data between two processes.

Memory mapped file for random I / O:
We have discussed memory-mapped file as a mechanism for sharing data between processes. Memory mapped file is much more than that. It allows to provide random access to a data in a file. If we need to update the contents of file on a very frequent basis at random basis, we can create a memory-mapped file from that file. Work with memory mapped file for the whole operation. Un-map the memory-mapped file saving the original file.

This is basically 10 times faster for random access of file data than FileStream.

File Not Found Exceptions:
If we attempt to open a non-existing memory-mapped file then it results in the familiar File Not Found Exception.



Lifetime of memory mapped file:
As we know that MemoryMappedFile available in .net 4.0 implements IDisposable. After disposing, all the references to the memory mapped file are lost. It doesn't mean that memory mapped file is scrapped by Windows. Windows keeps the file up even if there is one reference to the file by any process. If the number of references to the memory mapped file are ZERO, then it is claimed by the operating system. You would find the attached code similar to this concept. In order to prove this point, follow these steps:

1. Bring the WpfApplication_MemoryMappedFiles up and write some data to the memory mapped file (e.g. "Message from app1")

2. Bring the MemoryMappedFile_MessageRecipient application up. Read data from the memory mapped file.

3. Turn the application (step 1) down. Bring it back up and click Read Data button. You would notice that the same data is read into the TextBlock.

4. Now bring both applications down. Bring the first application (step 1) up and read data from memory mapped file by clicking "Read Data" button. No data is read.

This proves our point that as long as there is one reference to the memory mapped file, the resource is not claimed by Windows.

Download:

1 comment:

ernie said...

Very... Nicee... Blog.. I really appreciate it... Thanks..:-)