#if !defined(_FUSION_INC_FUSIONCOM_H_INCLUDED_) #define _FUSION_INC_FUSIONCOM_H_INCLUDED_ #pragma once #include #include #include #include "smartref.h" // // The basic design for this COM support is lifted from ATL. // // ATL's implementation is much more complicated because it tries to // support all the sorts of COM objects there may be. Perhaps over // time this COM base stuff will extend as much and we may wish we // just used ATL, but for now people feel that ATL is overkill, so // rather than implement IUnknown::AddRef() and IUnknown::Release() // a thousand times, we'll at least have enough to have a single // common implementation. // // Turn off the warning about using a typedef name as if it were a class name. // We tend to do that a lot with CFusionCOMObjectBase (which is really just // a typedef for a much hairier name...) #pragma warning(disable: 4097) // use of typedef as class name #pragma warning(disable: 4505) // dead code warning // UNUSED macro with local unique name: #define FUSION_FUSIONCOM_UNUSED(x) (x) #define FUSION_COM_PACKING 8 #pragma pack(push, FUSION_COM_PACKING) #define offsetofclass(base, derived) (reinterpret_cast(static_cast(reinterpret_cast(FUSION_COM_PACKING)))-FUSION_COM_PACKING) struct CFusionCOMInterfaceMapEntry { const IID *piid; DWORD_PTR dwOffset; enum { eSimpleMapEntry, eDelegate, eEndOfMap, } eType; }; class CFusionCOMCriticalSectionSynchronization { public: CFusionCOMCriticalSectionSynchronization() : m_fInitialized(false) { } ~CFusionCOMCriticalSectionSynchronization() { m_fInitialized = false; } static ULONG Increment(ULONG &rul) { return ::InterlockedIncrement((LONG *) &rul); } static ULONG Decrement(ULONG &rul) { return ::InterlockedDecrement((LONG *) &rul); } HRESULT Initialize(DWORD dwSpinCount = (0x80000000 | 4000)) { HRESULT hr = NOERROR; ASSERT(!m_fInitialized); if (m_fInitialized) { hr = E_UNEXPECTED; goto Exit; } if (!::InitializeCriticalSectionAndSpinCount(&m_cs, dwSpinCount)) { hr = HRESULT_FROM_WIN32(::GetLastError()); goto Exit; } m_fInitialized = true; hr = NOERROR; Exit: return hr; } HRESULT Lock() { HRESULT hr = NOERROR; ASSERT(m_fInitialized); if (!m_fInitialized) { hr = E_UNEXPECTED; goto Exit; } ::EnterCriticalSection(&m_cs); hr = NOERROR; Exit: return hr; } HRESULT Unlock() { HRESULT hr = NOERROR; ASSERT(m_fInitialized); if (!m_fInitialized) { hr = E_UNEXPECTED; goto Exit; } ::LeaveCriticalSection(&m_cs); hr = NOERROR; Exit: return hr; } HRESULT TryLock(bool &rfLocked) { HRESULT hr = NOERROR; ASSERT(m_fInitialized); if (!m_fInitialized) { hr = E_UNEXPECTED; goto Exit; } rfLocked = (::TryEnterCriticalSection(&m_cs) != 0); hr = NOERROR; Exit: return hr; } private: CRITICAL_SECTION m_cs; bool m_fInitialized; }; class CFusionCOMNullSynchronization { public: CFusionCOMNullSynchronization() : m_fInitialized(false) { } ~CFusionCOMNullSynchronization() { m_fInitialized = false; } static ULONG Increment(ULONG &rul) { return ++rul; } static ULONG Decrement(ULONG &rul) { return --rul; } HRESULT Initialize() { HRESULT hr = NOERROR; ASSERT(!m_fInitialized); if (m_fInitialized) { hr = E_UNEXPECTED; goto Exit; } m_fInitialized = true; hr = NOERROR; Exit: return hr; } HRESULT Lock() { HRESULT hr = NOERROR; ASSERT(m_fInitialized); if (!m_fInitialized) { hr = E_UNEXPECTED; goto Exit; } hr = NOERROR; Exit: return hr; } HRESULT Unlock() { HRESULT hr = NOERROR; ASSERT(m_fInitialized); if (!m_fInitialized) { hr = E_UNEXPECTED; goto Exit; } hr = NOERROR; Exit: return hr; } HRESULT TryLock(bool &rfLocked) { HRESULT hr = NOERROR; ASSERT(m_fInitialized); if (!m_fInitialized) { hr = E_UNEXPECTED; goto Exit; } rfLocked = true; hr = NOERROR; Exit: return hr; } private: bool m_fInitialized; }; template class __declspec(novtable) CFusionCOMObjectBaseEx : public IUnknown { protected: CFusionCOMObjectBaseEx() : m_cRef(0), m_fInFinalRelease(false), m_fFusionCOMObjectBaseExInitialized(false) { } virtual ~CFusionCOMObjectBaseEx() { ASSERT(m_cRef == 0); } ULONG _InternalAddRef() { // You really shouldn't be causing new references on this object after its ref // count has dropped to zero... ASSERT(!m_fInFinalRelease); // You shouldn't be adding refs to this object prior to calling the base class's // Initialize() function. ASSERT(m_fFusionCOMObjectBaseExInitialized); return TSynch::Increment(m_cRef); } ULONG _InternalRelease() { // You really shouldn't be causing new references on this object after its ref // count has dropped to zero... ASSERT(!m_fInFinalRelease); // You shouldn't be adding (or removing) refs to this object prior to calling the base class's // Initialize() function. ASSERT(m_fFusionCOMObjectBaseExInitialized); return TSynch::Decrement(m_cRef); } VOID OnFinalRelease() { m_srpIUnknown_FTM.Release(); } // Derived classes must call this in order for the critical section/mutex/whatever // to be initialized. HRESULT Initialize(bool fCreateFTM = true) { HRESULT hr = NOERROR; ASSERT(!m_fFusionCOMObjectBaseExInitialized); if (m_fFusionCOMObjectBaseExInitialized) { hr = E_UNEXPECTED; goto Exit; } hr = m_SynchronizationObject.Initialize(); if (FAILED(hr)) goto Exit; if (fCreateFTM) { hr = ::CoCreateFreeThreadedMarshaler(this, &m_srpIUnknown_FTM); if (FAILED(hr)) goto Exit; } m_fFusionCOMObjectBaseExInitialized = true; hr = NOERROR; Exit: return hr; } static HRESULT WINAPI InternalQueryInterface( void *pThis, const CFusionCOMInterfaceMapEntry* pEntries, REFIID riid, void **ppvObject) { HRESULT hr = NOERROR; IUnknown *pIUnknown = NULL; ASSERT(pThis != NULL); if (ppvObject != NULL) *ppvObject = NULL; if (ppvObject == NULL) { hr = E_POINTER; goto Exit; } while (pEntries->eType != CFusionCOMInterfaceMapEntry::eEndOfMap) { if ((pEntries->piid == NULL) || ((*(pEntries->piid)) == riid)) { switch (pEntries->eType) { default: ASSERT(FALSE); hr = E_UNEXPECTED; goto Exit; case CFusionCOMInterfaceMapEntry::eDelegate: pIUnknown = *((IUnknown **) (((DWORD_PTR) pThis) + pEntries->dwOffset)); if (pIUnknown != NULL) { hr = pIUnknown->QueryInterface(riid, ppvObject); if (FAILED(hr)) goto Exit; } break; case CFusionCOMInterfaceMapEntry::eSimpleMapEntry: ASSERT(pEntries->eType == CFusionCOMInterfaceMapEntry::eSimpleMapEntry); pIUnknown = (IUnknown *) (((DWORD_PTR) pThis) + pEntries->dwOffset); pIUnknown->AddRef(); *ppvObject = pIUnknown; break; } // If we did find a match, get out of here. (This may have been // a delegation entry that matched, but there was no delegatee in // the storage at pEntries->dwOffset, so we skipped it. This allows // a derived class to not create, for example, a free threaded marshaler // and have normal marshaling semantics. if (pIUnknown != NULL) break; } pEntries++; } if (pEntries->eType == CFusionCOMInterfaceMapEntry::eEndOfMap) { hr = E_NOINTERFACE; goto Exit; } hr = NOERROR; Exit: return hr; } ULONG m_cRef; TSynch m_SynchronizationObject; CSmartRef m_srpIUnknown_FTM; bool m_fInFinalRelease; bool m_fFusionCOMObjectBaseExInitialized; }; typedef CFusionCOMObjectBaseEx CFusionCOMObjectBase; // Putting the entry macros before the begin/end map macros so that // we can use the entry macros in the begin/end map macros to // automatically add entries for IUnknown and IMarshal. #define FUSION_COM_INTERFACE_ENTRY(x) { &__uuidof(x), offsetofclass(x, _ComMapClass), CFusionCOMInterfaceMapEntry::eSimpleMapEntry }, #define FUSION_COM_INTERFACE_ENTRY2(x, x2) { &__uuidof(x), (DWORD_PTR)((x*)(x2*)((_ComMapClass*)8))-8, CFusionCOMInterfaceMapEntry::eSimpleMapEntry }, #define FUSION_COM_INTERFACE_ENTRY_IID(iid, x) { &(iid), offsetofclass(x, _ComMapClass), CFusionCOMInterfaceMapEntry::eSimpleMapEntry }, #define FUSION_COM_INTERFACE_ENTRY_AGGREGATE(x, punk) \ {&__uuidof(x),\ offsetof(_ComMapClass, punk),\ CFusionCOMInterfaceMapEntry::eDelegate }, #define FUSION_BEGIN_COM_MAP_EX(x, basetype) \ public: \ virtual ULONG STDMETHODCALLTYPE AddRef(void) = 0; \ virtual ULONG STDMETHODCALLTYPE Release(void) = 0; \ virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID *) = 0; \ typedef x _ComMapClass; \ HRESULT _InternalQueryInterface(REFIID riid, void** ppvObject) \ { \ return InternalQueryInterface(this, _GetEntries(), riid, ppvObject); \ } \ const static CFusionCOMInterfaceMapEntry * WINAPI _GetEntries() \ { \ static const CFusionCOMInterfaceMapEntry _entries[] = { \ FUSION_COM_INTERFACE_ENTRY2(IUnknown, basetype) #define FUSION_BEGIN_COM_MAP(x) FUSION_BEGIN_COM_MAP_EX(x, CFusionCOMObjectBase) #define FUSION_END_COM_MAP() \ FUSION_COM_INTERFACE_ENTRY_AGGREGATE(IMarshal, m_srpIUnknown_FTM.m_pt) \ { NULL, 0, CFusionCOMInterfaceMapEntry::eEndOfMap} \ }; \ return _entries; \ } template class CFusionCOMObject : public TBase { public: CFusionCOMObject() { } virtual ~CFusionCOMObject() { } STDMETHODIMP_(ULONG) AddRef() { return this->_InternalAddRef(); } STDMETHODIMP_(ULONG) Release() { ULONG cRef = this->_InternalRelease(); // If the ref count hits zero, but we were already in final release, // we know that the outer Release() will actually do the delete, so // we don't have to. if ((cRef == 0) && !m_fInFinalRelease) { m_fInFinalRelease = true; this->OnFinalRelease(); // Make sure that the OnFinalRelease() didn't do something wacky // like increment the ref count... ASSERT(m_fInFinalRelease); ASSERT(m_cRef == 0); m_fInFinalRelease = false; delete this; } return cRef; } STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppv) { return this->_InternalQueryInterface(riid, ppv); } }; // // Use CFusionAutoCOMObject<> to instantiate a CFusionCOMObject-derived instance // in automatic (e.g. stack) storage. // template class CFusionAutoCOMObject : public TBase { public: CFusionAutoCOMObject() { } STDMETHODIMP_(ULONG) AddRef() { return this->_InternalAddRef(); } STDMETHODIMP_(ULONG) Release() { return this->_InternalRelease(); } STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppv) { return this->_InternalQueryInterface(riid, ppv); } typedef BOOL (CALLBACK * PUMPMESSAGESPROC)(LPVOID pvContext); BOOL WaitForZeroRefCount(DWORD dwMilliseconds, DWORD dwWakeMask, DWORD dwFlags, PUMPMESSAGESPROC lpMsgPump, LPVOID pvContext) { FUSION_FUSIONCOM_UNUSED(dwMilliseconds); // Anything other than dwMilliseconds == INFINITE is not implemented today... ASSERT(dwMilliseconds == INFINITE); ASSERT(dwFlags & MWMO_ALERTABLE); dwFlags |= MWMO_ALERTABLE; while (m_cRef != 0) { DWORD dwTemp = ::MsgWaitForMultipleObjectsEx(0, NULL, INFINITE, dwWakeMask, dwFlags); if (dwTemp == -1) return FALSE; if (m_cRef == 0) break; if (dwTemp == WAIT_OBJECT_0) { BOOL fSucceeded = (*lpMsgPump)(pvContext); if (!fSucceeded) return FALSE; } } return TRUE; } // Use WaitForZeroRefCount() prior to this virtual ~CFusionAutoCOMObject() { ASSERT(!m_fInFinalRelease); if (!m_fInFinalRelease) { m_fInFinalRelease = true; this->OnFinalRelease(); m_fInFinalRelease = false; } ASSERT(m_cRef == 0); } }; template class CFusionCOMIter { public: CFusionCOMIter() { } ~CFusionCOMIter() { } CFusionCOMIter(TEnumInterface *pTEnumInterface) { m_srpTEnumInterface = pTEnumInterface; } void operator =(TEnumInterface *pTEnumInterface) { m_srpTEnumInterface = pTEnumInterface; m_srpTItemInterface.Release(); } TEnumInterface **operator &() { return &m_srpTEnumInterface; } TItemInterface *operator ->() const { return m_srpTItemInterface; } operator TItemInterface *() const { return m_srpTItemInterface; } void Release() { m_srpTEnumInterface.Release(); m_srpTItemInterface.Release(); } bool EnumIsNull() const { return m_srpTEnumInterface == NULL; } HRESULT Reset() { HRESULT hr = NOERROR; if (m_srpTEnumInterface == NULL) { hr = E_UNEXPECTED; goto Exit; } hr = m_srpTEnumInterface->Reset(); if (FAILED(hr)) goto Exit; hr = m_srpTEnumInterface->Next(1, &m_srpTItemInterface, NULL); if (FAILED(hr)) goto Exit; hr = NOERROR; Exit: return hr; } bool More() const { return (m_srpTItemInterface != NULL); } HRESULT Next() { HRESULT hr = NOERROR; if (m_srpTEnumInterface == NULL) { hr = E_UNEXPECTED; goto Exit; } hr = m_srpTEnumInterface->Next(1, &m_srpTItemInterface, NULL); if (FAILED(hr)) goto Exit; hr = NOERROR; Exit: return hr; } protected: CSmartRef m_srpTEnumInterface; CSmartRef m_srpTItemInterface; }; template class CFusionCOMObjectCreator : public CSmartRef { public: CFusionCOMObjectCreator() : m_ptinstance(NULL), m_fDeleteInstance(false) { } ~CFusionCOMObjectCreator() { if (m_fDeleteInstance) { CSxsPreserveLastError ple; delete m_ptinstance; ple.Restore(); } } HRESULT CreateInstance() { if (m_ptinstance != NULL) return E_UNEXPECTED; m_ptinstance = NEW(CFusionCOMObject); if (m_ptinstance == NULL) return E_OUTOFMEMORY; m_fDeleteInstance = true; HRESULT hr = m_ptinstance->Initialize(); if (FAILED(hr)) return hr; hr = m_ptinstance->QueryInterface(__uuidof(TInterface), (LPVOID *) &m_pt); if (FAILED(hr)) return hr; m_fDeleteInstance = false; return NOERROR; } template HRESULT CreateInstanceWithArg(TArg1 arg1) { if (m_ptinstance != NULL) return E_UNEXPECTED; m_ptinstance = NEW(CFusionCOMObject); if (m_ptinstance == NULL) return E_OUTOFMEMORY; m_fDeleteInstance = true; HRESULT hr = m_ptinstance->Initialize(arg1); if (FAILED(hr)) return hr; hr = m_ptinstance->QueryInterface(__uuidof(TInterface), (LPVOID *) &m_pt); if (FAILED(hr)) return hr; m_fDeleteInstance = false; return NOERROR; } template HRESULT CreateInstanceWith2Args(TArg1 arg1, TArg2 arg2) { if (m_ptinstance != NULL) return E_UNEXPECTED; m_ptinstance = NEW(CFusionCOMObject); if (m_ptinstance == NULL) return E_OUTOFMEMORY; m_fDeleteInstance = true; HRESULT hr = m_ptinstance->Initialize(arg1, arg2); if (FAILED(hr)) return hr; hr = m_ptinstance->QueryInterface(__uuidof(TInterface), (LPVOID *) &m_pt); if (FAILED(hr)) return hr; m_fDeleteInstance = false; return NOERROR; } template HRESULT CreateInstanceWith3Args(TArg1 arg1, TArg2 arg2, TArg3 arg3) { if (m_ptinstance != NULL) return E_UNEXPECTED; m_ptinstance = NEW(CFusionCOMObject); if (m_ptinstance == NULL) return E_OUTOFMEMORY; m_fDeleteInstance = true; HRESULT hr = m_ptinstance->Initialize(arg1, arg2, arg3); if (FAILED(hr)) return hr; hr = m_ptinstance->QueryInterface(__uuidof(TInterface), (LPVOID *) &m_pt); if (FAILED(hr)) return hr; m_fDeleteInstance = false; return NOERROR; } template HRESULT CreateInstanceWith4Args(TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4) { if (m_ptinstance != NULL) return E_UNEXPECTED; m_ptinstance = NEW(CFusionCOMObject); if (m_ptinstance == NULL) return E_OUTOFMEMORY; m_fDeleteInstance = true; HRESULT hr = m_ptinstance->Initialize(arg1, arg2, arg3, arg4); if (FAILED(hr)) return hr; hr = m_ptinstance->QueryInterface(__uuidof(TInterface), (LPVOID *) &m_pt); if (FAILED(hr)) return hr; m_fDeleteInstance = false; return NOERROR; } template HRESULT CreateInstanceWith5Args(TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, TArg5 arg5) { if (m_ptinstance != NULL) return E_UNEXPECTED; m_ptinstance = NEW(CFusionCOMObject); if (m_ptinstance == NULL) return E_OUTOFMEMORY; m_fDeleteInstance = true; HRESULT hr = m_ptinstance->Initialize(arg1, arg2, arg3, arg4, arg5); if (FAILED(hr)) return hr; hr = m_ptinstance->QueryInterface(__uuidof(TInterface), (LPVOID *) &m_pt); if (FAILED(hr)) return hr; m_fDeleteInstance = false; return NOERROR; } // Call this if you need to clear out objects explicitly e.g. before uninitializing COM. inline void Release() { CSxsPreserveLastError ple; if (m_fDeleteInstance) delete m_ptinstance; CSmartRef::Release(); m_fDeleteInstance = false; m_ptinstance = NULL; ple.Restore(); } inline TInstance *ObjectPtr() const { return m_ptinstance; } private: CFusionCOMObject *m_ptinstance; bool m_fDeleteInstance; }; #pragma pack(pop) #endif