COM原理与实现之二: 聚合
C++没有同聚合等价的特性。聚合实际上是继承性的一种动态形式。而C++的继承总是静态的,是实现继承。COM是接口继承,通过聚合接口,可以做成动态配置。
研究COM,主要是利用接口继承的灵活性构筑强大的系统:可配置、可插拔、可脚本化。本文不讲太多理论,详细原理参考[COM技术内幕]这本书。关于[COM技术内幕],很多内容过时了,比如注册表,类厂之类的。我更关心COM思想所蕴含的哲学。我实现了跨平台COM,支持聚合。
GameStencil这个组件聚合了SystemMngmt这个组件。最初采用IGameStencil::getSystemMngmt()这样的形式返回一个ISystemMngmt接口指针,显然这不是聚合。通过A对象的方法得到B对象,显然A仅仅是B的一种类厂。于是我下决心解决组件聚合问题。就有了本文。
core目录下面就3个文件,是我的跨平台COM的基础。
1) Platform.h
/** * Platform.h * * * Init Created: 2016-06-10 * Last Updated: 2016-06-10 */ #ifndef PLATFORM_H #define PLATFORM_H #if defined _MSC_VER || WIN32 #ifndef OS_PLATFORM_WIN #define OS_PLATFORM_WIN #endif #endif #ifdef OS_PLATFORM_WIN #include <windows.h> #include <process.h> #else #include <pthread.h> #include <unistd.h> #endif #ifndef interface #define interface struct #endif /** * interface iid */ typedef unsigned int iid_t; /** * long result */ typedef long lresult_t; #define lres_success 0 #define lres_error (-1) #define lres_e_initdata (-2) #define lres_e_outmemory (-4) #define lres_e_nointerface (-11) #define lres_e_noaggregation (-12) /** * thread_ctx */ #define thread_ctx_single 0 #define thread_ctx_multiple 1 /** * ref count type */ #ifdef OS_PLATFORM_WIN typedef volatile unsigned long refcount_t; #define __interlock_inc(add) InterlockedIncrement(add) #define __interlock_dec(sub) InterlockedDecrement(sub) #else typedef volatile size_t refcount_t; #define __interlock_inc(add) __sync_add_and_fetch(add,1) #define __interlock_dec(sub) __sync_sub_and_fetch(sub,1) #endif #endif /* PLATFORM_H */
2) Universal.h
/** * Universal.h * * Refer: * <<Inside COM>> * * Init Created: 2016-06-10 * Last Updated: 2016-06-13 */ #ifndef UNIVERSAL_H #define UNIVERSAL_H #include "Platform.h" // IUniversal is a variant from IUnknown // interface IUniversal { static const iid_t IID = ((iid_t) (0)); virtual lresult_t query(iid_t iid,void **ppvOut) = 0; virtual unsigned long retain(void) = 0; virtual unsigned long release(void) = 0; }; #define iid_IUniversal (IUniversal::IID) // Nondelegating IUniversal interface // - Nondelegating version of IUniversal // interface INondelegatingUniversal { static const iid_t IID = ((iid_t) (-1)); virtual lresult_t NondelegatingQuery(iid_t iid,void **ppvOut) = 0; virtual unsigned long NondelegatingRetain(void) = 0; virtual unsigned long NondelegatingRelease(void) = 0; }; #define iid_INondelegatingUniversal (INondelegatingUniversal::IID) class UniversalImpl { private: unsigned thread_ctx; refcount_t ref_count; public: UniversalImpl() : ref_count(1),thread_ctx(thread_ctx_multiple) { printf("UniversalImpl\n"); } virtual ~UniversalImpl() { printf("~UniversalImpl\n"); } void init(unsigned threadctx) { thread_ctx = threadctx; } // Notification to derived classes that we are releasing void finalRelease() { // Increment reference count for final release ref_count = 1; } unsigned getThreadCtx() const { return thread_ctx; } // IUniversal // virtual lresult_t query(iid_t iid,void **ppv) = 0; virtual unsigned long retain(void) { if (thread_ctx == thread_ctx_multiple) { return __interlock_inc(&ref_count); } else { return ++ref_count; } } virtual unsigned long release(void) { if (thread_ctx == thread_ctx_multiple) { if (__interlock_dec(&ref_count) == 0) { delete this; return 0; } } else { if (--ref_count == 0) { delete this; return 0; } } return ref_count; } }; // Declaration of NondelegatingUniversalImpl // - Base class for implementing INondelegatingUniversal // class NondelegatingUniversalImpl : public INondelegatingUniversal { public: virtual lresult_t NondelegatingQuery(iid_t iid,void **ppv) = 0; virtual unsigned long NondelegatingRetain(void) { if (thread_ctx == thread_ctx_multiple) { return __interlock_inc(&ref_count); } else { return ++ref_count; } } virtual unsigned long NondelegatingRelease(void) { if (thread_ctx == thread_ctx_multiple) { if (__interlock_dec(&ref_count) == 0) { delete this; return 0; } } else { if (--ref_count == 0) { delete this; return 0; } } return ref_count; } // Constructor NondelegatingUniversalImpl(IUniversal* pUniversalOuter) : ref_count(1) { // Set outer_universal pointer if (! pUniversalOuter) { // Not aggregating; delegate to nondelegating IUniversal m_pUniversalOuter = reinterpret_cast<IUniversal*> (static_cast<INondelegatingUniversal*> (this)); } else { // Aggregating; delegate to outer IUniversal m_pUniversalOuter = pUniversalOuter; } } // Destructor virtual ~NondelegatingUniversalImpl() { } // Initialization (especially for aggregates) void init(unsigned threadctx) { thread_ctx = threadctx; } // Notification to derived classes that we are releasing virtual void finalRelease() { // Increment reference count for final release ref_count = 1; } protected: // Support for delegation IUniversal* getUniversalOuter() const { return m_pUniversalOuter; } private: // thread context unsigned thread_ctx; // Reference count for this object refcount_t ref_count; // Pointer to (external) outer IUniversal IUniversal* m_pUniversalOuter; }; /////////////////////////////////////////////////////////// // // Delegating IUniversal // - Delegates to the nondelegating IUniversal,or to the // outer IUniversal if the component is aggregated. // #define DECLARE_UNIVERSAL_INTERFACE \ virtual lresult_t query(iid_t iid,void **ppv) { \ return getUniversalOuter()->query(iid,ppv); \ } \ virtual unsigned long retain(void) { \ return getUniversalOuter()->retain(); \ } \ virtual unsigned long release(void) { \ return getUniversalOuter()->release(); \ } #define CREATE_INSTANCE_NO_AGGREGATION(className) \ static lresult_t createInstance(\ unsigned threadctx,\ IUniversal *pUniversalOuter,\ iid_t iid,\ void **ppv) {\ /* cannot be aggregated */ \ if (pUniversalOuter) {\ return lres_e_noaggregation; \ } \ className * p = new className();\ if ( ! p) {\ return lres_e_outmemory;\ }\ lresult_t hr = p->init(threadctx);\ if (hr != lres_success) {\ p->NondelegatingRelease();\ return hr;\ }\ hr = p->NondelegatingQuery(iid,ppv);\ p->NondelegatingRelease();\ return hr;\ } #define CREATE_INSTANCE_WITH_AGGREGATION(className) \ static lresult_t createInstance(\ unsigned threadctx,\ IUniversal* pUniversalOuter,\ void **ppv) {\ className * p = new className(pUniversalOuter);\ if ( ! p) {\ return lres_e_outmemory;\ }\ lresult_t hr = p->init(threadctx);\ if (hr != lres_success) {\ p->release();\ return hr;\ }\ hr = p->NondelegatingQuery(iid,ppv);\ p->NondelegatingRelease();\ return hr;\ } #endif /* UNIVERSAL_H */
3)SIPtr.h
/** * SIPtr.h * Smart Interface Pointer * * Use: SIPtr<IX> spIX; * Do not use with IUniversal; SIPtr<IUniversal> * will not compile. Instead,use IUniversalPtr. * * Refer: * <<Inside COM>> * * Init Created: 2016-06-10 * Last Updated: 2016-06-10 */ #ifndef SIPTR_H #define SIPTR_H #include "Universal.h" #include <assert.h> template <class T> class SIPtr { public: // Constructors SIPtr() { m_pI = 0; } SIPtr(T* lp) { m_pI = lp; if ( m_pI ) { m_pI->retain(); } } SIPtr(IUniversal* pI) { m_pI = 0; if ( pI ) { pI->query(T::IID,(void **) & m_pI); } } // Destructor ~SIPtr() { release(); } // Reset void release() { if ( m_pI ) { T* pOld = m_pI; m_pI = 0; pOld->release(); } } // Attach to an existing interface (does not retain) void attach(T * pI) { if (m_pI != pI) { IUniversal* pOld = m_pI; m_pI = pI; if (pOld) { // Release the old interface pOld->release(); } } } // Detach the interface (does not release) T* detach() { T* pOld = m_pI; m_pI = 0; return pOld; } T* get() { return m_pI; } // Conversion operator T*() { return m_pI; } // Pointer operations T& operator*() { assert(m_pI); return * m_pI; } T** operator&() { assert(!m_pI); return &m_pI; } T* operator->() { assert(m_pI); return m_pI; } // Assignment from the same interface T* operator=(T* pI) { if (m_pI != pI) { // Save current value IUniversal* pOld = (IUniversal *) m_pI; // Assign new value m_pI = pI; if (m_pI) { m_pI->retain(); } if (pOld) { // Release the old interface pOld->release(); } } return m_pI; } // Assignment from another interface T* operator=(IUniversal* pI) { // Save current value IUniversal* pOld = m_pI; m_pI = 0; // Query for correct interface if ( pI ) { lresult_t hr = pI->query(T::iid_interface,(void**) & m_pI); assert(hr == lres_success && m_pI); } if ( pOld ) { // Release old pointer pOld->release(); } return m_pI ; } // bool functions bool operator!() { return m_pI ? false : true; } // Requires a compiler that supports BOOL operator bool() const { return m_pI ? true : false; } // Interface ID iid_t iid() { return T::IID; } private: // Pointer variable T* m_pI; }; /** * IUniversalPtr is a smart interface for IUniversal */ class IUniversalPtr { public: // Constructors IUniversalPtr() { m_pI = 0; } IUniversalPtr(IUniversal* lp) { m_pI = lp; if ( m_pI ) { m_pI->retain(); } } // Destructor ~IUniversalPtr() { release(); } // Reset void release() { if (m_pI) { IUniversal* pOld = m_pI; m_pI = 0; pOld->release(); } } // Conversion operator IUniversal*() { return (IUniversal*) m_pI; } // Pointer operations IUniversal& operator*() { assert(m_pI); return *m_pI; } IUniversal** operator&() { assert(!m_pI); return &m_pI; } IUniversal* operator->() { assert(m_pI); return m_pI; } // Assignment IUniversal* operator=(IUniversal* pI) { if (m_pI != pI) { // Save current value IUniversal* pOld = m_pI; // Assign new value m_pI = pI; if ( m_pI ) { m_pI->retain(); } if ( pOld ) { // Release the old interface pOld->release(); } } return m_pI; } // Boolean functions bool operator!() { return m_pI ? false : true; } operator bool() const { return m_pI ? true : false; } private: // Pointer variable IUniversal* m_pI; }; #endif /* SIPTR_H */
组件 GameStencil的代码:
/** * IGameStencil.h * * Author: master@pepstack.com * * Refer: * http://www.richardlord.net/blog/what-is-an-entity-framework * http://blog.csdn.net/i_dovelemon/article/details/30250049 * http://blog.csdn.net/zhao_92221/article/details/46629553 * http://blog.csdn.net/ubuntu64fan/article/details/8839778 * * Init Created: 2016-06-13 * Last Updated: 2016-06-13 */ #ifndef IGAME_STENCIL_H #define IGAME_STENCIL_H #include "core/SIPtr.h" namespace ecs { interface IGameStencil : IUniversal { static const iid_t IID = ((iid_t) 0x00F000); virtual void update(float dt) = 0; }; }; /* namespace ecs */ #endif /* IGAME_STENCIL_H */
/** * GameStencil.h * The GameStencil class is the central point for creating * and managing your game state. * * Refer: * http://www.richardlord.net/blog/what-is-an-entity-framework * http://blog.csdn.net/i_dovelemon/article/details/30250049 * http://blog.csdn.net/zhao_92221/article/details/46629553 * http://blog.csdn.net/ubuntu64fan/article/details/8839778 * * Init Created: 2016-06-12 * Last Updated: 2016-06-12 */ #ifndef GAME_STENCIL_H #define GAME_STENCIL_H #include "IGameStencil.h" #include "SystemMngmt.h" #include <memory> #include <vector> using namespace std; namespace ecs { class GameStencil : public IGameStencil,public NondelegatingUniversalImpl { public: // Creation // CREATE_INSTANCE_NO_AGGREGATION(GameStencil) private: // Constructor GameStencil() : NondelegatingUniversalImpl(0),updating(false),m_pUniversalInner(0) { printf("GameStencil\n"); m_pISystemMngmt = 0; } // Destructor virtual ~GameStencil() { finalRelease(); printf("~GameStencil\n"); } // Initialization // virtual lresult_t init(unsigned threadctx) { NondelegatingUniversalImpl::init(threadctx); IUniversal * pUniversalOuter = this; lresult_t hr = SystemMngmt::createInstance(threadctx,pUniversalOuter,IUniversal::IID,(void**) &m_pUniversalInner); if (hr != lres_success) { return lres_error; } hr = m_pUniversalInner->query(ISystemMngmt::IID,(void**) &m_pISystemMngmt); if (hr != lres_success) { m_pUniversalInner->release(); return lres_error; } pUniversalOuter->release(); return lres_success; } virtual void finalRelease() { NondelegatingUniversalImpl::finalRelease(); getUniversalOuter()->retain(); m_pISystemMngmt->release(); if (m_pUniversalInner) { m_pUniversalInner->release(); } } // IUniversal // DECLARE_UNIVERSAL_INTERFACE // INondelegatingUniversal // virtual lresult_t NondelegatingQuery(iid_t iid,void** ppv) { if (iid == IUniversal::IID) { *ppv = static_cast<IGameStencil*> (this); } else if (iid == IGameStencil::IID) { *ppv = static_cast<IGameStencil*> (this); } else if (iid == ISystemMngmt::IID) { // contained component *ppv = m_pISystemMngmt; } else { *ppv = 0; return lres_e_nointerface; } reinterpret_cast<IUniversal*> (*ppv)->retain(); return lres_success; } // IGameStencil // virtual void update(float dt) { updating = true; // TODO: updating = false; } private: bool updating; IUniversal * m_pUniversalInner; ISystemMngmt * m_pISystemMngmt; }; }; /* namespace ecs */ #endif /* GAME_STENCIL_H */
被聚合的组件 SystemMngmt的代码:
/** * ISystemMngmt.h * * Author: master@pepstack.com * * Refer: * http://www.richardlord.net/blog/what-is-an-entity-framework * http://blog.csdn.net/i_dovelemon/article/details/30250049 * http://blog.csdn.net/zhao_92221/article/details/46629553 * http://blog.csdn.net/ubuntu64fan/article/details/8839778 * * Init Created: 2016-06-10 * Last Updated: 2016-06-12 */ #ifndef ISYSTEM_MNGMT_H #define ISYSTEM_MNGMT_H #include "core/SIPtr.h" namespace ecs { interface ISystemMngmt : IUniversal { static const iid_t IID = ((iid_t) 0x10F001); virtual void update(float dt) = 0; virtual void pause() = 0; virtual void resume() = 0; }; }; /* namespace ecs */ #endif /* ISYSTEM_MNGMT_H */
/** * SystemMngmt.h * * Refer: * http://www.richardlord.net/blog/what-is-an-entity-framework * http://blog.csdn.net/i_dovelemon/article/details/30250049 * http://blog.csdn.net/zhao_92221/article/details/46629553 * http://blog.csdn.net/ubuntu64fan/article/details/8839778 * * Init Created: 2016-06-13 * Last Updated: 2016-06-13 */ #ifndef SYSTEM_MNGMT_H #define SYSTEM_MNGMT_H #include "ISystemMngmt.h" #include <memory> #include <vector> using namespace std; namespace ecs { class SystemMngmt : public ISystemMngmt,public NondelegatingUniversalImpl { public: // Creation // CREATE_INSTANCE_WITH_AGGREGATION(SystemMngmt) private: // Constructor SystemMngmt(IUniversal * pUniversalOuter) : NondelegatingUniversalImpl(pUniversalOuter) { paused = false; printf("SystemMngmt\n"); } // Destructor virtual ~SystemMngmt() { finalRelease(); printf("~SystemMngmt\n"); } virtual lresult_t init(unsigned threadctx) { NondelegatingUniversalImpl::init(threadctx); return lres_success; } public: // IUniversal // DECLARE_UNIVERSAL_INTERFACE // INondelegatingUniversal // virtual lresult_t NondelegatingQuery(iid_t iid,void** ppv) { if (iid == IUniversal::IID) { *ppv = static_cast<INondelegatingUniversal*> (this); } else if (iid == ISystemMngmt::IID) { *ppv = static_cast<ISystemMngmt*> (this); } else { *ppv = 0; return lres_e_nointerface; } reinterpret_cast<IUniversal*> (*ppv)->retain(); return lres_success; } // ISystemMngmt // // Update all the system void update(float dt) { } // Pause all the systems void pause() { paused = true; } // Resume all the systems void resume() { paused = false; } private: bool paused; }; }; /* namespace ecs */ #endif /* SYSTEM_MNGMT_H */
最后是测试代码:
// // main.cpp // #ifdef WIN32 // Refer: // http ://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html #define _CRTDBG_MAP_ALLOC #include <stdlib.h> #include <crtdbg.h> #else #include <stdlib.h> #endif #include <assert.h> #include <stdio.h> #include <string.h> #include "model/GameStencil.h" using namespace ecs; void usage() { SIPtr<IGameStencil> spGame; GameStencil::createInstance(thread_ctx_single,spGame.iid(),(void**) &spGame); SIPtr<ISystemMngmt> spSysMngmt; spGame->query(spSysMngmt.iid(),(void**) &spSysMngmt); SIPtr<IGameStencil> spGame2; spSysMngmt->query(spGame2.iid(),(void**) &spGame2); assert(spGame2.get() == spGame.get()); spSysMngmt->update(0.1f); } int main() { #ifdef _CRTDBG_MAP_ALLOC _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); #endif printf("main.cpp start\n"); usage(); printf("main.cpp exit.\n"); return 0; }没有内存泄露。OK!