Non-persisted mapped memory file using C# and Windows Presentation Foundation and .NET Framework 4
This article discusses an application of mapped-memory in a program
Why mapped-memory?
While creating a Windows Presentation Foundation (WPF) program, one challenge you might run into is how to handle data between applications, this problem becomes especially acute when passing large amounts of data. This is because mapped-memory is passed by accessing pointers. You “map a view of all or part of a file on disk to a specific range of addresses within your process’s address space. And once that’s done, accessing the content of a memory-mapped file is as simple as de-referencing a pointer in the designated range of addresses.“ [ 1 ]
Interestingly, creating mapped-memory was made possible in Win32 programs since Windows NT. There is a good example in [ 1 ] under Creating a File Mapping section written in C. So although this article is about creating mapped-memory in .NET Framework, it’s possible to do it in Win32. That should be encouraging to anyone new to any readers who are new to programming in Win32.
Applying mapped-memory to a .NET Framework program
In this application, we are using what’s called a non-persisted memory-mapped file. It’s good to understand the difference between the difference between persisted and non-persisted. See [ 2 ] for more information. In this case since the application will use non-persisted mapped-memory, it allows for the garbage collection to take care of the file; which means it’s best for inter-process communications.
Consider a simple application which will create a non-persisted memory-mapped file, then have a separate application open and read that file. Code block 1 and 2 contain the application which will create the file using user input. Code block 3 and 4 contain the application which will read and display the file data.
Code block 1: XAML for the file creator program
<Window x:Class="WriteOutFile.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WriteOutFile"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="25"/>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Grid.Column="1" Grid.Row="0" Name="textBox1" Content="Mapped Memory Creator"/>
<Label Grid.Column="2" Grid.Row="2" Name="textBox2"/>
<Button Grid.Column="1" Grid.Row="1" Content="Send Message" Click="sndMsg"/>
<Button Grid.Column="2" Grid.Row="1" Content="Start Program" Click="SrtPrgm"/>
</Grid>
</Window>
Code block 2: C# for the code behind of the file creator program
/*
* Purpose: Create a non-persisted memory-mapped file that can be accessed and changed by another program
* Program creates file, it allows user to run a program that can read and write to the file
* It will respond to changes made to the file
*/
using System;
using System.Text;
using System.Windows;
using System.Windows.Threading;
using System.IO.MemoryMappedFiles;
using System.Diagnostics;
namespace WriteOutFile
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
//private string tmp = "Hello World!";
private double freq = 0;
private double t = 0;
MemoryMappedFile mFile;
MemoryMappedViewAccessor mfAcc;
public MainWindow()
{
InitializeComponent();
// Create mapped file
mFile = MemoryMappedFile.CreateNew("mmFile",10000, MemoryMappedFileAccess.ReadWrite);
// Create mapped file view accessor
mfAcc = mFile.CreateViewAccessor();
}
/// <summary>
/// Write to the mapped file
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void sndMsg(object sender, RoutedEventArgs e)
{
// Read in the mapped data
MemoryMappedViewAccessor newFacc = mFile.CreateViewAccessor();
byte[] msg = new byte[newFacc.ReadInt32(0)];
mfAcc.ReadArray<byte>(4, msg, 0, msg.Length);
// Set up timer for animation
DispatcherTimer dt = new DispatcherTimer();
dt.Tick += new EventHandler(WriteToFile);
dt.Interval = TimeSpan.FromMilliseconds(1.3);
dt.Start();
}
/// <summary>
/// Start the Read program
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SrtPrgm(object sender, RoutedEventArgs e)
{
Process ptmp = new Process();
ptmp.StartInfo.FileName = @"<path to file>\ReadOutFile.exe";
ptmp.StartInfo.Arguments = "";
bool result = ptmp.Start();
if (result == false)
{
textBox2.Content = "Error opening file\n";
}
else
{
textBox2.Content += "Program started\n";
}
}
/// <summary>
/// Continuously write data to the mapped file
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void WriteToFile(object sender, EventArgs e)
{
t += 0.001;
freq = Math.Cos(401.2*t);
byte[] message = Encoding.UTF8.GetBytes(freq.ToString());
mfAcc.Write(0, message.Length); // Prepares the accessor for writing
mfAcc.WriteArray<byte>(4, message, 0, message.Length); // Write message to file given the starting point
mfAcc.Flush(); // Clear out buffer
// Read in the mapped data
MemoryMappedViewAccessor newFacc = mFile.CreateViewAccessor();
byte[] msg = new byte[newFacc.ReadInt32(0)];
mfAcc.ReadArray<byte>(4, msg, 0, msg.Length);
textBox2.Content += Encoding.UTF8.GetString(msg) + "\n";
}
/// <summary>
/// Continuously write data to the mapped file
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void WriteToFile(object sender, EventArgs e)
{
t += 0.001;
freq = Math.Cos(401.2*t);
byte[] message = Encoding.UTF8.GetBytes(freq.ToString());
mfAcc.Write(0, message.Length); // Prepares the accessor for writing
mfAcc.WriteArray<byte>(4, message, 0, message.Length); // Write message to file given the starting point
mfAcc.Flush(); // Clear out buffer
// Read in the mapped data
MemoryMappedViewAccessor newFacc = mFile.CreateViewAccessor();
byte[] msg = new byte[newFacc.ReadInt32(0)];
mfAcc.ReadArray<byte>(4, msg, 0, msg.Length);
textBox2.Content += Encoding.UTF8.GetString(msg) + "\n";
}
}
}
Code block 3: XAML for the file reader program
<Window x:Class="ReadOutFile.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ReadOutFile"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="25"/>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Grid.Column="1" Grid.Row="0" Name="textBox1" Content="Message Reader"/>
<TextBox Grid.Column="2" Grid.Row="2" Name="textBox2" VerticalScrollBarVisibility="Visible"/>
<Button Grid.Column="1" Grid.Row="1" Content="Receive Message" Click="RxMsg"/>
</Grid>
</Window>
Code block 4: C# for the code behind of the file reader program
/*
This programs reads non-persisted mapped-memory
*/
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Text;
using System.Windows;
namespace ReadOutFile
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
textBox2.Text += "Program has begun\n";
}
// Read in the file when button is pushed by the user, then display it to the text box in the window
private void RxMsg(object sender, RoutedEventArgs e)
{
try
{
using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting("mm_tdms"))
{
using (MemoryMappedViewAccessor memView = mmf.CreateViewAccessor())
{
byte[] reader = new byte[memView.ReadInt32(0)];
memView.ReadArray<byte>(4,reader,0,reader.Length);
textBox2.Text += Encoding.UTF8.GetString(reader, 0, reader.Length)+"\n";
}
}
}
catch (FileNotFoundException)
{
textBox2.Text = "ERROR: File not found";
}
}
}
}
Demo of the program
Other applications of mapped-memory
An interesting application that I used mapped-memory was when I needed to pull radio frequency data from a device which required multiple displays each depicting a separate channel. It was a good intersection of Windows programming and signal processing. But best leave that for another post for some other time.