最近在做COM,有一段没理解,在进程A中CoRegisterClassObject,并没有使用特殊标志,那在进程B中CoGetClassObject是怎么准确得到刚注册的COM。
带着问题找到了这篇文章,关键在REGCLS_SINGLEUSE。但随后又说“该标记枚举不适用于作为本地服务器的情况。”表示不理解,因为在我的程序中是做为本地服务器来使用的。
一、IDL 的简单示例
import "oaidl.idl";
import "ocidl.idl";
[
object,
uuid("F9AB2225-ACEB-4642-A5BC-BAB98E289E7A"),
helpstring("ISomeInterface"),
pointer_default(unique)
]
interface ISomeInterface : IUnknown
{
HRESULT
SomeMethodA([in]int nLen, [in, out, size_is(nLen)]LPOLESTR
wcBuf);
HRESULT
SomeMethodB(REFIID riid, [out, iid_is(riid)]void** ppv);
};
二、MIDL 编译器
使用 midl *.idl 编译 IDL 文件
midl.exe /env win32 /notlb /h "LocExe_h.h"
".\LocExe.idl"
MIDL 编译器将生成类似以下几个文件:
1)LocExe_h.h 头文件 /h
2)LocExe_i.c IID 文件 /iid
3)LocExe_p.c 代理文件 /proxy
4)dlldata.c DLL 数据文件 /dlldata
三、使用 MIDL 编译器生成的代理存根文件创建 DLL
MIDL 仅生成代理存根所需的源文件,最终要编译成代理 DLL 并在注册表登记组件和接口条目。
1)添加 .def 模块定义文件:
LIBRARY
LocExe_Proxy
EXPORTS
DllGetClassObject
PRIVATE
DllCanUnloadNow
PRIVATE
GetProxyDllInfo
PRIVATE
DllRegisterServer
PRIVATE
DllUnregisterServer
PRIVATE
2)为 dlldata.c 数据文件的命令行添加定义:(有了4)中的设定,这一步其实不需要做)
/D REGISTER_PROXY_DLL
命令行在 Visual C++ 7.1 中位于特定文件的“配置属性 => C/C++ => 命令行”选项页
3)为代理存根 DLL 的链接器选项提供输入库:
rpcrt4.lib uuid.lib
4)工程 => 属性 =>
C/C++ => preprocessor =>
preprocessor define中,追加预处理器定义:
REGISTER_PROXY_DLL;_WIN32_WINNT=0x500;WIN32;
5)可选地为项目添加生成后行为以便简化注册
项目 => 配置属性
=>
生成事件
=> 生成后事件
命令行:regsvr32 -s $(OutDir)/LocExe_Proxy.dll
四、本地服务器
1)用户在 CoCreateInstance 中指定请求的服务器上下文:
CLSCTX_INPROC_SERVER 或 CLSCTX_LOCAL_SERVER 等。
通过 CLSCTX_LOCAL_SERVER 以请求本地服务器。
2)取消入口点函数
EXE 无法输出函数,需要替换 DLL 相应导出函数的功能。
首先、EXE 服务器不需要类似 DllCanUnloadNow
的被动卸载机制,它管理自己的生存期,并在检测到有关资源的引用计数下降为 0 时停止执行。
其次、DllRegisterServer 以及 DllUnregisterServer
则可以通过检查有关命令行文件,并使用相同的代码完成组件注册。
最后,EXE 服务器通过 CoRegisterClassObject 以及 CoUnregisterClassObject
登记类厂对象。
因为 EXE
服务器依赖于类厂的主动注册并仅在组件接收到停止服务的通知后方取消类厂的注册,所以类厂对象的计数并不作为服务器生存期管理的依据。
五、CoRegisterClassObject 的标志
STDAPI CoRegisterClassObject(
REFCLSID rclsid,
IUnknown * pUnk,
DWORD dwClsContext,
DWORD flags,
LPDWORD lpdwRegister
);
flags 使用了 REGCLS 枚举,以控制注册类厂对象的连接类型。
1)REGCLS 枚举
REGCLS_SINGLEUSE
类对象一旦被用户取得,即从列表中移除,但不影响对象引用程序履行其取消注册 CoRevokeClassObject 的职责。
该标记枚举不适用于作为本地服务器的情况。
REGCLS_MULTIPLEUSE
作为供多个用户应用程序连接的本地服务器。此类服务器的类厂对象总是需要被注册为进程中服务器,即使在
CLSCTX_LOCAL_SERVER 上下文中,不管 CLSCTX_INPROC_SERVER 是否被显式设置。
REGCLS_MULTI_SEPARATE
分
离作为本地服务器以及作为进程内服务器的上下文,设置此选项,并注册为在本地服务器上下文运行的类厂将不能作为进程内服务器运行,反之亦然。与
REGCLS_MULTIPLEUSE
不同,该选项不会为本地服务器上下文自动登记为进程内服务器。使用该选项为进程内服务器登记类厂对象,可以在 SCM
未参与的情况下有效地创建实例。
当应用程序不需区别本地及进程中对象实例的应用,则不必使用该选项进行创建,事实上,从 MULTIPLEUSE 更改到
MULTI_SEPARATE 并且不注册为进程中服务器时只会增加网络传输的来回。
2)REGCLS_MULTI_SEPARATE 和 REGCLS_MULTIPLEUSE 的区别
REGCLS_MULTI_SEPARATE 当独立设置本地服务器的注册时,且没有注册为进程中服务器,则最少需要对应的 DLL
进程中服务器的存在,以便服务器中的有关用户可以成功初始化进程内服务器。反之,两种标志在以下情况是等效的:
CoRegisterClassObject(CLSID_SomeObject, pClsUnk,
CLSCTX_LOCAL_SERVER, REGCLS_MULTIUSE, &pdwReg);
CoRegisterClassObject(CLSID_SomeObject, pClsUnk,
CLSCTX_LOCAL_SERVER | CLSCTX_INPROC_SERVER, REGCLS_MULTI_SEPARATE,
&pdwReg);
六、远程访问能力
已实现为本地服务器的 COM 组件即是一类标准的 DCOM
组件,通过向远端的机器的复制、注册,并更改本地相应组件的运行条件即可作为远程服务器运行。
DCOM 配置工具 dcomcnfg.exe 可以管理当前分布式 COM 的配置,以决定作为本地或者远程执行。
以下列出了允许组件具有远程访问能力的步骤:
1)编译进程间服务器以及用户应用程序,并编译必要的代理 DLL,并在本地注册
2)将进程间服务器的模块以及存在的代理 DLL 复制到远端系统,而后注册
3)运行 dcomcnfg.exe,更改其中的属性
位置中取消在本地计算机上运行,选择在特定计算机中运行,并填写目标主机名或地址。
标识更改为作为交互式用户运行。
七、dcomcnfg.exe 完成的工作
DCOM 管理应用程序主要将组件的行为写入注册表
1)在组件的 CLSID 标识下添加命名值 AppID
HKEY_CLASSES_ROOT\CLSID\{375E77E3-5BF4-4071-9175-A2B23F010F30}::AppID
= {375E77E3-5BF4-4071-9175-A2B23F010F30};
2)AppID 也是一个 GUID,并被保存在注册表的 AppID 节点之下
HKEY_CLASSES_ROOT\AppID\{375E77E3-5BF4-4071-9175-A2B23F010F30} =
LocDll sample component CA;
::RemoteServerName = Other machine;
::RunAs = Interactive User;
3)应用程序名称到 AppID 的索引:
HKEY_CLASSES_ROOT\AppID\LocExe.exe::AppId =
{375E77E3-5BF4-4071-9175-A2B23F010F30};
八、工作机理
对于作为本地服务器运行的用户,COM 将优先查询 AppID 注册表节点下的运行行为。即用户指定
CLSCTX_LOCAL_SERVER 作为组件的运行上下文,并本地登记了相关组件的远程信息,将作为远程服务器运行。
九、略过在代码中指定远端服务器以及回传多个接口以减少通信来回的部分。