Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Simple Wrapper for Sharing Data Structures Between Processes

0.00/5 (No votes)
17 Sep 2001 5  
A simple template class to create memory-mapped shared data structures.

Shared Memory in action!

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 // _MSC_VER > 1000


#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.

//m_SharedData1 and m_SharedData2 actually 'point' to the same data

CSharedStruct<MyData> m_SharedData1("Alpha");
CSharedStruct<MyData> m_SharedData2("Alpha");

//m_SharedData3, on the other hand, 'points' to a new block of shared memory

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).

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here