Introduction
Memory Mapped Files are a form of Interprocess Communication used to pass data between processes. This article introduces a set of C++ helper classes to simplify using MM files.
This article will assume that you know what a memory mapped file (MMF) is and understand that an MMF can be based off of a physical file or backed by the system page file. The classes introduced later will be system page file backed MMFs. For an excellent primer on Memory Mapped Files, see Managing Memory Mapped Files.
Passing Data between processes and Data Synchronization
A Memory Mapped File is like any other shared resource passed between threads – that is, its access must be synchronized otherwise race conditions can occur. Shared data passed between two threads of a single process can be synchronized using a critical section or a mutex. However, when data is shared between multiple processes, a mutex must be used (as critical sections can’t be used across processes).
To help with thread synchronization chores, we emply the technique or Resource Acquisition Is Initialization (RAII) via synchronization helper classes defined in AutoLock.h in the attached sources. There are several ‘lock’ classes that wrap critical sections, mutexes, and reader writer lock implementation which are locked via the RAII approach by an AutoLockT locker class. While the focus of this article isn’t on thread synchronization, the use of these classes trivializes the synchronization.
The following table describes the thread sync classes.
Class | Description |
---|---|
CSLock | Critical Section wrapper |
MutexLock | Mutex wrapper |
ReaderWriterLock | Reader Writer lock class. Allows single writer, multiple readers. |
AutoLockT | RAII locking class used with above lock classes.. Acquires lock on construction and auto-releases lock on destruction |
Listing 1 is a simple example of using the AutoLockT class with a CSLock class.
// Example class that illustrates using the CSLock and AutoLockT classes
class Example
{
// Called by thread 1
void SetDataSafely( LPCTSTR szData )
{
// Acquire the critical section lock (prevent t2 access)
AutoLockT< CSLock > lock( &m_csLock );m_sData = szData;
}// Called by thread 2
void GetDataSafely( LPTSTR szBuffer, rsize_t size )
{
// Acquire the critical section lock (prevent t1 access)
AutoLockT< CSLock > lock( &m_csLock );_tcscpy_s( szBuffer, size, m_sData );
}
private:
CSLock m_csLock; // Critical section lock
CString m_sData; // data to protect
};
Listing 1
In the set and get methods above, the lock is aquired with AutoLockT, the data is accessed and the lock is released when the method goes out of scope. That’s RAII as applied to thread synchronization in a nutshell.
MMF Helper Classes
The tables below describe the MMF classes and methods.
MMF Classes
Class | Description |
---|---|
MMFileT | Base class that wraps mmf file functionality. Can be used to pass raw bytes between processes. |
MMFileStructT | Class that passes a structure between processes. |
MMFileStructArrayT | Class that passes an array of structures between processes. |
Table 1
MMF Methods
Method | Description |
---|---|
CreateMap | Creates a memory mapped file and optionally opens a file mapping. For the structure classes, automatically sizes the mapping to the structure (or array of structures). |
FreeMap | Closes the file mapping. |
MapView | Maps a view. Typically not used for the structure derived implementations. |
OpenMap | Opens an existing MMF. Fails if the MMF does not exist. |
raw | Accesses the underlying structureClass that passes an array of structures between processes. |
UnMap | Unmaps the data view. |
Table 2
Sending a structure between processes
The sample solution includes two projects: The LogSender project and LogReceiver project which pass a LOGITEM stucture between two processes simulating interprocess logging.
Running the sample code
Open two instances of Visual Studio and load the MMFile.sln into each instance. In the first instance, set the LogReceive project as the startup project. Set a few breakpoints in the project and press F5 to start the receiver. In the second instance, set the LogSender projects as the startup project. Set a few breakpoints and press F5. As you navigate the break points of the LogSender project, the LogReceiver project should hit the break points and output the log data to it’s console.
Note: the code listings below do not include error handling. Consult the attached source code for more detailed error handling.
LogSender Code
int _tmain( int argc, _TCHAR* argv[] )
{
const int TIMEOUT = 5000;// Create the mmf class instance
MMFileStructT< LOGITEM > logItemMmf( _T(“{91085AD2-ECC3-4bbe-B81C-DC53E6E3A39F}”), TIMEOUT );// Create and open the file mapping
logItemMmf.CreateMap( );// Send a bunch of log items over to the LogReceiver process
for( int x = 0; x < 100; x++ ) { CString sMsg; sMsg.Format( _T("Log item msg - %d"), x ); { // Locking scope (briefly lock the mmf to set the log data) // Lock the mmf using RAII AutoLockT< MMFileStructT< LOGITEM > > lock( &logItemMmf, TIMEOUT ); // Change the log data LPLOGITEM pLogItem = logItemMmf.raw( ); pLogItem->uDepth = x;
_tcscpy_s( pLogItem->szMsg, sMsg );} // Mmf unlocked here
// Signal the LogReceiver process and wait for it to retrieve the log data
::SignalObjectAndWait( logItemMmf.GetChannelEvent( MMFile::EVENT_CH1 ),
logItemMmf.GetChannelEvent( MMFile::EVENT_CH2 ),
TIMEOUT,
FALSE );// Reset the CH2 event
logItemMmf.ResetChannelEvent( MMFile::EVENT_CH2 );
}return 0;
}
LogReceiver Code
int _tmain(int argc, _TCHAR* argv[])
{
const int TIMEOUT = 5000;MMFileStructT< LOGITEM > logItemMmf( _T(“{91085AD2-ECC3-4bbe-B81C-DC53E6E3A39F}”), TIMEOUT );
logItemMmf.CreateMap( );
HANDLE aHandles[] = { logItemMmf.GetChannelEvent( MMFile::EVENT_CH1 ) };
BOOL bContinue = TRUE;
while( bContinue )
{
switch( WaitForMultipleObjects( sizeof(aHandles) /sizeof(HANDLE),
&(aHandles[0]),
FALSE,
INFINITE))
{
// Channel 1 Event Fired (From LogSender process)
case WAIT_OBJECT_0:
{
// Lock the inter-process mmf mutex using RAII
AutoLockT< MMFileStructT< LOGITEM > > lock( &logItemMmf, TIMEOUT );// Access the log item
LPLOGITEM pLogItem = logItemMmf.raw( );// Display the message to the console
_tprintf( _T( “LogItem – depth: %d message: %sn” ), pLogItem->uDepth, pLogItem->szMsg );// Exit if we’ve reach 100 log items
if( pLogItem->uDepth >= 99 )
{
bContinue = FALSE;
}
}// Reset the CH1 event
logItemMmf.ResetChannelEvent( MMFile::EVENT_CH1 );// Set the CH2 event
logItemMmf.SetChannelEvent( MMFile::EVENT_CH2 );
}
}std::cout << "nLog Received completed! Exiting..." << std::endl; Sleep( 5000 ); return 0; }
Code Description
As you can see from the LogSender listing above, an instance of the MMFileStructT class is made given a unique string. Next, the CreateMap method is called to create the underlying memory mapped file based on the size of the structure. Internally the CreateMap also creates a view of the file.
To access the LOGITEM structure, the .raw( ) method is called. You’ll notice that before accessing the class the AutoLockT is used to lock the logItemMmf object. The base MMFile class internally creates a mutex (which is what the AutoLockT class locks) and a couple of ‘channel’ events.
The SignalObjectAndWait api is used to set the Channe1 1 event so the LogReceiver process gets signalled that the MMF data has changed. The LogReceiver process waits on the Channel 1 event and then locks the mutex, reads the log item data and then sets the Channel 2 event to indicate that it has finished reading the data. The SignalObjectAndWait api blocks until it the Channel 2 event has been set by the LogReceiver process.
The LogReceiver code creates an MMF instance with the same name as used by the LogSender project. The project code just waits on the Channel 1 event before accessing the log item structure. It sets the Channel 2 event when it’s finished accessing the structure. Setting the Channel 2 event causes the LogSender app to send the next log item.
Conclusion
Memory Mapped Files offer one form of interprocess communication. The classes detailed above help the developer use MMF’s to pass raw data, a structure, or an array of structures between processes.
About the Author
Arjay Hawco is an application developer/architect and Microsoft C++ MVP who works with the latest WxF .Net technologies. He is also cofounder of Iridyn, Inc, a software consulting firm.