分享
 
 
 

编写可复用性更好的C++代码——Band对象和COMToys(七)

王朝vc·作者佚名  2006-01-17
窄屏简体版  字體: |||超大  

编写可复用性更好的C++代码

——Band对象和COMToys(七)

编译/赵湘宁

原著:Paul Dilascia

MSJ November 1999 & December 1999

关键字:Bands 对象,Desk Bands,Info/Comm Bands,Explorer Bar,Tool

Bands。

本文假设你熟悉C++,COM,IE。

下载本文源代码: MyBands.zip

(128KB)

TestEditSrch.zip

(75KB)

第一部分:Band 对象介绍

第二部分:BandObj的类层次和MyBands服务程序的注册

第三部分:深入Band内部,揭开Band的面纱

第四部分:Band对象使用中遇到的一些问题

第五部分:建立自己的COM编程平台ComToys

第六部分:设计和构造COMToys

第七部分 类的实现

类的混合实现

到目前为止,我介绍了一种用多继承代替嵌套类在MFC中实现COM对象的方法。基本思路是忽略MFC宏和接口映射,并调用GetInterfaceHook来返回接口指针。很明显,这已经使编程容易了许多,但可复用性体现在哪呢?开发COMToys的主要目的是让它具备可复用性特点来实现普通的COM接口。为此,COMToys使用了混合类。

混合类被设计成利用多继承特性与其它类混合。通常混合类与主类的关系是直交的,也就是说它提供与类层次的无关性来避免菱形派生树(请原谅我的矛盾修饰法),不用怀疑,CBandObj比我在前面四个部分中讨论的要复杂一些,其新版本的代码如下:

//更新的 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如此类似!且看下文的分析。

CBandObj从IDeskBand继承IOleWindow 和 IDockingWindow,所以必须实现它们。对于IUnknown来说,COMToys的宏声明方法,DECLARE_IOleWindow 和 DECLARE_IDockingWindow仅声明大多数派生的方法,而不是继承的方法(马上就会明白为什么),这两个宏都是不可少的。DECLARE_IDeskBand宏是不存在的,因为COMToys不具备实现它的类;IDeskBand由CBandObj实现。对于IUnknown,COMToys有更多的宏实现此接口。

新的宏用CTOleWindow实现了IOleWindow,并用CTDockingWindow 实现了IDockingWindow。IMPLEMENT_IWhatsIt宏通过产生调用类实现的方法将某个接口定义(抽象类)与接口实现(具体的类)进行链接。

// 由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类中使用它,必须做四件事情:

派生IWhatsIt 和 CTWhatsIt;

用DECLARE_IWhatsIt声明方法;

用IMPLEMENT_IWhatsIt(CMyComClass, CTWhatsIt)来实现这些方法;

并在CMyComClass::GetInterfaceHook中添加几行相应的代码,看上面就知道只有总共五行代码,就这么简单。

实现执行类

前面的讨论都是假设已经具备了实现接口的CTWhatsIt!至少是已知如何将接口实现局部化在一个C++类中——换句话说,就是如何重用接口实现。为了提供更加方便的应用,COMToys内建了许多现成的接口实现。CTPersist, CTPersistStream, CTOleWindow, CTDockingWindow, CTInputObject等等。它们是如何工作的呢?

有关执行类的第一个让人奇怪的地方是它们不是从其对应的接口派生而来的!而只是带有相同名字和签名方法的类。

// 不从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的实现基本上照搬前面代码中相应的部分。

图十二 CBandObj 上下文菜单

当容器调用IContextMenu::QueryContextMenu获得菜单项时,CTMfcContextMenu用希望的CMenu项填写菜单,但不是在创建CcmdUI对象之前,并且通过CCmdTarget发送,从而ON_UPDATE_COMMAND_UI处理器都能挂上菜单。同样,当容器调用InvokeCommand,CTMfcContextMenu发送这个命令到ON_COMMAND处理器。CTMfcContextMenu甚至能通过查找串资源来处理提示串。简言之,CTMfcContextMenu将COM语言转换成MFC语言。剩下的事情只是给它一个菜单和命令目标(通常是COM类本身)。所有的命令处理与在MFC应用中处理一样,不用再次实现IContextMenu——用COMToys就能搞掂。(待续)

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有