What this Article Is
A common task in advanced Windows programming is to share data between two or more processes. There have been several articles written on the subject, but many were too complex for what I needed. What I needed was a very simple means to share data between several running instances of my application. Since the interaction between these instances is rather complex, I wanted to handle synchronization, etc. on my own rather than find a framework which supported the level of synchronization I wanted.
What this Article Is Not
I said, this article is about a simple way to share memory between multiple processes. The code presented makes no effort to synchronize read and write access to the data block. Likewise, all clients connecting to the shared memory are given full access to the shared memory. While adding a security model to the class is almost trivial, I decided not to in an effort to keep the code simple.
Introducing CSharedStruct
In this article, memory is shared via a class I created called CSharedStruct
. This class encapsulates the shared memory region. The code is relatively simple, and rather short, as seen below:
#if !defined(AFX_CSharedStruct_H__86467BA6_5AFA_11D3_863D_00A0244A9CA7__INCLUDED_)
#define AFX_CSharedStruct_H__86467BA6_5AFA_11D3_863D_00A0244A9CA7__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif
#define SHARED_NAME_SIZE 256
template <class StructType>
class CSharedStruct
{
private:
HANDLE m_hFileMapping;
char m_hSharedName[SHARED_NAME_SIZE];
DWORD m_dwMaxDataSize;
StructType *m_pvData;
BOOL m_bCreated;
public:
CSharedStruct();
VOID Release();
BOOL Acquire(char *Name);
StructType *operator->();
};
template <class StructType>
StructType *CSharedStruct<StructType>::operator->()
{
return m_pvData;
}
template <class StructType>
CSharedStruct<StructType>::CSharedStruct()
{
m_hFileMapping = NULL;
}
template <class StructType>
VOID CSharedStruct<StructType>::Release()
{
if (m_pvData)
{
UnmapViewOfFile(m_pvData);
}
if (m_hFileMapping)
{
CloseHandle(m_hFileMapping);
}
}
template <class StructType>
BOOL CSharedStruct<StructType>::Acquire(char *Name)
{
m_dwMaxDataSize = 0;
m_hFileMapping = CreateFileMapping (INVALID_HANDLE_VALUE, NULL,
PAGE_READWRITE, 0, sizeof(StructType), Name);
if (m_hFileMapping == NULL)
{
return FALSE;
}
m_dwMaxDataSize = sizeof(StructType);
strncpy(m_hSharedName, Name, SHARED_NAME_SIZE - 1);
m_pvData = (StructType *) MapViewOfFile( m_hFileMapping,
FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
if (m_pvData == NULL)
{
CloseHandle(m_hFileMapping);
return FALSE;
}
return TRUE;
}
#endif
The first thing to notice is that, this is a template based class. While template syntax might make the include file a little harder to read, it makes the code written to access the shared data much cleaner. To create a shared memory object, first we declare the structure we want to share, then we wrap it in a CSharedStruct
template, like:
typedef struct _MyData
{
int x;
char text[256];
} MyData;
CSharedStruct<MyData> m_SharedData("SharedDataName");
The text string passed into the constructor identifies the shared memory object to use. So, if we want to share multiple memory buffers, we need to give each one a unique name. If we want to access a shared memory buffer in another application, we must pass the same name to the constructor.
CSharedStruct<MyData> m_SharedData1("Alpha");
CSharedStruct<MyData> m_SharedData2("Alpha");
CSharedStruct<MyData> m_SharedData3("Beta");
Remember that these names need to be unique across all processes. If you write myprogram.exe which uses a shared structure named "Shared", and I write myotherprogram.exe using a CSharedStruct
with the same name, we'll end up pointing to the same data! As such, I suggest using the guidgen.exe program in the Platform SDK to create a unique GUID
to append to any names you use, just to be safe.
Also realize that these shared memory sections can be seen by other processes. If someone learns the name of the shared memory section (say by using SysInternals' HandleEx program), they can look at your shared data. If this bothers you, encrypt your data before placing it in a shared memory section and implement security attributes for the file mapping.
If for some reason, you want to use the default constructor, you have to "initialize" the shared memory buffer by explicitly calling Acquire(char *Name)
. I'm not sure why you'd want to do this, but it's there in case the need arises.
Finally, to access the data members of the structure, use the ->
operator, which has been overloaded to point to the shared memory our template class envelopes. An example shows how easy this is to do:
m_SharedData.x = 255;
strcpy(m_SharedData->text, "Hello Shared World!");
How does this work?
The CSharedStruct
class uses the memory-mapped file operations built in Windows. It's really a rather simple API call:
m_hFileMapping = CreateFileMapping (INVALID_HANDLE_VALUE, NULL,
PAGE_READWRITE, 0, sizeof(StructType), Name);
CreateFileMapping
returns a handle to a file mapping object and is usually used to map files on disk to user-mode addressable virtual addresses. However, if INVALID_HANDLE_VALUE
is passed as the file handle to map, the Operating System uses a portion of the page file to back-up the virtual address it returns. The next parameter, NULL
in this case, points to a Security Attributes object. If you are concerned about the security of the data you share, something besides NULL
should be passed in here. PAGE_READWRITE
grants us read/write access to the shared memory. Again, this can be changed if security is required and some clients only need read-only access to the data. The next two parameters are the size of the shared memory as a 64-bit number split into two 32-bit integers. Unless you're sharing more than 2GB of data, dwMaximumSizeHigh
will always be zero. Finally, we pass in the name of the Shared Memory object we want to access. If you're sharing data between a user mode application and a Windows NT/2000 service, make sure you prefix your name with "Global" if Terminal Services are installed, otherwise your shared structures will not point to the same data.
The Sample Application
The included sample uses CSharedStruct
to share a simple data structure. While the code presented above does not require MFC, the sample was built in MFC for simplicity. It's very straight forward, with a little code of interest other than the declaration of the CSharedStruct
object. OnSet()
and OnGet()
simply synchronize the shared memory and the user interface.
To test the application, you need to run two instances. I'd suggest running one normally (e.g. not debugging), then debug the second instance if you want to see how the code works. Enter in values for the Number and Text fields in one instance, and press 'Set', then switch to the other instance, and press 'Get'... That's about as exciting as the sample gets!
Updates, Errata
- Sept. 17th, 2001
Updated example and source to fix two bugs in the Release()
method which lead to potential resource leaks. Thanks to Stanley He for pointing this out.
- Sept. 18th, 2001
Updated example and source to support Service/User process interaction. Previously, if the service created the file-mapping object first, then user processes could not access it. The simple solution was to modify the ACL of the file-mapping object, giving everyone the access rights needed. More robust security concerns are left as an exercise for the reader. The demo application has been updated, and now includes a simple service (made with Joerg Koenig's CNTService class wizard).