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!