Figure 2 IMarshal
interface IMarshal : IUnknown {
// what is the clsid of the handler ?
HRESULT GetUnmarshalClass(REFIID riid,
void *pvObject,
DWORD dwDestContext,
void *pvDestContext,
DWORD mshlflags,
CLSID *pClsid);
// how big does the marshaling packet have to be?
HRESULT GetMarshalSizeMax(REFIID riid,
void *pvObject,
DWORD dwDestContext,
void *pvDestContext,
DWORD mshlflags,
DWORD *pSize);
// fill the marshaling packet
HRESULT MarshalInterface( IStream *pStm,
REFIID riid,
void *pvObject,
DWORD dwDestContext,
void *pvDestContext,
DWORD mshlflags);
// initialize the handler from marshaling packet
HRESULT UnmarshalInterface(IStream *pStm,
REFIID riid,
void **ppvObject);
// The marshaling packet is no longer needed
HRESULT ReleaseMarshalData(IStream *pStm);
// the object called CoDisconnectObject to sever
// any outstanding connections
HRESULT DisconnectObject(DWORD dwReserved);
}
Figure 3 Using Packets and Handlers
HGLOBAL *CreatePacket(IUnknown *pUnkSource,
REFIID riidInitialInterface)
{
HGLOBAL result = 0;
// grab the IMarshal interface
IMarshal *pmsh;
pUnkSource->QueryInterface(IID_IMarshal,
(void**)&pmsh);
// allocate the memory for the packet and
// create an IStream interface to it
ULONG cbPacketSize;
pmsh->GetMarshalSizeMax(riidInitialInterface,
pUnkSource,
MSHCTX_LOCAL,
0,
MSHLFLAGS_NORMAL,
&cbPacketSize);
result = GlobalAlloc(GHND, cbPacketSize);
IStream *pstm = 0;
CreateStreamOnHGlobal(result, FALSE, &pstm);
// write out the handler's CLSID and any data that
// will be needed to connect it to the object
CLSID clsid;
pmsh->GetUnmarshalClass(riidInitialInterface,
pUnkSource,
MSHCTX_LOCAL,
0,
MSHLFLAGS_NORMAL,
&clsid);
WriteClassStm(pstm, clsid);
pmsh->MarshalInterface( pstm,
riidInitialInterface,
pUnkSource,
MSHCTX_LOCAL,
0,
MSHLFLAGS_NORMAL);
pstm->Release();
pmsh->Release();
return result;
}
void CreateHandler(HGLOBAL hgpacket,
REFIID riidInitialInterface,
void **ppvObj)
{
// create an IStream interface to the packet
IStream *pstm = 0;
CreateStreamOnHGlobal(hgpacket, FALSE, &pstm);
// read the CLSID for the handler and instantiate
CLSID clsid;
ReadClassStm(pstm, &clsid);
IMarshal *pmsh = 0;
CoCreateInstance(clsid, 0,
CLSCTX_INPROC_HANDLER,
IID_IMarshal, (void**)&pmsh);
// connect/initialize the handler
IStream *pstmClone;
pstm->Clone(&pstmClone); // keep our place
pmsh->UnmarshalInterface(pstm,
riidInitialInterface,
ppvObj);
// release the marshaling packet
pmsh->ReleaseMarshalData(pstmClone);
pstm->Release();
pstmClone->Release();
pmsh->Release();
GlobalFree(0, hgpacket);
}
Figure 6 Generic Shared Object
SHAREDOB.H
/////////////////////////////////////////////////////////////
//
// SharedObj.h - 1996, Don Box
//
#ifndef _SHAREDOBJ_H
#define _SHAREDOBJ_H
// we need the placement operator
#include <new.h>
// CoSharedObjectBase is a non-template base class that implements
// most of the custom marshaling code to share a memory section in
// a thread-safe fashion
class CoSharedObjectBase : private IMarshal {
// handle to mutex that protects the shared
// data members
HANDLE m_hmutex;
// handle to section object that contains
// shared data members
HANDLE m_hsection;
// pointer to shared data member(s)
void *m_pvData;
// unique GUID that "names" the 2 shared Win32
// kernel objects
GUID m_guid;
// normal COM refcount
ULONG m_cRef;
// helper function to attach to section object
BOOL AttachToSection(DWORD dwTimeOut = INFINITE);
struct SECTIONHEADER;
SECTIONHEADER *GetSectionHeader();
void *GetSectionData();
// Virtual upcalls that allow derived client to construct/destroy
// shared data members
virtual BOOL OnInitializeSection(void *pv) = 0;
virtual void OnDestroySection(void *pv) = 0;
// Virtual upcalls that are used to find the size of the shared
// data members and the CLSID of this class
virtual DWORD OnGetSize() = 0;
virtual const CLSID& OnGetCLSID() = 0;
// IMarshal members
STDMETHODIMP GetUnmarshalClass( REFIID riid,
void * pv,
DWORD dwDestCtx,
void * pvDestCtx,
DWORD mshlflags,
CLSID * pclsid);
STDMETHODIMP GetMarshalSizeMax( REFIID riid,
void * pv,
DWORD dwDestCtx,
void * pvDestCtx,
DWORD mshlflags,
ULONG *pcb);
STDMETHODIMP MarshalInterface( IStream *pStm,
REFIID riid,
void *pv,
DWORD dwDestCtx,
void *pvDestCtx,
DWORD mshlflags);
STDMETHODIMP UnmarshalInterface(IStream * pStm,
REFIID riid,
void ** ppv);
STDMETHODIMP ReleaseMarshalData(IStream * pStm);
STDMETHODIMP DisconnectObject(DWORD dwReserved);
protected:
// IUnknown members (protected to allow forwarding)
STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
// Lock/Unlock the shared data
void *AccessSharedData(DWORD dwTimeOut = INFINITE);
void ReleaseSharedData();
CoSharedObjectBase();
virtual ~CoSharedObjectBase();
};
// CoSharedObject is a template class that implements a handler
// that accesses a shared data structure (SharedState) and implements
// one COM interface (beyond IMarshal)
template < class SharedState, // the shared data
const CLSID *pclsid, // this CLSID
interface IUser, // Primary Interface
const IID* PIID_IUser // Primary IID
>
class CoSharedObject : private CoSharedObjectBase,
public IUser {
private:
// default implementation uses placement version
// of new to construct the object "in place"
virtual BOOL OnInitializeSection(void *pv) {
new (pv) SharedState;
return TRUE;
}
// default implementation calls destructor explicitly
// to destruct the object "in place"
virtual void OnDestroySection(void *pv) {
((SharedState*)pv)->~SharedState();
}
// default implementation returns the size of the
// data members without chasing pointer members
virtual DWORD OnGetSize() {
return sizeof(SharedState);
}
// simply return the template paramter
virtual const CLSID& OnGetCLSID() {
return *pclsid;
}
protected:
// SharedThis is a helper class that aquires the mutex in
// an exception-safe fashion. Declare an instance of SharedThis
// at the beginning of each member function of your derived class
// to access the shared data members of your object
class SharedThis {
CoSharedObject *m_phandler;
SharedState *m_pthis;
public:
SharedThis(CoSharedObject *pt, DWORD dwTimeOut = INFINITE)
: m_phandler(pt),
m_pthis(0)
{
m_pthis = (SharedState*)pt->AccessSharedData(dwTimeOut);
}
~SharedThis()
{
if (m_pthis)
m_phandler->ReleaseSharedData();
}
operator SharedState * () { return m_pthis; }
SharedState * operator -> () { return m_pthis; }
};
friend class SharedThis;
public:
// hook QI to expose the new interface
STDMETHODIMP QueryInterface(REFIID riid, void**ppv) {
if (riid == *PIID_IUser) {
LPUNKNOWN(*ppv = (IUser*)this)->AddRef();
return S_OK;
}
return CoSharedObjectBase::QueryInterface(riid, ppv);
}
// use default AddRef and Release
STDMETHODIMP_(ULONG) AddRef() {
return CoSharedObjectBase::AddRef();
}
STDMETHODIMP_(ULONG) Release() {
return CoSharedObjectBase::Release();
}
};
#endif
SHAREDOB.CPP
/////////////////////////////////////////////////////////////
//
// SharedObj.cpp - 1996, Don Box
//
#include <windows.h>
#include "sharedobj.h"
// standard module lifetime functions (defined elsewhere)
extern void SvcLock();
extern void SvcUnlock();
// header for shared memory section
struct CoSharedObjectBase::SECTIONHEADER {
// shared ref count
DWORD m_cRef;
// padding
DWORD m_reserved;
};
// the header resides at the beginning of the section
inline CoSharedObjectBase::SECTIONHEADER *
CoSharedObjectBase::GetSectionHeader() {
return (SECTIONHEADER*)m_pvData;
}
// the payload resides immediately after the header, so use
// the magic of pointer arithmetic to find its address
inline void *
CoSharedObjectBase::GetSectionData() {
return GetSectionHeader() + 1;
}
// initialize data members to null and lock the server module
CoSharedObjectBase::CoSharedObjectBase()
: m_hmutex(0), m_hsection(0), m_pvData(0), m_cRef(0)
{
SvcLock();
}
// close all handles and unlock the server module
CoSharedObjectBase::~CoSharedObjectBase() {
if (m_hsection) {
UnmapViewOfFile(m_pvData);
CloseHandle(m_hsection);
}
if (m_hmutex)
CloseHandle(m_hmutex);
SvcUnlock();
}
// helper function to create or open section object and mutex
BOOL
CoSharedObjectBase::AttachToSection(DWORD dwTimeOut)
{
// synthesize section and mutex name based on guid value,(e.g.,
// "{12345678-0000-0000-0000-DEAD00000000}_MTX" for the mutex
// "{12345678-0000-0000-0000-DEAD00000000}_SCT" and for the section
TCHAR szSectionName[128];
TCHAR szMutexName[128];
OLECHAR szGUID[128];
// m_guid acts as the object ID in this usage
StringFromGUID2(m_guid, szGUID, sizeof(szGUID));
#ifdef UNICODE
lstrcpy(szSectionName, szGUID);
#else
wcstombs(szSectionName, szGUID, sizeof(szSectionName));
#endif
lstrcpy(szMutexName, szSectionName);
lstrcat(szMutexName, TEXT("_MTX"));
lstrcat(szSectionName, TEXT("_SCT"));
// Create/open the mutex, ascertain "firstness" and grab the mutex
m_hmutex = CreateMutex(0, FALSE, szMutexName);
BOOL bFirst = GetLastError() != ERROR_ALREADY_EXISTS;
WaitForSingleObject(m_hmutex, dwTimeOut);
// create/open a section big enough to hold shared state and
// header, then map it into address space
m_hsection = CreateFileMapping(HANDLE(0xFFFFFFFF), 0, PAGE_READWRITE, 0,
sizeof(SECTIONHEADER) + OnGetSize(),
szSectionName);
m_pvData = MapViewOfFile(m_hsection, FILE_MAP_ALL_ACCESS, 0,0,0);
if (bFirst)
{
// set shared ref count to one and initialize payload of section via
// the virtual function OnInitializeSection
GetSectionHeader()->m_cRef = 1;
OnInitializeSection(GetSectionData());
}
else
{
// simply bump the shared ref count
GetSectionHeader()->m_cRef++;
}
ReleaseMutex(m_hmutex);
return TRUE;
}
// IUnknown members
STDMETHODIMP
CoSharedObjectBase::QueryInterface(REFIID riid, void **ppv)
{
if (riid == IID_IUnknown || riid == IID_IMarshal)
LPUNKNOWN(*ppv = (IMarshal*)this)->AddRef();
else
*ppv = 0;
return *ppv ? S_OK : E_NOINTERFACE;
}
STDMETHODIMP_(ULONG)
CoSharedObjectBase::AddRef()
{
return ++m_cRef;
}
STDMETHODIMP_(ULONG)
CoSharedObjectBase::Release()
{
ULONG result = --m_cRef;
if (result == 0)
{
// We must detach from section prior to destruction
// in order to get the appropriate virtual function
// called (we can't call OnDestroySection in our destructor)
WaitForSingleObject(m_hmutex, INFINITE);
if (--GetSectionHeader()->m_cRef == 0)
OnDestroySection(GetSectionData());
ReleaseMutex(m_hmutex);
delete this;
}
return result;
}
// IMarshal members
// GetUnmarshalClass uses the virtual upcall OnGetCLSID to
// ascertain the classname of the handler. Marshaling contexts
// that do not support shared memory simply fail
STDMETHODIMP
CoSharedObjectBase::GetUnmarshalClass( REFIID riid, void * pv, DWORD dwDestCtx,
void * pvDestCtx, DWORD mshlflags,
CLSID * pclsid)
{
SCODE result = E_FAIL;
if (dwDestCtx == MSHCTX_LOCAL ||
dwDestCtx == MSHCTX_INPROC)
{
*pclsid = OnGetCLSID();
result = S_OK;
}
return result;
}
// Since we only send the object ID (m_guid) in the marshaling
// packet, we hard code the size to sizeof(GUID)
STDMETHODIMP
CoSharedObjectBase::GetMarshalSizeMax( REFIID riid, void * pv, DWORD dwDestCtx,
void * pvDestCtx, DWORD mshlflags,
ULONG *pcb)
{
SCODE result = E_FAIL;
if (dwDestCtx == MSHCTX_LOCAL ||
dwDestCtx == MSHCTX_INPROC)
{
*pcb = sizeof(GUID);
result = S_OK;
}
return result;
}
// We transmit the object ID (m_guid) through the stream
// using WriteClassStm
STDMETHODIMP
CoSharedObjectBase::MarshalInterface( IStream *pStm, REFIID riid, void *pv,
DWORD dwDestCtx, void *pvDestCtx,
DWORD mshlflags)
{
SCODE result = E_FAIL;
if (dwDestCtx == MSHCTX_LOCAL ||
dwDestCtx == MSHCTX_INPROC)
{
WriteClassStm(pStm, m_guid);
result = S_OK;
}
return result;
}
// We receive the object ID (m_guid) from the stream
// using ReadClassStm and attach to the shared section
// that the object ID names
STDMETHODIMP
CoSharedObjectBase::UnmarshalInterface(IStream * pStm, REFIID riid, void ** ppv)
{
*ppv = 0;
ReadClassStm(pStm, &m_guid);
if (AttachToSection())
return this->QueryInterface(riid, ppv);
else
return E_FAIL;
}
// since we allocate no new resources in our marshaling code,
// there is no need to implement anything interesting
// in this function
STDMETHODIMP
CoSharedObjectBase::ReleaseMarshalData(IStream * pStm)
{
// seek past our portion of the marshaling packet
LARGE_INTEGER li;
LISet32(li, sizeof(m_guid));
pStm->Seek(li, STREAM_SEEK_CUR, 0);
return S_OK;
}
// For the simple case, we do not want the handler to disconnect
STDMETHODIMP
CoSharedObjectBase::DisconnectObject(DWORD dwReserved)
{
return S_OK;
}
// AccessSharedData allows derived clients to acquire the mutex. If the
// section has not been mapped, then this is the first access to the object
// and causes the object ID to be generated using CoCreateGuid
// This function and AttachToSection perform most of the work of this class.
void *
CoSharedObjectBase::AccessSharedData(DWORD dwTimeOut)
{
// test for first access to first handler
if (m_pvData == 0)
{
CoCreateGuid(&m_guid);
if (!AttachToSection(dwTimeOut))
return 0;
}
if (WaitForSingleObject(m_hmutex, dwTimeOut) == WAIT_OBJECT_0)
return GetSectionData();
else
return 0;
}
// simply release the mutex
void
CoSharedObjectBase::ReleaseSharedData()
{
ReleaseMutex(m_hmutex);
}
Figure 8 CoSharedObject Demo
SHAREDPO.H
/////////////////////////////////////////////////////////////
//
// SharedPoint.h - 1996 Don Box
//
#ifndef _SHAREDPOINT_H
#define _SHAREDPOINT_H
#include "SharedObj.h"
#include "IPoint.h"
// shared data members
class PointRep {
friend class CoSharedPoint;
long m_x;
long m_y;
char m_sz[1024];
public:
PointRep() : m_x(100), m_y(200) { lstrcpyA(m_sz, "Some Other Model?"); }
~PointRep() { OutputDebugString("I am done!\n"); }
};
// handler class that is instantiated in each process
class CoSharedPoint
: public CoSharedObject<PointRep, // Shared state
&CLSID_CoSharedPoint, // this CLSID
IPointAndText, // Primary interface
&IID_IPointAndText> // Primary IID
{
public:
// IPointAndText members
STDMETHODIMP Set(long x, long y);
STDMETHODIMP Get(long FAR* px, long FAR* py);
STDMETHODIMP SetText(LPCSTR lpsz);
STDMETHODIMP GetText(LPSTR lpsz, int cb);
};
#endif
SHAREDPO.CPP
/////////////////////////////////////////////////////////////
//
// SharedPoint.cpp - 1996, Don Box
//
#include <windows.h>
#include "sharedpoint.h"
STDMETHODIMP
CoSharedPoint::Set(long x, long y)
{
SharedThis pThis(this); // initialize locked pointer to shared state
pThis->m_x = x; // copy parameters to shared state
pThis->m_y = y;
return NOERROR;
}
STDMETHODIMP
CoSharedPoint::Get(long FAR* px, long FAR* py)
{
SharedThis pThis(this); // initialize locked pointer to shared state
*px = pThis->m_x; // copy parameters from shared state
*py = pThis->m_y;
return NOERROR;
}
STDMETHODIMP
CoSharedPoint::SetText(LPCSTR lpsz)
{
SharedThis pThis(this);
if (strncpy(pThis->m_sz, lpsz, sizeof(pThis->m_sz)))
return S_OK;
else
return E_INVALIDARG;
}
STDMETHODIMP
CoSharedPoint::GetText(LPSTR lpsz, int cb)
{
SharedThis pThis(this);
if (strncpy(lpsz, pThis->m_sz, cb))
return S_OK;
else
return E_INVALIDARG;
}