3. 接口对称性。如果从一个接口指针查询到另一个接口指针,则从第二个接口指针再回到第一个接口指针必定成功,比如:
pIDictionary->QueryInterface(IID_SpellCheck, (void **)&pISpellCheck);
如果查找成功的话,则再从 pISpellCheck 查回 IID_Dictionary 接口肯定成功。
4. 接口传递性。如果从第一个接口指针查询到第二个接口指针,从第二个接口指针可以查询到第三个接口指针,则从第三个接口指针一定可以查询到第一个接口指针。
5. 接口查询时间无关性。如果在某一个时刻可以查询到某一个接口指针,则以后任何时间再查询同样的接口指针,一定可以查询成功。
总之,不管我们从哪个接口出发,我们总可以到达任何一个接口,而且我们也总可以回到最初的那个接口。
===============================================================================
⊙ 第三章 COM 的实现
===============================================================================
COM 组件注册信息
-------------------------------------------------------------------------------
当前机器上所有组件的信息 HKEY_CLASS_ROOT/CLSID
进程内组件 HKEY_CLASS_ROOT/CLSID/guid/InprocServer32
进程外组件 HKEY_CLASS_ROOT/CLSID/guid/LocalServer32
组件所属类别(CATID) HKEY_CLASS_ROOT/CLSID/guid/Implemented Categories
COM 接口的配置信息 HKEY_CLASS_ROOT/Interface
代理 DLL/存根 DLL HKEY_CLASS_ROOT/CLSID/guid/ProxyStubClsid
HKEY_CLASS_ROOT/CLSID/guid/ProxyStubClsid32
类型库的信息 HKEY_CLASS_ROOT/TypeLib
字符串命名 ProgID HKEY_CLASS_ROOT/ (例如 "COMCTL.TreeCtrl")
组件 GUID HKEY_CLASS_ROOT/COMTRL.TreeControl/CLSID
缺省版本号 HKEY_CLASS_ROOT/COMTRL.TreeControl/CurVer
(例如 CurVer = "COMTRL.TreeCtrl.1", 那么
HKEY_CLASS_ROOT/COMTRL.TreeControl.1 也存在)
当前机器所有组件类别 HKEY_CLASS_ROOT/Component Categories
COM 提供两个 API 函数 CLSIDFromProgID 和 ProgIDFromCLSID 转换 ProgID 和 CLSID。
如果 COM 组件支持同样一组接口,则可以把它们分到同一类中,一个组件可以被分到多个类中。比如所有的自动化对象都支持 IDispatch 接口,则可以把它们归成一类“Automation Objects”。类别信息也用一个 GUID 来描述,称为 CATID。组件类别最主要的用处在于客户可以快速发现机器上的特定类型的组件对象,否则的话,就必须检查所有的组件对象,并把组件对象装入到内存中实例化,然后依次询问是否实现了必要的接口,现在使用了组件类别,就可以节省查询过程。
-------------------------------------------------------------------------------
注册 COM 组件
-------------------------------------------------------------------------------
RegSrv32.exe 用于注册一个进程内组件,它调用 DLL 的 DllRegisterServer 和 DllUnregisterServer 函数完成组件程序的注册和注销操作。如果操作成功返回 TRUE,否则返回 FALSE。
对于进程外组件程序,情形稍有不同,因为它自身是个可执行程序,而且它也不能提供入口函数供其他程序使用。因此,COM 规范中规定,支持自注册的进程外组件必须支持两个命令行参数 /RegServer 和 /UnregServer,以便完成注册和注销操作。命令行参数大小写无关,而且 “/” 可以用 “-” 替代。如果操作成功,程序返回 0,否则,返回非 0 表示失败。
-------------------------------------------------------------------------------
类厂和 DllGetObjectClass 函数
-------------------------------------------------------------------------------
类厂(class factory)是 COM 对象的生产基地,COM 库通过类厂创建 COM 对象;对应每一个 COM 类,有一个类厂专门用于该 COM 类的对象创建操作。类厂本身也是一个 COM 对象,它支持一个特殊的接口 IClassFactory:
class IClassFactory : public IUnknown
{
virtual 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,以便创建正确的 class factory。另两个参数 iid 和 ppv 分别指于指定接口 IID 和存放类厂接口指针。
COM 库在接到对象创建的指令后,它要调用进程内组件的 DllGetClassObject 函数,由该函数创建类厂对象,并返回类厂对象的接口指针。COM 库或客户一旦拥有类厂的接口指针,它们就可以通过 IClassFactory 的成员函数 CreateInstance 创建相应的 COM 对象。
-------------------------------------------------------------------------------
CoGetClassObject 函数
-------------------------------------------------------------------------------
在 COM 库中,有三个 API 可用于对象的创建,它们分别是 CoGetClassObject、CoCreateInstnace 和 CoCreateInstanceEx。通常情况下,客户程序调用其中之一完成对象的创建,并返回对象的初始接口指针。COM 库与类厂也通过这三个函数进行交互。
HRESULT CoGetClassObject(const CLSID& clsid, DWORD dwClsContext,
COSERVERINFO *pServerInfo, const IID& iid, (void **)ppv);
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 函数必须配对,以保证 COM 信息的一致性。
-------------------------------------------------------------------------------
CoCreateInstance / CoCreateInstanceEx 函数
-------------------------------------------------------------------------------
HRESULT CoCreateInstance(const CLSID& clsid, IUnknown *pUnknownOuter,
DWORD dwClsContext, const IID& iid, (void **)ppv);
CoCreateInstance 是一个被包装过的辅助函数,在它的内部实际上也调用了 CoGetClassObject 函数。CoCreateInstance 的参数 clsid 和 dwClsContext 的含义与 CoGetClassObject 相应的参数一致,(CoCreateInstance 的 iid 和 ppv 参数与 CoGetClassObject 不同,一个是表示对象的接口信息,一个是表示类厂的接口信息)。参数 pUnknownOuter 与类厂接口的 CreateInstance 中对应的参数一致,主要用于对象被聚合的情况。CoCreateInstance 函数把通过类厂创建对象的过程封装起来,客户程序只要指定对象类的 CLSID 和待输出的接口指针及接口 ID,客户程序可以不与类厂打交道。CoCreateInstance 可以用下面的代码实现:
上一页 1 2 3 4 5 6 下一页 |