分类: C/C++
2008-03-18 15:23:30
//更新的 CBandObj ///////////////////////////////////////// // CBandObj —— 典型的实现多接口的 COM 类 // 头文件.h : class CBandObj : public CWnd, // interfaces // public IOleWindow, // 从IDeskBand继承 // public IDockingWindow, // 从IDeskBand继承 public IDeskBand, public IObjectWithSite, public IInputObject, // public IPersist, // 从IPersistStream继承 public IPersistStream, public IContextMenu, // 使用ComToys实现 public CTOleWindow, public CTDockingWindow, public CTPersist, public CTPersistStream, public CTInputObject, public CTInputObjectSite, public CTContextMenu { public: CBandObj(REFCLSID clsid); virtual ~CBandObj(); // 改写以便实现 QueryInterface virtual LPUNKNOWN GetInterfaceHook(const void* iid); // 用ComToys实现的接口 DECLARE_IUnknown(); DECLARE_IOleWindow(); DECLARE_IDockingWindow(); DECLARE_IInputObject(); DECLARE_IPersist(); DECLARE_IPersistStream(); DECLARE_IContextMenu(); DECLARE_IObjectWithSite(); // IDeskBand STDMETHOD (GetBandInfo) (DWORD, DWORD, DESKBANDINFO*); }; // 实现文件 .cpp : CBandObj::CBandObj(REFCLSID clsid) : // 初始化所有实现类 CTMfcComObj(this), CTOleWindow(this), CTDockingWindow(this), CTPersist(clsid), CTPersistStream(), CTInputObject(this), CTContextMenu(this, CTMfcComObjFactory::GetFactory(clsid)->GetResourceID()) { ...... } ////////////////////////////////// // 这些宏用ComToys实现了所有接口 // IMPLEMENT_IUnknown (CBandObj, CTMfcComObj); IMPLEMENT_IOleWindow (CBandObj, CTOleWindow); IMPLEMENT_IDockingWindow (CBandObj, CTDockingWindow); IMPLEMENT_IPersist (CBandObj, CTPersist); IMPLEMENT_IContextMenu (CBandObj, CTContextMenu); IMPLEMENT_IPersistStream (CBandObj, CTPersistStream); IMPLEMENT_IInputObject (CBandObj, CTInputObject); ///////////////////////////////////////////////////////////////////// // 改写后旁路掉MFC在CCmdTarget::InternalQueryInterface中的接口映射 // LPUNKNOWN CBandObj::GetInterfaceHook(const void* piid) { REFIID iid = *((IID*)piid); if (iid==IID_IUnknown) return (IDeskBand*)this; #define IF_INTERFACE(iid, iface) \ if (iid==IID_##iface) \ return (iface*)this; \ IF_INTERFACE(iid, IOleWindow); IF_INTERFACE(iid, IDockingWindow); IF_INTERFACE(iid, IObjectWithSite); IF_INTERFACE(iid, IInputObject); IF_INTERFACE(iid, IPersist); IF_INTERFACE(iid, IPersistStream); IF_INTERFACE(iid, IContextMenu); IF_INTERFACE(iid, IDeskBand); return NULL; }乍一看这些类与ATL如此类似!且看下文的分析。
// 由IMPLEMENT_IOleWindow产生 HRESULT CBandObj::GetWindow(HWND* pHwnd) { CMDTARGENTRYTR( _T("CBandObj(%p)::IOleWindow::GetWindow\n"), this); return CTOleWindow::GetWindow(pHwnd); }一旦你声明并实现了这些接口,剩下的事情是通过在GetInterfaceHook中编写代码,让MFC知道它们:
// 在CBandObj::GetInterfaceHook中 if (iid==IID_IOleWindow) return (IOleWindow*)this; if (iid==IID_IDockingWindow) return (IDockingWindow*)this;很简单,如果理解了它们在IOleWindow中的工作原理,便会明白CBandObj的代码大多是相同东西的重复。对于新的COM类实现的每个IWhatsIt接口,要从两个类派生:COM接口本身(IWhatsIt)和实现CTWhatsIt的CT类。执行类可以是COMToys所带的一个类或是自己的类。两种情况的接口和实现都是分开的。这样感觉比较专业,因为它也是COM的基本原则。 现在假设已经有了实现某些接口的CTWhatsIt,为了在自己的COM类中使用它,必须做四件事情:
// 不从IOleWindow派生! class CTOleWindow { public: // 与主类相同的宏 DECLARE_IOleWindow(); };接口和实现之间没有真正的联系;所谓的联系完全是词汇上的。这就是另外一个使用DECLARE_IFoo的理由:它保证获得正确的名字和签名。在CTOleWindow中的编排也不会产生错误,简单地定义一个新函数即可。 关于混合类的另外一个要注意的事情是方法实现只针对派生接口,而不是继承的接口。IDockingWindow 实现 ShowDW,CloseDW 和ResizeBorderDW——而不是GetWindow 或ContextSensitiveHelp,它们从IOleWindow继承。所以要想实现IDockingWindow,就必须混合CTDockingWindow 和CTOleWindow。同样,CTPersistStream也不实现GetClassID,它是从IPersist继承的。 为什么我要这么做呢?第一,它混合并匹配不同的实现类。第二,可选择性考虑。例如,假设我从CTPersist派生CTPersistFile 和CTPersistStream,它们都有GetClassID函数(我最初就是这么做的)。现在使用CTPersistFile 和CTPersistStream的任何类都有GetClassID的两个拷贝,这样造成了不明确的多继承。图十七 中的多继承规则(Magic MI)只用于纯粹的虚函数,而非具体函数,由此所有多继承接口获得相同的具体函数。想象得到,尽管希望IPersistFile 和 IPersistStream有不同的GetClassID实现,但百分之九十九的时间做不到。通过在CTPersistFile 和 CTPersistStream,中只实现大多数派生方法,COMToys可以进行一次而且是仅有的一次混合来实现想要的CTPersist。当编写下面的代码后:
IMPLEMENT_IPersist(CMyComClass, CTPersist);产生的GetClassID方法便替代IPersistStream 和 IPersistFile中的GetClassID。 最后正是在这些方法中我不得不针对语言规范编写一些代码。所幸的是它不算太麻烦。只是将前面用嵌套类实现的BandObj转换成本文中对应的CTXxx类,将必须的变量转换成数据成员并在构造函数中进行初始化。例如,CTPersist的代码如下:
class CTPersist { public: const CLSID& m_clsid; CTPersist(const CLSID& clsid) : m_clsid(clsid) { } STDMETHODIMP GetClassID(LPCLSID pClassID) { return pClassID ? (*pClassID=m_clsid, S_OK) : E_UNEXPECTED; } };CTPersist保存着类ID的引用,并不是类ID本身。此乃COMToys的一般原则:执行类不存储实际数据,只有外部对象的指针或引用。对于CTPersist,它无关紧要,因为一旦运行中的对象类ID改变,它便无所适从。但一般来说,数据是可以改变的,所以让父类拥有数据是明智的,这样它就可以自由的处理这些数据。CTPersistFile 和 CTPersistStream都有一个m_bModified修改标志,但它是对BOOL类型的一个引用,而不是BOOL类型。如果主类改变了实际的标志,执行类自动改变,不用自己去调用诸如SetModified之类的函数。作为一个一般的编程规则,对状态的操作最好是用按需方式进行(demand-pull),而不要用先入方式(supply-push),并且整个系统的每个状态变量自始至终只应该有一个拷贝。 CTMfcContextMenu的情况类似,它保存着对仅仅一个CMenu的引用,主类必须在构造时提供。
// 在COMToys.h文件中 class CTMfcContextMenu { public: CCmdTarget* m_pCmdTarget; CMenu& m_ctxMenu; CTMfcContextMenu(CCmdTarget* pTarg, CMenu& menu) : m_pCmdTarget(pTarg), m_ctxMenu(menu) { } …… }; // 在BandObj.cpp文件中 CBandObj::CBandObj(REFCLSID clsid) : CTMfcContextMenu(this, m_menu), ... { …… }因为CTMfcContextMenu::m_ctxMenu是个引用,CBandObj可以任何方式改变菜单,不必显式通知CTMfcContextMenu。CBandObj依赖这个特性,因为当用户右键单击Band时,它产生其浮动菜单。如图十二)。CTMfcContextMenu的实现基本上照搬前面代码中相应的部分。