Creating a shared memory segment in a C++ (Win32) ATL COM DLL (VC 6)

Environment: Visual C++ 6.0 (Win32)  (Show C++ .NET Example)

Sometimes you need to create a shared memory segment available to multiple processes.  This can be acomplished with an ActiveX Server, memory mapped file (or serveral other ways) but with the additional overhead that it implies.  The following example defines a .SHAREDMEMORY data segment that can be shared across multiple process on the same computer. The project is a VC6 ATL COM DLL that can be used in both .NET and Win32 applications (VB,C#,C++). A VS 2008 .NET example can be found here. This example is part of an ATL COM object but could as well be an OCX created in Visual C++ 6.0 and used by various applications that need to communicate with one another.  This is accomplished by setting the _Xml buffer from an access property (.XmlData in example), and retrieving the XML document from another application by calling the same access method. This example uses a wchar_t buffer to store data but you could define a BYTE buffer and access methods to store any kind of raw data.

Using the #pragma data_seg (".SHAREDMEMORY") you must make sure that the variables declared in the segment are initialized when they are declared.  During construction of the CSharedMem class the variables are set to whatever values you require to begin using the object.  The shared memory segment will only be initialized when when the first instance of the object is created because _Initialized will be set to true when the next application creates an instance of the object.

Using this class in the ATL COM object each application that uses the DLL can access the same memory address space just like in an old fashioned C style DLL.  This class can be used to pass an XML string between applications, for instance if a desktop application is used to communicate with MS Word using VBA.  The XML document is passed to Word document, VBA processes the document and passes the XML string back to the calling application.

As you will see sharing memory between different processes can be quite simple!
 
To download this project click here (VC 6 SharedMem32.zip) or you can download the compiled dll class library (32K buffer size) by clicking here (Compiled ATL COM DLL). To use the dll simply add a reference to your application and create a new instance of the SharedMem object.

First we add a C++ header file with the shared memory segment defined. The variables in the .SHAREDMORY segment need to be initialized when they are declared, and the XML buffer is defined at 32K + 1 byte. To increase the size of the shared memory segment all you need to do is set the XML_BUFFER_SIZE definition to a different value before you compile the dll. The linker will reserve this block of memory for all processes that use this dll. Regardless of the type of project you create you should be able to define a shared memory segment by only adding the code in the SharedStorage.h file.
// SharedStorage.h

#ifndef __SHAREDSTORAGE_H_
#define __SHAREDSTORAGE_H_

/* Define the shared memory segment **************************************************/
//define the largest required document size, memory must be defined and initialized
//when declared
#define XML_BUFFER_SIZE 32769	

#pragma data_seg (".SHAREDMEMORY")
    long    _DataLength = 0;
    bool    _Locked = false;
    bool    _Initialized = false;	
    wchar_t _Xml[XML_BUFFER_SIZE] = {0};
#pragma data_seg() 

#pragma comment(linker,"/SECTION:.SHAREDMEMORY,RWS")
/*************************************************************************************/

#endif // __SHAREDSTORAGE_H_
Next you will find the header file that defines our ATL COM interface and the bulk of all code in the project. This is just a simple COM object that lets our COM DLL offer an interface to the outside world.  When the first process references this dll it will initialize the shared memory segment in it's constuctor.


// SharedMem.h : Declaration of the CSharedMem Class
// Code provided as is with no warranty or support provided

#ifndef __SHAREDMEM_H_
#define __SHAREDMEM_H_

#include "resource.h"       // main symbols

/////////////////////////////////////////////////////////////////////////////
// CSharedMem
class ATL_NO_VTABLE CSharedMem : 
	public CComObjectRootEx<CComSingleThreadModel>,
	public CComCoClass<CSharedMem, &CLSID_SharedMem>,
	public IDispatchImpl<ISharedMem, &IID_ISharedMem, &LIBID_SHAREDMEM32Lib>
{
public:

	CSharedMem();

DECLARE_REGISTRY_RESOURCEID(IDR_SHAREDMEM)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CSharedMem)
	COM_INTERFACE_ENTRY(ISharedMem)
	COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

// ISharedMem
public:
	STDMETHOD(get_ClearOnGet)(/*[out, retval]*/ BOOL *pVal);
	STDMETHOD(put_ClearOnGet)(/*[in]*/ BOOL newVal);
	STDMETHOD(ClearXml)();
	STDMETHOD(get_DataLength)(/*[out, retval]*/ long *pVal);
	STDMETHOD(get_LockTimeout)(/*[out, retval]*/ long *pVal);
	STDMETHOD(put_LockTimeout)(/*[in]*/ long newVal);
	STDMETHOD(get_BufferSize)(/*[out, retval]*/ long *pVal);
	STDMETHOD(get_XmlData)(/*[out, retval]*/ BSTR *pVal);
	STDMETHOD(put_XmlData)(/*[in]*/ BSTR newVal);

private:

	bool LockMemory();
	long m_LockDelay;
	bool m_ClearOnGet;
};

#endif //__SHAREDMEM_H_
The following SharedMem class implementation provides the code to allow multiple processes on the same box to share a common block of memory. The property .XmlData with it's get and put implementation set and get the data from the buffer. In this program I've defined the buffer as a wchar_t and put and get text from the COM object. There is also a rudimentary locking mechanism to try to ensure that only one process reads and writes data at the same time. The property .LockTimeout sets the number of milleseconds that a process will wait before reading or writing data. If the process can't get a lock in that time period the call will resume anyway.

