如何加载 COM dll 模块并将其接口公开为进程外服务器

Posted

技术标签:

【中文标题】如何加载 COM dll 模块并将其接口公开为进程外服务器【英文标题】:How to load COM dll modules and expose their interfaces as out of proccess servers 【发布时间】:2012-07-06 18:57:36 【问题描述】:

我有一个基于 ATL 服务 VC++2010 模板的进程外服务器。现在我不会通过动态加载包含自己的 COM 类的附加 dll 来扩展他的 COM 接口。 要加载的 dll 基于 ATL dll VC++2010 模板,包含一个简单的 ATL 对象“IMModule”。我更改了相应的 .rgs 文件,通过添加 LocalServer 部分和服务器的 AppID 将类从 dll 连接到 EXE 服务器,如下所示:

HKCR

  NoRemove CLSID
  
    ForceRemove 59276614-A811-4D27-B131-514656E643D3 = s 'IMModule Class'
    
      ForceRemove Programmable
      LocalServer32 = s 'path to the service exe'
      
    val ServerExecutable = s 'path to the service exe'
      
      TypeLib = s '250685C7-CBD3-4FF8-A3A6-2AF668794CFC'
      Version = s '1.0'
      val AppID = s '7EFD508A-53C6-4EA0-B21A-D29277B86CBC'
    
  

在加载 dll 后由服务调用的 dll init() 方法中,我调用 CoRegisterClassObject 来注册 IMModule 类对象。但我不确定如何获取 IUnknown 接口指针( CoRegisterClassObject 的第二个参数)。我尝试了以下方法:

CIMModule::_ClassFactoryCreatorClass* pClassFak = 
    new CIMModule::_ClassFactoryCreatorClass;
IUnknown* pUnk;
HRESULT hr =
pClassFak->CreateInstance(CIMModule::_ClassFactoryCreatorClass::CreateInstance, 
                            IID_IIMModule, (LPVOID*)&pUnk);

但是对 CreateInstance 的调用失败并出现 E_NOINTERFACE。 那么,如何将我在 dll 中实现的 IMModule 类注册为可用于来自我的进程外服务器的 COM 客户端?

【问题讨论】:

你用一组接口实现了一些 COM 类。您是否希望此类通过动态加载的模块以某种方式实现其他接口? 简短回答:不。长答案:COM 服务器本身不会提供任何 COM 接口。服务器将加载向客户端提供 COM 接口的模块(实现为 DLL)。 IMModule 类是此类模块 DLL 的一部分。启动时,服务器加载并初始化模块。据我了解,DLL 必须在初始化期间在服务器上下文中注册 IMModule COM 类。这就是对 CreateInstance 的调用失败的地方。 如果服务器本身没有公开任何接口,这会将问题重新标记为加载模块并使用它们的接口。您不需要为此进行任何特殊注册,只需定期注册 COM 服务器、其 coclasses 和类型库。 我改了tite,希望现在更清楚了。 你的主“伞”服务器应该暴露为进程外服务器,并且在它的ineinterface中将有一个方法FindMeAnInterface(<args>, IDispatch** ppDispatch),通过该方法伞服务器将返回模块的接口。模块将具有常规接口,它们只需要它们的接口可编组(即使用类型库或代理/存根类),以便它们可以超出进程边界。 【参考方案1】:

在 Roman.R 的帮助下,我得到了我需要的行为。我不能说谢谢,@roman-r。我将准确描述我所做的事情,所以也许有人可以追溯这些步骤并给我一些回应。

首先,我创建了一个基于 ATL 的 Windows 服务(名为 UmbrellaService)。在 UmbrellaService 中,我添加了一个名为 Control 的简单 ATL-Object 并添加了方法:

 FindMeAnInterface(BSTR moduleName, IDispatch** ppDispach);

这就是 VC++ 向导的全部内容。然后我通过添加以下内容来修复 Control.rgs 文件:

 val AppID = s '%APPID%'

为什么 VC++ 经过 17 年的发展仍然存在这样的错误? (看 CoCreateInstance does not start or connect to ATL COM service) 然后我创建了一个名为 MyModule 的 ATL-dll 项目,其中包含一个“模块” 里面有简单的 ATL-Object。 Module 类有一个方法

testMethod (LONG a, LONG b, LONG* sum)"

MyModule dll 已注册为进程内服务器。此外,该 dll 有一些类 这使得 dll 成为我需要的插件。

在 UmbrellaService 的 PreMessageLoop 方法中,MyModule dll 将使用 LoadLibrary 加载,并通过 GetProcAddress 获取工厂创建方法的地址。工厂创建方法返回一个依赖于插件的 FactoryClass,它充当插件入口点。这是我独立于 COM 的插件机制。

现在要通过 UmbrellaService 接口从插件 dll 导出模块接口,我做了以下操作:在 FactoryClass 上添加方法:

IDispatch* getInterface();

在getInterface方法中我调用了

CoCreateInstance(__uuidof(Module), NULL , CLSCTX_INPROC_SERVER , __uuidof(IDispatch), (VOID**) &pDispatch); 

并返回获取到的IDispatch接口。在将传递给 FindMeAnInterface 的名称与 FactoryClass 提供的名称进行比较之后,在 UmbrellaService 的 Control::FindMeAnInterface 方法中调用 FactoryClass::getInterface 方法。 FindMeAnInterface 将当时获得的 IDispatch 指针返回给客户端。

在客户端,我从 UmbrellaService 导入 tlb 文件,从适当的插件 dll 导入 tlb。我调用 testMethod 如下:

IControlPtr pControl(__uuidof(Control));
_bstr_t moduleName("Module");
IDispatchPtr moduleDisp = pControl->FindMeAnInterface(moduleName);
IModulePtr pModule(moduleDisp );
LONG res = pModule->testMethod(42,23);

这一切确实有效,但我不确定这是否是这样做的方法。我错过了一些关于引用计数的事情吗?插件DLL会被加载两次吗?第一次通过我的插件机制,第二次通过 CoCreateInstance?还有什么需要注意的吗?

感谢您的帮助!

【讨论】:

【参考方案2】:

到目前为止我找不到我的代码。但我确实查看了我最喜欢的网站之一,The Code Project。它曾经很流行,尤其是在 COM 等较旧的技术中(是的,确实如此)。我希望您已经确信必须使用 COM 而不是新的 WFC 或其他技术。

请检查良好的文档和示例代码@ATL COM EXE doc。我相信我使用这个网页作为我过去项目的开始。

祝你好运,玩得开心。

【讨论】:

以上是关于如何加载 COM dll 模块并将其接口公开为进程外服务器的主要内容,如果未能解决你的问题,请参考以下文章

kernel32.dll 这个系统模块

DLLHijack漏洞原理

VC++ 解决dll库动态库加载失败问题(调用LoadLibrary加载失败)(附源码)

枚举进程加载模块

枚举进程加载模块

如何查找加载到进程中的DLL及其位置等