Com 组件的装载和卸载
进程内组件的装载:
客户程序调用COM 库的 CoCreateInstance 或 CoGetClassObject 函数创建 COM 对象,在 CoGetClassObject 函数中,COM 库根据系统注册表中的信息,找到类标识符 CLSID 对应的组件程序(DLL 文件)的全路径,然后调用 LoadLibrary(实际上是 CoLoadLibrary)函数,并调用组件程序的 DllGetClassObject 引出函数。DllGetClassObject 函数创建相应的类厂对象,并返回类厂对象的 IClassFactory 接口。至此 CoGetClassObject 函数的任务完成,然后客户程序或者 CoCreateInstance 函数继续调用类厂对象的 CreateInstance 成员函数,由它负责 COM 对象的创建工作。
CoCreateInstance
|-CoGetClassObject
|-Get CLSID -> DLLfile path
|-CoLoadLibrary
|-DLLfile.DllGetClassObject
|-return IClassFactory
|-IClassFactory.CreateInstnace
进程外组件的装载:
在 COM 库的 CoGetClassObject 函数中,当它发现组件程序是 EXE 文件(由注册表组件对象信息中的 LocalServer 或 LocalServer32 值指定)时,COM 库创建一个进程启动组件程序,并带上“/Embedding”命令行参数,然后等待组件程序;而组件程序在启动后,当它检查到“/Embedding”命 令行参数后,就会创建类厂对象,然后调用 CoRegisterClassObject 函数把类厂对象注册到 COM 中。当 COM 库检查到组件对象的类厂之后,CoGetClassObject 函数就把类厂对象返回。由于类厂与客户程序运行在不同的进程中,所以客户程序得到的是类厂的代理对象。一旦客户程序或 COM 库得到了类厂对象,它就可以完成组件对象的创建工作。
进程内对象和进程外对象的不同创建过程仅仅影响了 CoGetClassObject 函数的实现过程,对于客户程序来说是完全透明的。
CoGetClassObject
|-LocalServer/LocalServer32
|-Execute EXE /Embedding
|-Create class factory
|-CoRegisterClassObject ( class factory )
|-return class factory (proxy)
进程内组件的卸载:
只有当组件程序满足了两个条件时,它才能被卸载,这两个条件是:组件中对象数为 0,类厂的锁计数为 0。满足这两个条件时,DllCanUnloadNow 引出函数返回 TRUE。COM 提供了一个函数 CoFreeUnusedLibraries,它会检测当前进程中的所有组件程序,当发现某个组件程序的 DllCanUnloadNow 函数返回 TRUE 时,就调用 FreeLibrary 函数(实际上是 CoFreeLibrary 函数)把该组件从程序从内存中卸出。
该由谁来调用 CoFreeUnusedLibraries 函数呢?因为在组件程序执行过程中,它不可能把自己从内存中卸出,所以这个任务应该由客户来完成。客户程序随时都可以调用 CoFreeUnusedLibraries 函数完成卸出工作,但通常的做法是,在程序的空闲处理过程中调用 CoFreeUnusedLibraries 函数,这样做既可以避免程序中处处考虑对 CoFreeUnusedLibraries 函数的调用,又可以使不再使用的组件程序得到及时清除,提高资源的利用率,COM 规范也推荐这种做法。
进程外组件的卸载:
进程外组件的卸载比较简单,因为组件程序运行在单独的进程中,一旦其退出的条件满足,它只要从进程的主控函数返回即可。在 Windows 系统中,进程的主控函数为 WinMain。
前面曾经说过,在组件程序启动运行时,它调用 CoRegisterClassObject 函数,把类厂对象注册到 COM 中,注册之后,类厂对象的引用计数始终大于 0,因此单凭类厂对象的引用计数无法控制进程的生存期,这也是引入类厂对象的加锁和减锁操作的原因。进程外组件的载条件与 DllCanUnloadNow 中的判断类似,也需要判断 COM 对象是否还存在、以及判断是否锁计数器为 0,只有当条件满足了,进程的主函数才可以退出。
从原则上讲,进程外组件程序的卸载就是这么简单,但实际上情况可能复杂一些,因为有些组件程序在运行过程中可以创建自己的对象,或者包含用户界面的程序在
运行过程中,用户手工关闭了进程,那么进程对这些动作的处理要复杂一些。例如,组件程序在运行过程中,用户又打开了一个文件并进行操作,那么即使原先创建
的对象被释放了,而且锁计数器也为 0,进程也不能退出,它必须继续为用户服务,就像是用户打开的进程一样。对这种程序,可以增加一个“用户控制”标记
flag,如果 flag 为 FALSE,则可以按简单的方法直接退出程序即可;如果 flag 为
TRUE,则表明用户参与了控制,组件进程不能马上退出,但应该调用 CoRevokeClassObject 函数以便与
CoRegisterClassObject 调用相响呼应,把进程留给用户继续进行。
如果组件程序在运行过程中,用户要关闭进程,而此时并不满足进程退出条件,那么进程可以采取两种办法:第一种方法,把应用隐藏起来,并把 flag 标记设置为 FALSE,然后组件程序继续运行直到卸载条件满足为止;另一种办法是,调用 CoDisconnectObject 函数,强迫脱离对象与客户之间的关系,并强行终止进程,这种方法比较粗暴,不提倡采用,但不得已时可以也使用,以保证系统完成一些高优先级的操作。
-------------------------------------------------------------------------------
COM 库常用函数
-------------------------------------------------------------------------------
初始化函数 CoBuildVersion 获得 COM 库的版本号
CoInitialize COM 库初始化
CoUninitialize COM 库功能服务终止
CoFreeUnusedLibraries 释放进程中所有不再使用的组件程序
GUID 相关函数 IsEqualGUID 判断两个 GUID 是否相等
IsEqualIID 判断两个 IID 是否相等
IsEqualCLSID 判断两个 CLSID 是否相等 (*为什么要3个函数)
CLSIDFromProgID 字符串组件标识转换为 CLSID 形式
StringFromCLSID CLSID 形式标识转化为字符串形式
IIDFromString 字符串转换为 IID 形式
StringFromIID IID 形式转换为字符串
StringFromGUID2 GUID 形式转换为字符串(*为什么有 2)
对象创建函数 CoGetClassObject 获取类厂对象
CoCreateInstance 创建 COM 对象
CoCreateInstanceEx 创建 COM 对象,可指定多个接口或远程对象
CoRegisterClassObject 登记一个对象,使其它应用程序可以连接到它
CoRevokeClassObject 取消对象的登记
CoDisconnectObject 断开其它应用与对象的连接
内存管理函数 CoTaskMemAlloc 内存分配函数
CoTaskMemRealloc 内存重新分配函数
CoTaskMemFree 内存释放函数
CoGetMalloc 获取 COM 库内存管理器接口
-------------------------------------------------------------------------------
HRESULT 类型
-------------------------------------------------------------------------------
大多数 COM 函数以及一些接口成员函数的返回值类型均为 HRESULT 类型。HRESULT 类型的返回值反映了函数中的一些情况,其类型定义规范如下:
31 30 29 28 16 15 0
|-----|--|------------------------|-----------------------------------|
类别码 (30-31) 反映函数调用结果:
00 调用成功
01 包含一些信息
10 警告
11 错误
自定义标记(29) 反映结果是否为自定义标识,1 为是,0 则不是;