Type Method/Property Description
Property .XmlData Gets or sets data to the shared memory segment. (Text)
Property .LockTimeout Gets or sets timeout in milliseconds to wait for a lock on the memory segment
Property .BufferSize Gets the size of the shared memory segment as defined in SharedStorage.h
Property .ClearOnGet Gets or sets the property, if true buffer is cleared from shared segment on .XmlData get
Property .DataLength Gets the number of bytes waiting in buffer
Method .ClearXml() Clears shared buffer


// SharedMem.cpp : Implementation of CSharedMem
// Code provided as is with no warranty or support

#include "stdafx.h"
#include "SharedMem32.h"
#include "SharedMem.h"
#include "SharedStorage.h"


/////////////////////////////////////////////////////////////////////////////
// CSharedMem

CSharedMem::CSharedMem()
{
	//first consumer of this class initializes the shared memory for all
	if(!_Initialized)
	{
		_Xml[0] = L'\0';
		_Initialized = true;
		_DataLength = 0;
		_Locked = false;
	}

	m_LockDelay = 3000;
	m_ClearOnGet = false;
}

/**************************************************************************
* XmlData    GET Returns data in shared memory segment
*					
* RETURNS    [BSTR String] data currently in buffer
**************************************************************************/
STDMETHODIMP CSharedMem::get_XmlData(BSTR *pVal)
{
	//get a passive lock on the shared memory segment
	LockMemory();

	//set document to CComBSTR
	CComBSTR data = _Xml;
	
	//set value of property for this request
	*pVal = data.Copy();

	//clear xml buffer after get if true
	if(m_ClearOnGet)
	{
		_Xml[0] = L'\0';
		_DataLength = 0;
	}

	_Locked = false;

	return S_OK;
}

/**************************************************************************
* XmlData    PUT Sets data to shared memory segment
*
* PARAMS     [BSTR string] data to set to shared memory segment
**************************************************************************/
STDMETHODIMP CSharedMem::put_XmlData(BSTR newVal)
{
	//get a passive lock on the shared memory segment
	LockMemory();

	//clear out the shared buffer
	_Xml[0] = L'\0';
	
	_DataLength = SysStringLen(newVal);
	
	//make sure data is smaller than buffer, if not just leave clear buffer
	if(_DataLength < XML_BUFFER_SIZE)
	{
		//copy the data into shared memory segment element for element
		//to avoid memory problems from ATL macros
		for(long i = 0; i < _DataLength; i++)
		{
			_Xml[i] = (wchar_t)newVal[i];
		}

		_Xml[_DataLength] = L'\0';
	}
	
	_Locked = false;

	return S_OK;
}

/**************************************************************************
* LockMemory()	Attempts to lock the shared memory segment for this thread.
*					
* RETURNS		[bool] true if successful
**************************************************************************/
bool CSharedMem::LockMemory()
{
	//try to lock the memory segment with specified ms delay,
	//if we can't lock it we return false
	for(int trys = 0; trys < m_LockDelay; trys++)
	{
		if(!_Locked)
		{
			_Locked = true;
			return true;
		}
		Sleep(1); //wait 1ms between attempts
	}
	
	return false;
}

/**************************************************************************
* BufferSize()	Returns size of defined shared memory segment
*					
* RETURNS		[long] maximum buffer size
**************************************************************************/
STDMETHODIMP CSharedMem::get_BufferSize(long *pVal)
{
	*pVal = (long)XML_BUFFER_SIZE - 1;

	return S_OK;
}

/**************************************************************************
* DataLength    GET Returns size in bytes of last put_XmlData, this is
*               the number of bytes of data with get_XmlData.
*					
* RETURNS       [long] number of bytes in buffer to be read
**************************************************************************/
STDMETHODIMP CSharedMem::get_DataLength(long *pVal)
{
	*pVal = (long)_DataLength;

	return S_OK;
}

/**************************************************************************
* LockTimeout   GET Returns the delay in ms thread will wait to obtain lock
*					
* RETURNS       [long] millisecond delay to wait for lock
**************************************************************************/
STDMETHODIMP CSharedMem::get_LockTimeout(long *pVal)
{
	*pVal = (long)m_LockDelay;

	return S_OK;
}

/**************************************************************************
* LockTimeout    PUT Sets the timout delay when obtaining lock to shared
*                memory segment for this thread.  Min value for delay
*                is 100 ms;
*
* PARAMS         [long] newVal, sets delay in milliseconds to wait
**************************************************************************/
STDMETHODIMP CSharedMem::put_LockTimeout(long newVal)
{
	m_LockDelay = (long)(newVal < 100 ? 100 : newVal);

	return S_OK;
}

/**************************************************************************
* ClearXml()    Clears Xml Buffer for all consumers
*					
**************************************************************************/
STDMETHODIMP CSharedMem::ClearXml()
{
	LockMemory();

	_Xml[0] = L'\0';
	_DataLength = 0;

	_Locked = false;

	return S_OK;
}

/**************************************************************************
* ClearOnGet    GET Returns bool value indicating whether buffer clears
*               on each get_XmlData
*
* RETURNS       [bool] indicates if buffer clears on each get_XmlData					
**************************************************************************/
STDMETHODIMP CSharedMem::get_ClearOnGet(BOOL *pVal)
{
	*pVal = (bool)m_ClearOnGet;

	return S_OK;
}

/**************************************************************************
* ClearOnGet    PUT Sets flag to clear _Xml buffer on each get_XmlData
*					
**************************************************************************/
STDMETHODIMP CSharedMem::put_ClearOnGet(BOOL newVal)
{
	m_ClearOnGet = (bool)(newVal ? true : false);

	return S_OK;
}


To download this project click here (VC 6 SharedMem32.zip) or you can download the compiled dll class library (32K buffer size) by clicking here (Compiled ATL COM DLL). To use the dll simply add a reference to your application and create a new instance of the SharedMem object.

I hope you find this to be helpful!