肝胆相照论坛

 

 

肝胆相照论坛 论坛 电脑技术 存档 1 面向对象的组件模型-COM
查看: 374|回复: 1

面向对象的组件模型-COM [复制链接]

Rank: 9Rank: 9Rank: 9

现金
25967 元 
精华
帖子
11078 
注册时间
2003-4-14 
最后登录
2018-3-24 

开心乐园 荣誉之星

1
发表于 2004-8-27 05:29
Windows系统霸主地位诂计三四年内是不会被动摇的。因此,有n多Windows开发平台出现在我们面前。n多种开发语言是百花齐放啊。于是,我们像圣经里说的那样,操着不同的语言,彼此无法沟通。为改变这一现实,可爱的比尔就站出来了,”偶要改变世界!”。微软公司制定一个基于二进制通用接口规范-Component Object Model(组件对象模型)。但是,一开始COM的解决目标并非是为了通用接口,而是应用于复合文档(OLE)的实现。而今由于语言无关性、进程透明性、可重用性、保密性(除非高手高手高高手,有谁能从汇编码中看出实现技术来)、而且编写并不困难,所以发展成为了一项应用广泛的技术。 1) 组件对象与接口 组件对象、接口是COM的根基。 下面,请允许我用C++对象做一个类比。 组件对象与C++对象的意义是基本相同的。它是一个功能、属性与逻辑的整体。它是一个实体对象,通过对它的接口操作,可以使用它所提供的功能。 接口相当于C++对象中的public成员。它被暴露给外部使用者,使用者只被允许调用这些被暴露在外面的接口来使用对象的功能。与public成员有所不同的是,接口不是一个变量也不是一个函数,而应该是一组函数。在逻辑上,这个组函数应该是功能相关的。一个组件对象可以拥有许多个接口。 我只知道C++的COM实现方法,至于Dephi我就一无所知。 C++实现方法是:由C++类对象来完成组件对象的实现,由C++纯虚类来代表接口。C++类对象通过多重继承多个接口,来的拥有多个接口。 下面,我举一个例子,来说明C++中的组件对象与接口的关系(下面的例子并不是一个COM实现,只是用来表示组件对象与接口的关系) 我如果要做一个人的组件对象的话,我首先要定义一些接口来表示人的外部表现行为。 class physiology { public: virtual void eat(Food in) = 0; virtual void drink(Liquid in) = 0; virtual Somethings toilet() = 0; }; class psychics { public: virtual Sound laugh() = 0; virtual Sound cry() = 0; virtual Sound angry() = 0; }; class dynamics { public: virtual Speed run() = 0; virtual Speed walk() = 0; virtual Interval jump() = 0; }; 我将人的行为分成了生理学、心理学和动力学三类,让它们分别表示人不同的行为。那么,这么三组相关函数就是三个接口。C++组件对象的实现就是从这些接口中多重派生,并实现它们。这样,我们就得到一个组件对象(声明啊,本示例只是一个表示概念,真正的COM组件对象还需要加一些东东)。 class human : public physiology, public psychics, public dynamics { public: void eat(Food in) { cout << "Good! Very delicious!"; } void drink(Liquid in) { cout << "No! I am not drunk!"; } Something toilet() { cout << "hum……."; return dejecta(); } Sound laugh() { return Sound("Ha…Ha…Ha"); } Sound cry() { return Sound("dad!Don’t beat my buns."); } Sound angry() { return Sound("where did you go last night? Darling."); } Speed run() { cout << "Run, Police come!"; return 20km/h; } Speed walk() { cout << "out. yegg, I am no…not afraid o….of y…you."; return 1m/s; } Interval jump() { cout << "Yeah…."; return 4m; } }; 这样,一个组件对象就定义完了。当使用组件对象时,系统所给予你的一个指针。它是一个组件对象实现了的虚类指针,我们可以使用它来调用组件对象对于这个纯虚类所实现的功能(当然,我们有选择什么虚类指针的权利;只要组件对象支持就可以了)。 总之,一个组件对象外部特征是由不同的接口也就是这些虚类所组成,它们向使用者展现组件所提供的功能。 注:如果你的C++虚函数没学得不太好的话,那么请找一本C++语法书再看一看. 或请参看VCKBASE第12期的《解析动态联编》。 2) 标识符(GUID) 上面,我说过COM组件是基于二进制的。那么要我们使用签名(比如说类名、接口名)来指定一个组件显然是不理想的了(至少在识别方面会有些麻烦)。那么,既然是二进制系统最方便当然就是使用数字标识了。于是,微软定义了这么一个结构标准: typedef struct _GUID { DOWRD Data1; WORD Data2; WORD Data3; WORD Data4[8]; }GUID; 结构用来储存一些数字信息,来表识一个COM对象,接口以及其它COM元素。这个结构体就叫做标识符。 在C++中一个标识符是这么表示的:extern "C" const GUID CLISID_MYSPELLCHECKER = {0x54bf6567, 0x1007, 0x11d1, {0xb0, 0xaa, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}} 同样的标识符在其它非C环境中是这么表示的:{54bf6567-1007-11d1-b0aa-444553540000} 这个标识符代表着一个COM对象,这是因为一个COM对象的标识符名都以CLISID_为前缀。接口名则是以IID_为前缀。不要问我,标识符定义与对象具体有什么关系式。我不知道。它们根本就没有什么关系的。一个COM对象在编写时,我们会使用随机的方法来确定它的标识符(这个工作可以由VC来帮我们搞定)。一旦COM对象得到一个标识符并发布出去的话,那么就不能更改了。另外,不要担心GUID会有所冲突。如果你的高中数学已经及格了的话,那么请算一算128位二进制中,重复的概率会有多少。假如你真的发现了GUID有冲突的话(你要保证这不是人为),建议你赶去买彩票吧。你离500万不远了。 3) IUnknown接口 COM模式所有接口必须遵守一定规范,这就是IUnknown接口的出处。每个一接口都必须从这个接口继承。在C++中,微软已经为我们把IUnknown定义好了: typedef GUID IID; class IUnknown { public: virtual HRESULT _stdcall QueryInterface(const IID& iid, void **ppv) = 0; virtual ULONG _stdcall AddRef() = 0; virtual ULONG _stdcall Release() = 0; }; 注:void *可以指向任何对象。我开始的时候对void*一点都不理解。这里使用的原因是传出与传入指针类型不确定。 QueryInterface函数功能是当我们得到一个接口指针,并且我们想得到另一个接口指针的时候,提供帮助。我们将我们想要得到的接口的标识符传给iid,将把指针的做一个次&来传给ppv。如果QueryInterface成功的话,会返回S_OK。我们指针中就会指向我们想要的接口。 AddRef,Release用于实现引用计数机制。 在二进制系统中,组件对象不像C++环境中对象那样具有明确的生存期。可能会出现这种情况,两个(或者两个以上)的地方(可能是不同的程序之间,也可能是不同的线程之间)同时使用着一个组件对象,如果其中一个地方delete掉了组件对象的话。其它地方不可能会知道,当它们尝试调用这个象的话,轻则导致重伤,重则导致死亡。这不是我们希望看到的。于是,COM模型设制一个引用计数机制。 当一个地方开始使用对象的时候,它必须调用AddRef()一次。当我们使用QueryInterface时候,QueryInterface必须为我们调用一次AddRef()。AddRef()会使组件对象的引用计数增1。当这个地方不再使用对象时,它必须调用Release()一次。Release()会使组件对象的引用计数减1。当组件对象的引用计数变成0,就表明没有人再去使用组件对象了。这时,组件对象应该结束自己的生命。这样,就保证了组件对象生存期间其它程序的安全。 当然,你可以使用自己的引用机制,只要你的行为上支持AddRef和Release。比如说,不设置对象的引用计数,而是为每个接口设置一个引用计数。当所有的接口引用计数都为0时,delete对象。 好了,前面的示例中,我并没有遵守IUknown规范,下面我要遵守它。我把上次同样东西用……省略掉了。 // {6AAF876E-FCED-4ee0-B5D3-63CD6E2242F5} static const GUID IID_IPhysiology = { 0x6aaf876e, 0xfced, 0x4ee0, { 0xb5, 0xd3, 0x63, 0xcd, 0x6e, 0x22, 0x42, 0xf5 } }; class IPhysiology: public IUnknown { public: …… }; // {183FC7A1-4C27-4c38-B72D-D1326E2E8A7C} static const GUID IID_IPsychics = { 0x183fc7a1, 0x4c27, 0x4c38, { 0xb7, 0x2d, 0xd1, 0x32, 0x6e, 0x2e, 0x8a, 0x7c } }; class IPsychics: public IUnknown { public: …… }; // {5F144D5C-A20C-42e7-8F91-4D5CAE430B29} static const GUID IID_IDynamics = { 0x5f144d5c, 0xa20c, 0x42e7, { 0x8f, 0x91, 0x4d, 0x5c, 0xae, 0x43, 0xb, 0x29 } }; class IDynamics: public IUnknown { public: …… }; // {ABFA7022-7E2F-4d0e-8A4F-F58BBCEBB2DA} static const GUID CLISID_Human = { 0xabfa7022, 0x7e2f, 0x4d0e, { 0x8a, 0x4f, 0xf5, 0x8b, 0xbc, 0xeb, 0xb2, 0xda } }; class human : public IPhysiology, public IPsychics, public IDynamics { public: …… human() { m_ulRef = 0; } HRESULT QueryInterface(const IID& iid, void **ppv) { if (iid == IID_IUnknown || iid == IID_IPhysiology) { *ppv = static_cast(this); (IPhysiology*)(*this))->AddRef(); } else if (iid == IID_IPsychics) { *ppv = static_cast(this); (IPsychics*)(*this))->AddRef(); } else if (iid == IID_IDynamics) { *ppv = static_cast(this); (IDynamics*)(*this))->AddRef(); } else { *ppv = NULL; return E_NOTINTERFACE; } return S_OK; } ULONG AddRef() { return ++m_ulRef; } ULONG Release() { m_ulRef--; if (m_ulRef <= 0) { m_ulRef = 0; delete this; } return m_ulRef; } ULONG m_ulRef; }; 这样我们的组件对象就定义完全了。 下面给出我们这个组件对象的IDL描述和图形描述#include "olectl.h" import "oaidl.idl"; import "ocidl.idl"; [ object, uuid(6AAF876E-FCED-4ee0-B5D3-63CD6E2242F5), nonextensible, helpstring("IPhysiology 接口"), pointer_default(unique) ] interface IPhysiology : IUnknown { void eat(Food in); void drink(Liquid in); Somethings toilet(); }; [ object, uuid(5F144D5C-A20C-42e7-8F91-4D5CAE430B29), nonextensible, helpstring("IPsychics 接口"), pointer_default(unique) ] interface IPsychics : IUnknown { Sound laugh(); Sound cry(); Sound angry(); }; [ object, uuid(5F144D5C-A20C-42e7-8F91-4D5CAE430B29), nonextensible, helpstring("IDynamics 接口"), pointer_default(unique) ] interface IDynamics : IUnknown { Speed run() = 0; Speed walk() = 0; Interval jump() = 0; }; [ uuid(6CC7B329-B92F-4A8F-9CDD-1AB6D7E4CF4D), version(1.0), helpstring("OLEOBJECT 1.0 类型库") ] library OLEOBJECTLib { importlib("stdole2.tlb"); [ uuid(62FD0E39-DA84-4B19-BAB0-960A27AC2B71), helpstring("OlePaint Class") ] coclass OlePaint { [default] interface IPhysiology, interface IPsychics, interface IDynamics }; }; 请伃细,观察上面的描述IDL代码和图形。并不是太难吧。 4) COM对象的接口原则 为了规范COM的接口机制,微软向COM开发者发布了COM对象的接口原则。 (1)IUnknown接口的等价性 当我们要等到两个接口指针,我如何判断它们从属于一个对象呢。COM接口原则规定,同一个对象的Queryinterface的IID_IUnknown查询出来的IUnknown指针值应当相等。也就是说,每个对象的IUnknown指是唯一的。我们可以通过判断IUnknown指针是否相等来判断它们是否指向同一个对象。 IUnknown *pUnknown1 = NULL, *pUnknown2 = NULL; pObjectA->QueryInterface(IID_IUnknown,(void **) &pUnknown1); pObjectB->QueryInterface(IID_IUnknown,(void **) &pUnknown2); if (pUnknown1 == pUnknown2) { cout << “I am sure ObjectA is ObjectB.”; } else { cout << “I am sure ObjectA is not ObjectB.”; } 当然,如果查询的不是IUnknown接口,则无此限制。同一对象对非IUnknown接口的查询值可以不同。 (2)接口自反性,对一个接口来说,查询它本身应该是允许的。 设pPsychics是已赋值IPsychics的接口。 那么pPsychics->QueryInterface(IID_IPsychics,(void **) &XXX);应当成功。 (3)接口对称性,当我们从一个接口查询到另一个接口时,那么我们再从结果接口还可以查询到原来的接口。 例如:IPsychics *pSrcPsychics = …something, *pTarget = NULL; IDynamics *pDynamics = NULL; 如果pSrcPsychics->QueryInterface(IID_IDynamics,(void **) &pDynamics);成功的话。 那么pDynamics->QueryInterface(IID_IPsychics,(void **) &pTarget);也相当成功。 (4)接口传递性。如果我们从第一个接口查询到了第二个接口,又从第二个接口查询到了第三接口。则我们应该能够从第三个接口查询到第一个接口。其它依此类推。 (5)接口查询时间无关性。当我们在某时查询到一个接口,那么在任意时刻也应该查询到这个接口。 [em24]

换大米喽,谁要换大米?

Rank: 4

现金
2523 元 
精华
帖子
843 
注册时间
2002-9-9 
最后登录
2012-3-26 
2
发表于 2004-8-27 11:30
原来学了一段时间
没有具体的项目支持
也就放弃了

楼主如果想做普及
介绍的是不是有点深了。。。
‹ 上一主题|下一主题

肝胆相照论坛

GMT+8, 2024-11-29 13:25 , Processed in 0.015368 second(s), 12 queries , Gzip On.

Powered by Discuz! X1.5

© 2001-2010 Comsenz Inc.