Inter-Process Communication using Shared Memory      by  Erwin Neyt


DOC REV  2 June 2000
CONTENTS:  *  Introduction
    1. WIN32 API Functions
    2. Defining the structure
    3. Full example
    4. Conclusion

Introduction

    In order to communicate between two (or more) processes in the Win32 environment, with little or no overhead, the use of shared memory is an elegant solution. This document was written to show how data can be exchanged between a simulation program and third-party (user) software. 

    The three steps in implementing shared memory.
     

  • 1    Create a mapped file during the initialization phase of the application 
  • 2    Read and write to structure members of the file mapping 
  • 3    Close the mapped file during shutdown of the application 
1. WIN32 API Functions
Memory-mapped file functions can be thought of as second cousins to the virtual-memory management functions in Win32. Like the virtual-memory functions, these functions directly affect a process's address space and pages of physical memory. No overhead is required to manage the file views, other than the basic virtual-memory management that exists for all processes. These functions deal in reserved pages of memory and committed addresses in a process. The most important of memory-mapped file functions are:
 
CreateFileMapping Creates or opens a file(-mapping)
MapViewOfFile Maps a view to the file(-mapping)
OpenFileMapping Opens the view to a file(-mapping)
UnmapViewOfFile Destroys the view to a file(-mapping)

For more info on Shared Memory techniques and API functions, please refer to the Microsoft MSDN Library or MSDN Online

CreateFileMapping
The CreateFileMapping function creates or opens a named or unnamed file-mapping object for the specified file.
HANDLE CreateFileMapping(
  HANDLE hFile,                       // handle to file
  LPSECURITY_ATTRIBUTES lpAttributes, // security
  DWORD flProtect,                    // protection
  DWORD dwMaximumSizeHigh,            // high-order DWORD of size
  DWORD dwMaximumSizeLow,             // low-order DWORD of size
  LPCTSTR lpName                      // object name
);

Example (all examples are in C)

hMap = CreateFileMapping(INVALID_HANDLE_VALUE,
                         NULL,
                         PAGE_READWRITE,
                         0,
                         SizeOf(FlightData),
                         "SimDataFile")

if (hMap != NULL && GetLastError() == ERROR_ALREADY_EXISTS) 
{ 
    CloseHandle(hMap); 
    hMap = NULL; 
} 
return hMap; 
Explanation

We call the function with INVALID_HANDLE_VALUE which creates a file-mapping object of the specified size backed by the operating-system paging file rather than by a named file in the file system

The lpAttributes is set to nil, which means default security, i.e. the user's security.

flProtect set to PAGE_READWRITE gives read/write access to the committed region of pages.

dwMaximumSizeHigh and dwMaximumSizeLow should reflect the size of the file, thus the size of the object RefStruct.

lpName is the name of the file, used in subsequent calls.

After the file is no longer needed, you must close the handle: CloseHandle(hMap); 

MapViewOfFile
The MapViewOfFile function maps a view of a file into the address space of the calling process.

To specify a suggested base address, use the MapViewOfFileEx function.

LPVOID MapViewOfFile(
  HANDLE hFileMappingObject,   // handle to file-mapping object
  DWORD dwDesiredAccess,       // access mode
  DWORD dwFileOffsetHigh,      // high-order DWORD of offset
  DWORD dwFileOffsetLow,       // low-order DWORD of offset
  SIZE_T dwNumberOfBytesToMap  // number of bytes to map
);


hView = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);

if (hView = NULL) 
{
        // Error mapping view
}

If dwNumberOfBytesToMap is set to zero, the entire file is mapped

OpenFileMapping
The OpenFileMapping function opens a named file-mapping object.
HANDLE OpenFileMapping(
  DWORD dwDesiredAccess// access mode
  BOOL bInheritHandle,    // inherit flag
  LPCTSTR lpName          // object name
);
FlightDataMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, false, "SimDataFile")

if (FlightDataMap != NULL)
{
  hFlightData = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
   
  if (hFlightData != NULL) 
  {
     // now we have access to the shared memory
  }
  CloseHandle(FlightDataMap);
}
else
{
  // Error opening file mapping
}
UnmapViewOfFile
The UnmapViewOfFile function unmaps a mapped view of a file from the calling process's address space.
BOOL UnmapViewOfFile( LPCVOIDlpBaseAddress // starting address);

lpBaseAddress is the pointer to the base address of the mapped view of a file that is to be unmapped. 
This value must be identical to the value returned by a previous call to the MapViewOfFile or MapViewOfFileEx function.

2. Defining the structure
As we need a definition to access the different locations in the mapped file, we typically define a structure.

Things we can put in the structure:

  • values of the flight model (position, acceleration, speed etc.)
  • values of the different gauges
  • states of all the lights in the cockpit
  • states of all the switches in the cockpit
  • bitmaps of various displays
Depending on the complexity of interaction these values/states can be read-only or read/write. In the latter case synchronization must be implemented in the application.

Typically the mapped file contains data which reflect as many variable states from the sim program as possible.

Example

struct
{
// outputs
float AirSpeed;   // access to airspeed
int LightBits;   // access to lights states (bitwise)
int AutePilot; // access to AutoPilot switch state 
byte LeftMFD[200][200]; // access to a Multi Function Display

// inputs
float HeadPitch; // Head pitch of VR helmet
float HeadRoll; // Head roll offset of VR helmet
float HeadYaw; // Head yaw offset  of VR helmet

} FlightData;

3. Full example.
These examples are straightforward and should be able to provide the developer and the user to setup inter-process communication using shared-memory. I will call the program sharing the data the server. This is typically the simulation software. Software written to communicate with the server, I will call the client
construction (server only)
hMap = CreateFileMapping(INVALID_HANDLE_VALUE,
                         NULL,
                         PAGE_READWRITE,
                         0,
                         SizeOf(FlightData),
                         "SimDataFile")

if (hMap != NULL && GetLastError() == ERROR_ALREADY_EXISTS) 
{ 
    CloseHandle(hMap); 
    hMap = NULL; 
} 
destruction (server only)
if (hMap != NULL) 
{ 
    CloseHandle(hMap); 
    hMap = NULL; 
} 
 
initialization (client and server)
int init (void)
{
FlightData* FlightData;
HANDLE hFlightData = NULL;
void* FlightDataMap = NULL;

FlightDataMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, false, "SimDataFile")

if (FlightDataMap != NULL)
{
  hFlightData = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
   
  if (hFlightData != NULL)
  {
    FlightData = (FlightData*)hFlightData;
  } 
  {
    // do exception handling here
  }
}
 
in process reading and writing (client and server)
// Write internal values to shared memory
FlightData->Airspeed = intAirSpeed;
// Read values from shared memory
intHeadPitch = FlightData->HeadPitch;
 
destruction (client and server)
int Destroy (void)
{
  if (hFlightData)
  {
    UnmapViewOfFile(hFlightData);
    hFlightData = NULL;
  }
  CloseHandle(FlightDataMap);
}
4. Conclusion
Using shared memory has great advantages for sim developers:
  • It gives the ability to expose internal data to 'the outside world' without the troubles of writing complicated API's.
  • Allows full interaction bases on the presence of data in the shared memory file.
  • Will allow their products to be used on simulation platforms and VR devices, thus enhancing the value of the simulation.
If you have any questions or remarks concerning the use of shared memory or simulation cockpit building in general, feel free to contact me.

Erwin Neyt (eagle9@simpits.org)
SimPits International
Back
Top