3 com里的代理存根库,是怎么被com库找到的?
在 COM(Component Object Model)中,代理存根(Proxy-Stub)库的作用是实现跨进程(或跨机器)接口调用的参数序列化(Marshal)和反序列化(Unmarshal)。当客户端调用远程对象的方法时,COM 库需要找到并加载对应的代理存根库,以确保接口参数的正确传递。以下是其工作原理:
1. 接口与代理存根的注册关系
COM 通过注册表(Registry)管理接口(Interface)和对应的代理存根库之间的映射关系。关键步骤如下:
(1) 接口定义 (IID) 的注册
- 每个 COM 接口都有一个唯一的 IID(Interface Identifier),例如
IID_IMyInterface
。 - 在注册表中,接口的 IID 会注册在路径:
HKEY_CLASSES_ROOT\Interface\{IID}
例如:HKEY_CLASSES_ROOT\Interface\{00000001-0000-0000-C000-000000000046}\
(2) 代理存根的 CLSID 绑定
- 在接口的注册表项下,存在一个子键
ProxyStubClsid32
(或ProxyStubClsid
,取决于系统位数)。
例如:HKEY_CLASSES_ROOT\Interface\{IID}\ProxyStubClsid32 (Default) = {ProxyStub_CLSID}
- 这里的
ProxyStubClsid32
值指向一个 CLSID,即代理存根库的类标识符。
2. 代理存根库的 CLSID 到 DLL 的映射
- COM 根据
ProxyStubClsid32
找到对应的 CLSID 后,会进一步在注册表中查找该 CLSID 对应的 DLL 路径:HKEY_CLASSES_ROOT\CLSID\{ProxyStub_CLSID}\InProcServer32 (Default) = "C:\Path\to\YourProxyStub.dll"
- 这个路径指向代理存根库的 DLL 文件。
3. COM 库加载代理存根库
- 客户端调用远程接口方法时,COM 运行时发现需要跨进程/跨机器通信。
- 查询注册表:通过接口的 IID,找到关联的代理存根 CLSID。
- 加载 DLL:根据 CLSID 对应的
InProcServer32
路径,加载代理存根库(DLL)。 - 创建代理对象:在客户端进程中创建代理(Proxy),负责将参数序列化并转发到服务端。
- 服务端存根处理:服务端进程中的存根(Stub)接收数据,反序列化后调用实际对象的方法。
4. 特殊情况
- 系统内置代理存根:某些标准接口(如
IUnknown
、IDispatch
)的代理存根由系统(ole32.dll
或rpcrt4.dll
)直接提供,无需额外注册。 - 自定义代理存根:开发者需通过 IDL(Interface Definition Language)定义接口,编译生成代理存根代码,并编译为 DLL 后注册到系统。
- 类型库(TypeLib):若接口使用通用调度(Automation),可能通过类型库的
marshal
关键字自动生成代理存根。
5. 关键工具与流程
- IDL 编译:使用 MIDL(Microsoft Interface Definition Language Compiler)生成代理存根代码。
- 注册 DLL:通过
regsvr32.exe
注册代理存根库(调用其DllRegisterServer
函数)。 - 调试工具:使用
OleView
或Registry Editor
检查接口和代理存根的注册是否正确。
总结
COM 库通过注册表中的 IID → ProxyStubClsid32 → CLSID → InProcServer32 链,定位并加载代理存根库。这一机制使得跨进程/跨机器的接口调用对开发者透明,只需确保代理存根库正确实现并注册即可。
代理存根的 CLSID 绑定
在接口的注册表项下,存在一个子键 ProxyStubClsid32(或 ProxyStubClsid,取决于系统位数)。
例如:
复制
HKEY_CLASSES_ROOT\Interface\{IID}\ProxyStubClsid32
(Default) = {ProxyStub_CLSID}
这里的 ProxyStubClsid32 值指向一个 CLSID,即代理存根库的类标识符。
这个对应关系,在哪注册到注册表里的?
在 COM 中,代理存根(Proxy-Stub)的 CLSID 绑定关系(即接口 IID 到代理存根 CLSID 的映射)是通过以下两种方式注册到系统注册表中的:
1. 自动注册(通过 regsvr32
注册代理存根 DLL)
当开发者编译代理存根库(例如 YourProxyStub.dll
)后,会调用 regsvr32.exe
工具注册该 DLL。此操作会触发 DLL 的 DllRegisterServer
函数,该函数负责将以下信息写入注册表:
(1) 注册接口的 IID 与代理存根 CLSID 的绑定关系
- 代理存根库的代码中会包含一个
.rgs
脚本文件(由 MIDL 生成),该脚本定义了如何将接口的 IID 与代理存根 CLSID 绑定。例如:HKCR { Interface\{IID_YourInterface} { ProxyStubClsid32 = s '{ProxyStub_CLSID}' } }
- 当
DllRegisterServer
执行时,会根据.rgs
脚本在注册表中创建如下键值:HKEY_CLASSES_ROOT\Interface\{IID_YourInterface}\ProxyStubClsid32 (Default) = "{ProxyStub_CLSID}"
(2) 注册代理存根 CLSID 的 DLL 路径
- 同时,代理存根库的
.rgs
脚本也会注册其自身的 CLSID 和 DLL 路径:HKCR { CLSID\{ProxyStub_CLSID} { InProcServer32 = s '%MODULE%' // 自动替换为 DLL 的完整路径 } }
- 注册后,注册表中会生成:
HKEY_CLASSES_ROOT\CLSID\{ProxyStub_CLSID}\InProcServer32 (Default) = "C:\Path\to\YourProxyStub.dll"
2. 手动注册(通过 IDL 和 MIDL 生成)
如果开发者使用 IDL(Interface Definition Language) 定义接口,并通过 MIDL(Microsoft IDL Compiler) 生成代理存根代码,MIDL 会自动生成以下内容:
(1) 自动生成注册表脚本
- MIDL 编译 IDL 文件时,会生成一个
YourProxyStub.rgs
文件,其中包含注册接口 IID 和代理存根 CLSID 的脚本。 - 例如,对于如下 IDL 定义:
MIDL 生成的[ uuid(IID_YourInterface), // 接口的 IID helpstring("Your Interface"), proxyStubClsid(ProxyStub_CLSID) // 显式指定代理存根的 CLSID ] interface IYourInterface : IUnknown { ... }
.rgs
文件会包含ProxyStubClsid32
的注册逻辑。
(2) 编译后的注册流程
- 开发者编译生成的代理存根代码后,调用
regsvr32.exe
注册 DLL,此时 MIDL 生成的.rgs
脚本会被执行,自动写入注册表。
3. 系统内置代理存根的注册
对于系统内置接口(如 IUnknown
、IDispatch
),它们的代理存根 CLSID 绑定关系已由 Windows 在系统安装时预先注册,无需开发者干预。例如:
IUnknown
的代理存根由ole32.dll
直接实现。- 注册表路径:
HKEY_CLASSES_ROOT\Interface\{00000000-0000-0000-C000-000000000046}\ProxyStubClsid32 (Default) = "{00000320-0000-0000-C000-000000000046}"
4. 关键验证步骤
如果代理存根未正确注册,可通过以下方式检查:
- 检查接口的
ProxyStubClsid32
值是否存在:reg query HKCR\Interface\{IID_YourInterface}\ProxyStubClsid32
- 检查代理存根 CLSID 的 DLL 路径是否正确:
reg query HKCR\CLSID\{ProxyStub_CLSID}\InProcServer32
- 使用工具验证:
- 使用
OleView
(OLE/COM Object Viewer)查看接口的代理存根信息。 - 使用
regsvr32.exe
重新注册代理存根 DLL。
- 使用
总结
- 自动注册:通过
regsvr32.exe
调用代理存根 DLL 的DllRegisterServer
函数,执行 MIDL 生成的.rgs
脚本,将接口 IID 与代理存根 CLSID 的绑定关系写入注册表。 - 手动生成:开发者通过 IDL 定义接口并指定
proxyStubClsid
,MIDL 生成对应的注册表脚本。 - 系统内置:Windows 预先注册了标准接口的代理存根。