




已阅读5页,还剩85页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
6.IPersistStreamInit接口(1)IPersistStreamInit:InitNew 初始化永久对象为默认状态。原 型:HRESULT InitNew();返回值:成功,返回S_OK;否则,返回HRESULT错误码。8.IPersistStorage接口(1)IPersistStorage:InitNew 初始化一个新的存贮对象。原 型:HRESULT InitNew(IStorage * pStg);/存贮对象的IStorage接口返回值:成功,返回S_OK;否则,返回HRESULT错误码。(2)IPersistStorage:Load 从一个现有存贮初始化一个对象。原 型:HRESULT Load(IStorage * pStg);/现有存贮对象的IStorage接口返回值:成功,返回S_OK;否则,返回HRESULT错误码。(3)IPersistStorage:SetData 提。原 型:(4)IPersistStorage:SetData 提。原 型:(5)IPersistStorage:SetData 提。原 型:9.IPersistStream接口(1)IPersistStream:SetData 提。原 型:4.IAdviseSink接口(1)IAdviseSink:OnDataChange 当前已注册的服务器的通知接收器对象中的数据已更改。原 型:void OnDataChange(FORMATETC * pFormatetc,/FORMATETC结构(数据格式)STGMEDIUM * pStgmed);/STGMEDIUM结构(数据位置)备 注:通知接收器是客户程序实现的一个内部对象。(2)IAdviseSink:OnViewChange 通知接收器对象及其视图已更改。原 型:void OnViewChange(DWORD dwAspect,/LONG lindex);/(3)IAdviseSink:OnRename 通知所有注册的接收器对象已更名。原 型:void OnRename(IMoniker * pmk);/(4)IAdviseSink:OnSave 对象已被保存到磁盘。原 型:void OnSave();(5)IAdviseSink:OnClose 对象已被关闭。原 型:void OnClose();COM对象模型用ATL写一个COM组件,在组件中实现了一个自定义接口(当然可把200个函数都加到这一个接口中,果真如此的话,恐怕就没有人使用这个组件了)。一个组件既然可提供多个接口,那么在设计时,就应按函数功能进行分类,把不同功能分类的函数用多个接口表现出来。优点是:1.一个接口中的函数个数有限、功能集中,便于学习使用;2.容易维护和升级。当给组件增加函数时,无需修改已发表的接口,而是提供一个新的接口来完成功能扩展。接口结构如下:组件A有2个自定义接口,组件B是A的升级假设设计了组件A,它有2个自定义接口。IMathe有Add方法完成整数加法,IStr有Cat方法完成字符串连接。升级组件A到B,欲增加一个Mul方法完成整数乘法。由于组件A已发布,因此不能把这个方法安排到IMathe中。解决方法是再定义一个新接口IMathe2,在新接口中增加Mul方法并保留Add方法。这样,老用户不知道新接口IMathe2的存在,仍可使用旧接口IMathe;而新用户则可抛弃IMathe直接使用IMathe2的新接口功能。多平滑的升级方式!COM组件是一种基于二进制对象协议的概念。可理解为,这是一个二进制意义上的类。一个COM组件,对外暴露的不是一组方法,而是一组接口。COM组件直观理解就是一个类,但这不是严谨的定义(有的语言没有类,但它可实现COM组件);COM组件通常是一个类,也可能是用多个类实现的。是一个类还是多个类实现的,对客户而言不知道也不关心。从COM意义上讲,接口是一种和目前vtbl机制相容的二进制协议,且vtbl的前3项与IUnknown接口相容(从继承角度上讲可理解为要求从IUnknown继承)。可定义如下接口:interface IFoo:IUnknownvirtual void _stdcall fooA()=0;virtual int _stdcall fooB(intarg1,intarg2)=0;也可不这样写,而是用纯C风格:struct IFoo VtblHRESULT(_stdcall*QueryInterface)(void * pThis,const GUID * iid,void * ppv);ULONG(_stdcall*AddRef)(void*pThis);ULONG(_stdcall*Release)(void*pThis);void(_stdcall*fooA)(void*pThis);int(_stdcall*fooB)(void*pThis,intarg1,intarg2);struct IFoostructI Foo Vtbl*vptr;1.COM是什么COM是由Microsoft提出的组件标准,它不仅定义了组件程序之间进行交互的标准,并且也提供了组件程序运行所需的环境。在COM标准中,一个组件程序也被称为一个模块,它可以是一个动态链接库,被称为进程内组件;也可以是一个可执行程序(即EXE程序),被称作进程外组件。一个组件程序可包含一个或多个组件对象,因为COM是以对象为基本单元的模型,所以在程序与程序之间进行通信时,通信的双方应该是组件对象,也叫做COM对象,而组件程序(或称作COM程序)是提供COM对象的代码载体。COM对象不同于一般面向对象语言(如C+语言)中的对象概念,COM对象是建立在二进制可执行代码级的基础上,而C+等语言中的对象是建立在源代码级上,因此COM对象是语言无关的。这一特性使用不同编程语言开发的组件对象进行交互成为可能。2.COM对象与接口类似于C+中对象的概念,对象是某个类的一个实例;类则是一组相关的数据和功能组合在一起的一个定义。使用对象的应用(或另一个对象)称为客户,有时也称为对象的用户。接口是一组逻辑上相关的函数集合,其函数也被称为接口成员函数。按照习惯,接口名常是以I为前缀。COM对象通过接口成员函数为客户提供各种形式的服务。在COM模型中,对象本身对客户来说是不可见的。客户请求服务时,只能通过接口进行。每个接口都由一个128位的全局唯一标识符来标识。客户通过GUID来获得接口指针,再通过接口指针,客户就可调用其成员函数。与接口类似,每个组件也用一个128位GUID来标识,称为CLSID(类标识或类ID),用CLSID标识对象可保证(概率意义上)在全球范围内的唯一性。实际上,客户成功地创建对象后,它得到的是一个指向对象某个接口的指针,因为COM对象至少实现一个接口(没有接口的COM对象是没有意义的),所以客户就可调用该接口提供的所有服务。根据COM规范,一个COM对象如果实现了多个接口,则可从某个接口得到该对象的任意其它接口。从这个过程也可看出,客户与COM对象只通过接口打交道,对象对客户来说只是一组接口。从技术上讲,接口是包含了一组函数的数据结构,通过这组数据结构,客户代码可调用组件对象的功能。接口定义了一组成员函数,这组成员函数是组件对象暴露出来的所有信息,客户程序利用这些函数获得组件对象的服务。通常把接口函数表称为虚函数表(vtable),指向vtable的指针为pVtable。对一个接口来说,它的虚函数表是确定的,因此接口的成员函数个数不变,成员函数的先后顺序也不变;对每个成员函数来说,其参数和返回值也是确定的。在一个接口的定义中,所有这些信息都必须在二进制一级确定,不管什么语言,只要能支持这样的内存结构描述,就可使用接口。每一个接口成员函数的第一个参数为指向对象实例的指针(=this),这是因为接口本身并不独立使用,它必须存在于某个COM对象上,因此该指针可提供对象实例的属性信息,在被调用时,接口可知道是对哪个COM对象在进行操作。在接口成员函数中,字符串变量必须用Unicode字符指针,COM规范要求使用Unicode字符,且COM库提供的API函数也使用Unicode字符。所以,如果在组件程序内部使用了ANSI字符,就应进行两种字符的转换。当然,在既建立组件程序又建立客户程序的情况下,可使用自定义参数类型,只要它们与COM所能识别的参数类型兼容。VC+提供两种字符串的转换:namespace _com_util BSTR ConvertStringToBSTR(const char *pSrc)throw(_com_error);BSTR ConvertBSTRToString(BSTR pSrc)throw(_com_error); BSTR是双字节串,是最常用的自动化数据类型3.全局唯一标识符COM规范采用了128位全局唯一标识符GUID来标识对象和接口,这是一个随机数,并不需要专门机构进行分配和管理。因为GUID是个随机数,所以并不绝对保证唯一性,但发生标识符相重的可能性非常小。从理论上讲,如果一台机器每秒产生10000000个GUID,则可保证(概率意义上)的3240年不重复)。GUID在C/C+中可用这样的结构来描述:typedef struct DWORD Data1;WORD Data2;WORD Data3;BYTE Data48; GUID;例:64BF4372-1007-B0AA-444553540000可如下定义一个GUID:extern C const GUID CLSID_MYSPELLCHECKER = 0x54BF0093,0x1048,0x399D, 0xB0,0xA3,0x45,0x33,0x43,0x90,0x47,0x47 ;VC+提供了2个生成GUID的程序:UUIDGen.exe(命令行)和GUIDGen.exe(对话框)。COM库也提供了生成GUID的函数:HRESULT CoCreateGuid(GUID *pguid);4.接口描述语言IDLCOM规范在IDL接口描述语言(OSF的DCE规范)的基础上,通过扩展形成了COM接口的描述语言。接口描述语言提供了一种不依赖于任何语言的接口的描述方法,因此,可成为组件程序和客户程序之间的共同语言。COM规范使用的IDL不仅可用于定义COM接口,同时还定义了一些常用数据类型,也可描述自定义的数据结构,对接口成员函数,可定义每个参数的类型、输入输出特性,甚至支持可变长度的数组的描述。IDL支持指针类型,与C/C+很类似。interface IDictionary/使用IDL语言定义接口HRESULT Initialize();HRESULT LoadLibrary(instring);HRESULT InsertWord(instring,instring);HRESULT DeleteWord(instring);HRESULT LookupWord(instring,outstring*);HRESULT RestoreLibrary(instring);HRESULT FreeLibrary();Microsoft VC+提供了MIDL工具,可把IDL接口描述文件编译成C/C+兼容的接口描述头文件(.h)。5.IUnknown 接口interface IUnknown/ IUnknown接口的IDL定义HRESULT QueryInterface(inREFIID iid,outvoid *ppv);ULONG AddRef(void);ULONG Release(void);class IUnknown/ IUnknown接口的C+定义virutal HRESULT _stdcall QueryInterface(const IID&iid,void*ppv)=0;virtual ULONG _stdcall AddRef()= 0;virutal ULONG _stdcall Release()= 0;6.COM进程模型有两种进程模型:进程内对象和进程外对象。如果是进程内对象,则它在客户进程空间上运行;如果是进程外对象,则它运行在本机上的另一个进程空间或远程机上。进程内服务器:服务程序被加载到客户的进程空间,在Win32环境下,通常服务程序代码以动态连接库(DLL)的形式实现;本地服务器:服务程序与客户程序运行在同一台机器上,服务程序是一个独立的应用程序,通常它是一个EXE文件;远程服务器:服务程序运行在与客户不同的机器上,既可以是一个DLL模块,也可以是一个EXE文件。如果远程服务程序以DLL形式实现,远程机上会创建一个代理进程。虽然COM对象有不同的进程模型,但这种区别对客户来说是透明的,因此客户程序在使用组件对象时可不管这种区别的存在,只要遵照COM规范即可。然而,在实现COM对象时,还是应慎重选择进程模型。进程内模型的优点是效率高,但组件不稳定会引起客户进程崩溃,因此组件可能会危及客户(注:如果组件不稳定,进程外模型同样也会出问题,但进程内组件和客户同处一个地址空间,出现冲突的可能性较大);进程外模型的优点是稳定性好,组件进程不会危及客户程序,一个组件进程可以为多个客户进程提供服务,但进程外组件开销大,且调用效率相对低一点。7.COM特性(可重用性)由于COM标准是建立在二进制代码级的,因此COM对象的可重用性与一般的面向对象语言如C+对象的重用过程不同。对COM对象的客户程序来说,它只是通过接口使用对象提供的服务,它并不知道对象内部的实现过程,因此,组件对象的重用性可建立在组件对象的行为方式上,而不是具体实现上,这是建立重用的关键。COM用两种机制实现对象的重用。假定有两个COM对象,对象1希望能重用对象2的功能,对象1被称为外部对象,对象2称为内部对象。(1)包容方式。对象1包含了对象2,当对象1需要用到对象2的功能时,可简单地把实现交给对象2来完成,虽然对象1和对象2支持同样的接口,但对象1在实现接口时实际上调用了对象2的实现。(2)聚合方式。对象1只需简单地把对象2的接口递交给客户即可,对象1并没有实现对象2的接口,但它把对象2的接口也暴露给客户程序,而客户程序并不知道内部对象2的存在。在组件对象被聚合的情况下,当客户请求它所不支持的接口或请求IUnknown接口时,它必须把控制交给外部对象,由外部对象决定客户程序的请求结果。聚合模型体现了组件软件真正意义上的重用。聚合模型实现的关键在CoCreateInstance函数和IClassFactory接口。其中pUnknownOuter参数用于指定组件对象是否被聚合,是外部组件对象的接口指针。如果pUnknownOuter=NULL,说明组件对象正常使用,否则说明被聚合使用。聚合模型下的被聚合对象的引用计数成员函数也要进行特别处理。在未被聚合的情况下,可使用一般的引用计数方法。在被聚合时,由客户调用AddRef/Release时,必须转向外部组件对象的AddRef/Release方法。这时,外部组件对象要控制被聚合的对象必须采用其它的引用计数接口。COM实现1.COM组件注册信息根据COM规范,客户程序通过COM库完成COM对象的创建,COM库则通过注册表所提供的信息进行组件的创建。注册表中包含了所有COM组件的必要信息。组件程序和客户程序都可访问注册表。组件程序把它所实现的COM对象的信息及接口信息保存到注册表中,称之为组件的注册。COM组件在HKEY_CLASSES_ROOT下。对一个进程内组件,组件CLSID子项下有InprocServer32子项(进程外组件则有LocalServer32子项),子项的默认建值就是组件程序的全路径文件名;组件CLSID子项下还包含一些与组件相关的其它信息。如,组件的版本、OLE组件的InprocHandler32子项、组件程序的图标信息、组件程序的类型库等。如果COM组件支持同一组接口,则可把它们分到同一个类中。一个组件可被分到多个类中。比如所有的自动化对象都支持IDispatch接口,则可把它们归成一类Automation Objects。类别信息也用一个GUID来描述,称为CATID。组件类别最主要的用处在于客户可快速发现本机上的特定类型的组件对象。否则,就必须检查所有的组件对象,并把组件对象载入内存,然后依次询问是否实现了必要的接口。使用组件类别,就可节省查询过程。RegSrv32.exe用于注册一个进程内组件,它调用DLL的DllRegisterServer和DllUnregisterServer函数完成组件程序的注册和注销操作。如果操作成功返回TRUE,否则返回 FALSE。对于进程外组件程序,情形稍有不同,因为它自身是个可执行程序,而且它也不能提供入口函数供其它程序使用。因此,COM规范中规定,支持自注册的进程外组件必须支持两个命令行参数 /RegServer和/UnregServer,用于注册和注销。命令行参数大小写无关(可用-替代/)。操作成功,返回0,否则,返回非0。2.类厂和DllGetObjectClass函数类厂是COM对象的生产基地,COM库通过类厂创建COM对象;对应每一个COM类,都有一个类厂,用于该COM类的对象创建操作。类厂本身也是一个COM对象,它支持一个特殊接口(IClassFactory):class IClassFactory:public IUnknownvirtual HRESULT _stdcall CreateInstance(IUnknown * pUnknownOuter,const IID& iid,void * ppv)= 0;virtual HRESULT _stdcall LockServer(BOOL bLock)= 0;CreateInstance用于创建对应的COM对象。第一个参数pUnknownOuter用于对象类被聚合的情形,一般设为NULL;第二个参数iid是对象创建完成后,客户请求的接口IID;第三个参数ppv存放返回的接口指针。LockServer用于控制组件的生存期。类厂对象是由DLL导出函数DllGetClassObject创建的。原型:HRESULT DllGetClassObject(const CLSID& clsid,const IID& iid,(void *)ppv);DllGetClassObject的第一个参数为待创建对象的CLSID。因为一个组件可能实现了多个COM对象类,所以在DllGetClassObject参数中有必要指定CLSID,以便创建正确的类厂。另两个参数iid和ppv分别指于指定接口IID和存放类厂接口指针。COM库在接到对象创建指令后,调用进程内组件的DllGetClassObject,由该函数创建类厂对象,并返回类厂对象的接口指针。COM库或客户一旦拥有类厂的接口指针,就可通过IClassFactory成员函数CreateInstance创建相应的COM对象。3.CoGetClassObject函数在COM库中有3个可用于创建COM对象的函数,分别是CoGetClassObject和CoCreateInstnace/Ex。通常情况下,客户程序调用其中之一完成对象的创建,并返回对象的初始接口指针。COM库与类厂也通过这3个函数进行交互。CoGetClassObject先找到由clsid指定的COM类的类厂,然后连接到类厂对象,如果需要的话,CoGetClassObject装入组件代码。如果是进程内组件对象,则CoGetClassObject调用DLL模块的DllGetClassObject导出函数,把参数clsid、iid和ppv传递给DllGetClassObject,并返回类厂对象的接口指针。通常情况下iid为IClassFactory的标识符IID_IClassFactory。如果类厂对象还支持其它可用于创建操作的接口,也可使用其它的接口标识符。例如,可请求IClassFactory2接口,以便在创建时,验证用户的许可证情况。IClassFactory2接口是对IClassFactory的扩展,它加强了组件创建的安全性。参数dwClsContext指定组件类别,可指定为进程内组件、进程外组件或进程内控制对象(类似于进程外组件的代理对象,主要用于OLE技术);参数iid和ppv分别对应于DllGetClassObject的参数,用于指定接口IID和存放类对象的接口指针;参数pServerInfo用于创建远程对象时指定服务器信息,在创建进程内组件对象或本地进程外组件时,设置NULL。如果CoGetClassObject创建的类厂对象位于进程外组件,情形则要复杂得多。首先CoGetClassObject启动组件进程,然后一直等待,直到组件进程把它支持的COM类对象的类厂注册到COM中。于是CoGetClassObject把COM中相应的类厂信息返回。因此,组件外进程被COM库启动时(带命令行参数/Embedding),必须把所支持的COM类的类厂对象通过 CoRegisterClassObject注册到COM中,以便COM库创建COM对象使用。当进程退出时,必须调用CoRevokeClassObject以便通知COM它所注册的类厂对象不再有效。组件程序调用CoRegisterClassObject和CoRevokeClassObject必须配对。4.CoCreateInstance/Ex函数CoCreateInstance是一个被包装过的函数,在它的内部实际上也调用了CoGetClassObject。CoCreateInstance参数clsid和dwClsContext含义与CoGetClassObject中的一致,(CoCreateInstance的iid和ppv参数与CoGetClassObject不同,一个是表示对象的接口信息,一个是表示类厂的接口信息)。参数pUnknownOuter与类厂接口的CreateInstance中对应的参数一致,主要用于对象被聚合的情况。CoCreateInstance把通过类厂创建对象的过程封装起来,客户程序只要指定对象类的CLSID和待输出的接口指针及接口ID,客户程序可不与类厂打交道。CoCreateInstance可用下面的代码实现:HRESULT CoCreateInstance(clsid,IUnknown *pUnknownOuter,DWORD dwClsContext,const IID& iid,void *ppv)IClassFactory *pCF;HRESULT hr = CoGetClassObject(clsid,dwClsContext,NULL,IID_IClassFactory,(void *)pCF);if (FAILED(hr)return hr;hr = pCF-CreateInstance(pUnknownOuter,iid,(void *)ppv);pFC-Release();return hr;从这段代码可看出,CoCreateInstance首先利用CoGetClassObject创建类厂对象,然后用得到的类厂对象的接口指针创建真正的COM对象,最后把类厂对象释放掉并返回,这样就把类厂屏蔽起来。但是,用CoCreateInstance并不能创建远程机上的对象,因为在调用CoGetClassObject时,把第三个用于指定服务器信息的参数设置为NULL。要创建远程对象,可使用CoCreateInstanceEx函数。前三个参数与CoCreateInstance一样,pServerInfo与CoGetClassOjbect的参数一样,用于指定服务器信息,最后两个参数dwCount和rgMultiQI指定了一个结构数组,可用于保存多个对象接口指针,其目的在于一次获得多个接口指针,以便减少客户程序与组件程序之间的频繁交互,这对于网络环境下的远程对象是很有意义的。5.COM库的初始化调用COM库函数之前,必须调用COM库的初始化函数:HRESULT CoInitialize(IMalloc *pMalloc);pMalloc用于指定一个内存分配器,可由应用程序指定内存分配。一般情况下,直接把参数设为NULL,则COM库将使用缺省提供的内存分配器。返回值:S_OK表示初始化成功;S_FALSE表示初始化成功,但不是本进程中首次调用。通常,一个进程对COM库只进行一次初始化,且在同一个模块单元中对COM库进行多次初始化并没有意义。唯一不需要初始化COM库的函数是获取COM库版本的函数:DWORD CoBuildVersion();返回值:高16位=主版本号;低16位=次版本号。COM程序在用完COM库服务之后,通常是在程序退出之前,一定要调用终止COM库服务函数,以便释放COM库所维护的资源:void CoUninitialize(void);6.COM库的内存管理由于COM组件程序和客户程序是通过二进制级标准建立连接的,所以在COM应用程序中凡涉及客户、COM库和组件三者之间内存交互(分配和释放不在同一个模块中)的操作必须使用一致的内存管理器。COM提供的内存管理标准,实际上是一个IMalloc接口:IID_IMalloc =00000002-0000-0000-C000-000000000046class IMalloc:public IUnknownvoid * Alloc(ULONG cb)= 0;void * Realloc(void *pv,ULONG cb)= 0;void Free(void *pv)= 0;ULONG GetSize(void *pv)= 0;/返回分配的内存大小int DidAlloc(void *pv)= 0;/确定内存指针是否由该内存管理器分配void HeapMinimize()= 0;/使堆内存尽可能减少,把没用到的内存还给操作系统,用于性能优化获得IMalloc接口指针:HRESULT CoGetMalloc(DWORD dwMemContext,IMalloc *ppMalloc);CoGetMalloc的第一个参数dwMemContext用于指定内存管理器的类型。COM库中包含两种内存管理器,一种就是在初始化时指定的内存管理器或其内部缺省的管理器,也称为作业管理器(task allocator),这种管理器在本进程内有效,要获取该管理器,应指定dwMemContext=MEMCTX_TASK;另一种是跨进程的共享分配器,由OLE系统提供,要获取这种管理器,应指定dwMemContext=MEMCTX_SHARED,使用共享管理器的便利是,可在一个进程内分配内存并传给第二个进程,在第二个进程内使用此内存甚至释放掉此内存。只要返回值为S_OK,则ppMalloc就指向了COM库的内存管理器接口指针,可使用它进行内存操作,使用完毕后,应调用Release成员函数释放控制权。COM库还封装了三个API函数,可用于内存分配和释放:void * CoTaskMemAlloc(ULONG cb);void CoTaskFree(void *pv);void CoTaskMemRealloc(void *pv,ULONG cb);这三个函数分配对应于IMalloc的三个成员函数:Alloc、Realloc 和 Free。7.组件程序的装/卸载(1)进程内组件的装载客户调用COM库的CoCreateInstance或CoGetClassObject创建COM对象时,在CoGetClassObject中,COM库根据注册表中的信息,找到类标识符CLSID对应的组件程序(DLL)的全路径并调用CoLoadLibrary,再调用组件程序的DllGetClassObject导出函数。DllGetClassObject创建相应的类厂对象,并返回类厂对象的IClassFactory接口。至此CoGetClassObject任务完成,然后客户程序或CoCreateInstance继续调用类厂对象的CreateInstance成员函数,由它负责COM对象的创建工作。(2)进程外组件的装载在COM库的CoGetClassObject中,当发现组件程序是EXE(由注册表组件对象信息中的LocalServer或LocalServer32值指定)时,COM库创建一个进程启动组件程序,并带上/Embedding命令行参数,然后等待组件程序;组件程序启动后,当它检查到/Embedding命令行参数时,就会创建类厂对象,然后调用CoRegisterClassObject把类厂对象注册到COM中。当COM库检查到组件对象的类厂之后,CoGetClassObject就返回类厂对象。由于类厂与客户运行在不同进程中,所以客户程序得到的是类厂的代理对象。一旦客户程序或COM库得到了类厂对象,就可完成组件对象的创建工作。进程内对象和进程外对象的不同创建过程仅仅影响了CoGetClassObject的实现过程,对客户程序来说是完全透明的。(3)进程内组件的卸载只有当组件程序满足了两个条件时,它才能被卸载,这两个条件是:组件中对象数为0,类厂的锁计数为0。满足这两个条件时,DllCanUnloadNow导出函数返回TRUE。COM库提供了CoFreeUnusedLibraries,它会检测当前进程中的所有组件程序,当发现某个组件程序的DllCanUnloadNow返回TRUE时,就调用FreeLibrary(实际是CoFreeLibrary)把该组件从内存中卸出。由谁来调用CoFreeUnusedLibraries呢?因为组件执行过程中它不可能把自己从内存中卸出,所以这个任务应该由客户完成。客户程序随时都可调用CoFreeUnusedLibraries完成卸载工作,通常做法是,在程序空闲处理过程中调用CoFreeUnusedLibraries,这样做既可避免程序中处处考虑对CoFreeUnusedLibraries的调用,又可使不再使用的组件程序得到及时清除,提高了资源的利用率,COM规范也推荐这种做法。(4)进程外组件的卸载进程外组件的卸载比较简单,因为组件程序运行在单独的进程中,一旦其退出的条件满足,它只要从进程的主控函数返回即可。在Windows系统中,进程的主控函数为WinMain。前面曾说过,在组件程序启动运行时,它调用CoRegisterClassObject函数,把类厂对象注册到COM中,注册之后,类厂对象的引用计数始终大于0,因此单凭类厂对象的引用计数无法控制进程的生存期,这也是引入类厂对象的加锁和减锁操作的原因。进程外组件的卸载条件与DllCanUnloadNow中的判断类似,也需要判断COM对象是否存在、以及判断是否锁计数器为0,只有当条件满足了,进程的主函数才可退出。从原则上讲,进程外组件程序的卸载就是这么简单,但实际情况可能复杂一些,因为有些组件程序在运行过程中可创建自己的对象,或包含用户界面的程序在运行过程中,用户手工关闭了进程,那么进程对这些动作的处理要复杂一些。例如,组件程序在运行过程中,用户又打开了一个文件并进行操作,那么即使原先创建的对象被释放了,而且锁计数器也为0,进程也不能退出,它必须继续为用户服务,就像是用户打开的进程一样。对这种程序,可增加一个用户控制标记flag,如果flag=FALSE,则可按简单方法直接退出程序即可;如果flag=TRUE,则表明用户参与了控制,组件进程不能马上退出,应该调用CoRevokeClassObject以便与CoRegisterClassObject调用相呼应,把进程留给用户继续进行。如果组件程序在运行过程中,用户要关闭进程,而此时并不满足进程退出条件,那么进程可采取两种办法:第一种方法,把应用隐藏起来,并设置flag=FALSE,然后组件程序继续运行直到卸载条件满足为止;另一种办法是调用CoDisconnectObject,强迫脱离对象与客户之间的关系,并强行终止进程,这种方法比较粗暴,不提倡采用,但不得已时也可使用,以保证系统完成一些高优先级的操作。8.Win32 SDK提供的头文件Unknwn.h标准接口IUnknown和IClassFacatory的IID及接口成员函数的定义Wtypes.h包含COM使用的数据结构的说明Objidl.h所有标准接口的定义,既可用于C语言风格的定义,也可用于C+语言Comdef.h所有标准接口及COM和OLE内部对象的CLSIDObjBase.h所有的COM API函数的说明Ole2.h所有经过封装的OLE辅助函数9.与COM接口有关的宏DECLARE_INTERFACE(iface)声明接口iface,它不从其它接口派生DECLARE_INTERFACE_(iface,baseiface)声明接口iface,它从接口baseiface派生STDMETHOD(method)声明接口成员函数method,函数返回类型为HRESULTSTDMETHOD_(type,method)声明接口成员函数method,函数返回类型为type10.HRESULT类型HRESULT类型的返回值反映了函数中的一些情况,定义规范如下:类别码(D30-31)反映函数调用结果:00调用成功;01包含一些信息;10警告;11错误。自定义标记(D29)反映结果是否为自定义标识,1为是,0则不是;操作码(D28-16)标识结果操作来源,在Win32平台的定义如下:#define FACILITY_NULL0#define FACILITY_RPC1#define FACILITY_DISPATCH2#define FACILITY_STORAGE3#define FACILITY_ITF4#define FACILITY_WIN327#define FACILITY_WINDOWS8#define FACILITY_SSPI9#define FACILITY_CONTROL10#define FACILITY_CERT11#define FACILITY_INTERNET12操作结果码(D15-0)反映操作的状态,WinError.h定义了Win32函数所有可能返回结果。常用的如下:S_OK函数执行成功,其值为0 (注意,与TRUE相反)S_FALSE函数执行成功,其值为1S_FAIL函数执行失败,失败原因不确定E_OUTOFMEMORY函数执行失败,失败原因为内存分配不成功E_NOTIMPL函数执行失败,成员函数没有被实现E_NOTINTERFACE函数执行失败,组件没有实现指定的接口不能简单地把返回值与S_OK和S_FALSE比较,而要用SECCEEDED和FAILED宏进行判断。可连接对象(源对象)如果一个COM对象支持一个或多个出接口,这样的对象称为可连接对象。有时也称源对象。可连接对象的出接口也是COM接口,它包含一组成员函数,每个成员函数代表了一个事件、一个通知,或一个请求。如,COM对象中的某个属性被改变时,可给客户发送一个通知;而当特定事件发生时,如定时器消息或用户鼠标操作发生时,对象产生一个事件,客户可处理这些事件。请求则是对象给客户发请求,希望客户能提供某些信息。从COM规范上讲,不管是事件、通知还是请求,都是通过出接口的成员函数来实现。之所以称为出接口,是因为这些接口不是由对象实现,而是由客户程序实现。客户实现这些接口,并把接口指针告诉对象。以后,对象则利用此接口指针与客户进行通信。在客户方,实现这些接口的对象被称为接收器。可连接对象除了为客户提供入接口外,还提供出接口,使得COM对象与其客户可实现双向通信。入接口由COM对象实现,通过入接口可接收来自客户的调用;而出接口则由客户端的接收器实现,接收来自COM对象的调用。即,COM对象根据其使用意愿定义一个接口,客户端实现该接口。入接口由COM对象定义并实现,客户通过对象的IUnknown:QueryInterface方法得到入接口,然后通过对象调用入接口的方法,最终由COM对象代表客户执行期望的行为。出接口也是由COM对象定义的,但其实现则由客户在一个接收器对象中提供,该接收器对象由客户创建。尔后,COM对象通过接收器对象调用出接口的方法,来通知客户在对象中发生的变化,或触发客户事件,或从客户端请求某些东西,或者COM对象创建者提出的任何目的。出接口的一个例子是由按钮控件定义的IButtonSink接口,将按钮事件通知其客户。例如:当用户点击按钮时,按钮对象通过客户的接收器对象调用IButtonSink:OnClick。按钮控件定义出接口,按钮的客户要处理事件,就必须在一个接收器对象中实现该出接口,并将该接收器和按钮控件连接起来。此后,当事件出现在按钮中时,按钮就会调用接收器,客户就可执行任何期望的与按钮点击关联的行为。可连接对象为对象与客户之间的通信提供了一种通用机制。除通用的可连接对象技术,COM提供许多特殊目的的接收器和站点接口(site interfaces),用于对象通知客户:发生了一些客户感兴趣的事件。例如,IAdviseSink可被COM对象用于通知客户:对象中的数据和视图发生了变化。客户、可连接对象、连接点和接收器之间的关系示意图可连接对象的基本结构可连接对象通过IConnectionPointContainer接口管理所有的出接口。对应每一个出接口,可连接对象又管理一个连接点对象,每一个连接点对象实现了IConnectionPoint接口,客户通过连接点对象建立接收器与可连接对象的连接。由于连接点对象被包含在可连接对象的内部,所以连接点对象既可访问可连接对象的内部信息,也可访问客户方的接收器。而且连接点对象的引用计数可包含在可连接对象的引用计数内,也就是说,它可直接使用可连接对象的引用计数。因此,只要连接点存在,可连接对象总不会被释放。一个可连接对象可支持多个出接口,在IConnectionPointContainer接口的成员函数中,使用一个枚举器暴露此对象所支持的所有出接口;对每一个出接口的连接点对象,在IConnectionPoint接口中也用一个枚举器管理它所连接的接收器。通过这两个枚举器的引入,使得可连接对象支持多个出接口,每个出接口支持多个与接收器的连接。1. 可连接对象(Connectable Objects)可连接对象只是整个体系结构中的一个环节,实际上可连接对象技术的整体结构包括如下内容:(1)可连接对象。实现IConnectionPointContainer接口;创建至少一个连接点对象;为客户定义一个出接口。(2)客户(Client)。对IConnectionPointContainer接口查询COM对象,以确定对象是否可连接;创建一个实现出接口的接收器对象,该出接口在可连接对象中定义。(3)接收器对象(Sink object)。实现出接口;用于建立到可连接对象的连接。2.连接点对象(Connection point object)。实现IConnectionPoint 接口,管理与客户接收器之间的连接。一个连接点可支持多个建立连接的接收器接口,且每次在该接口上进行方法调用时应该迭代连接列表,该过程被称为多播(Multi-casting)。使用可连接对象,需要理解:可连接对象、每个连接点、每个接收器、及所有枚举器都是独立对象,具有独立的IUnknow实现、独立的引用计数、独立的生命期。使用这些对象的客户必须担负起释放所有引用计数的责任。注意:一个可连接对象可支持多个客户,及一个客户中的多个接收器;同样,一个接收器可连接到多个可连接对象。客户除了实现接收器对象外,还必须建立接收器与连接点对象之间的连接关系。首先,接收器对象可支持多个与可连接对象之间的连接。即,写一个接收器对象,它可处理所有其它对象发来的事件或请求,这样便于把事件集中处理。一般来说,可连接对象不应向接收器对象请求其它接口,接收器对象只实现该出接口。在客户和可连接对象之间建立连接的步骤如下(这个过程由客户控制):1.客户首先询问对象是否为可连接对象(调用对象的QueryInterface成员函数,请求IConnectionPointContainer接口,如果请求成功,表明为可连接对象,并返回IConnectionPointContainer接口指针,否则表明不是可连接对象,不支持出接口);2.客户得到IConnectionPointContainer接口指针后,调用其成员函数,获取相应出接口的连接点对象。如果对象支持此出接口,则可得到连接点对象,然后调用连接点对象的IConnectionPoint接口的成员函数建立连接。一旦建立了连接,当可连接对象激发事件或发出请求时,接收器的成员函数就会被调用。如果客户要取消连接,同样调用IConnectionPoint接口的成员函数即可。如果对象可连接,接下来客户尝试在可连接对象中某个连接点上获得一个指向IConnectionPoint接口的指针。在ICon
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 部队会务保障课件
- 临潭县第一中学2025-2026学年上学期阶段性测试卷高三语文
- 河北省廊坊市文安县第一中学2025-2026学年高二上学期开学考试语文试卷(含答案)
- 2025-2026学年广西来宾中学高二(上)开学物理试卷(含答案)
- 20xx年集团经理个人年终述职报告范文
- 部门安全培训感悟课件
- 福彩财务合规管理-洞察及研究
- 达尔文学说课件
- 车队驾驶员安全培训课件
- 基于区块链技术的法兰供应链溯源管理在质量风险追溯中的实践困境
- 智慧养猪解决方案演示课件
- 最新中医骨伤科学考试题库及答案
- 产品形态设计课件完整
- 德国巴斯夫抗氧剂和紫外线吸收剂
- SG-A088接地装置安装工程工检验批质量验收记录
- 《芯片原理与技术》课件微流控芯片
- HY_T 0330-2022 海滩养护与修复工程验收技术方法
- 混凝土外观质量缺陷及治理措施PPT课件
- 十四条经络养生课件
- 麻醉医师资格分级授权管理能力评价与再授权制及程序培训考核试题及答案
- 钢结构厂房监理实施细则
评论
0/150
提交评论