如何使用 COM 在两个进程之间传输数据(对于 IPC - 进程间通信)

How to use COM to transfer Data between two processes (for IPC - inter process communication)

我对 COM 的了解一般,想了解 COM 如何帮助进行数据传输。假设有两个进程,Process-A 和 Process-B 并且他们都希望彼此共享一些数据,当然有很多 RPC 机制,但我想使用 COM。

  1. 您不能创建 COM dll,因为那样它会变得特定于进程并且无法使用
  2. 我们能否创建一个单一的 COM EXE 服务器并将结构包装在 COM CoClass 中并将其成员公开为属性,然后...不知道如何做?

以上是我的理解,有大神能帮我理清一下这个题目的理解吗?基本上我想使用 COM

在两个进程之间共享一个数据结构

更新:当一个对象调用另一个对象的方法(在参数中传递信息)时,我们说第一个对象向第二个对象发送消息。通常这发生在一个进程地址 space 内。 COM 允许一个进程中的对象调用另一进程中对象的方法 - 从而实现进程间通信。

COM是一个很大的话题,不可能用溢出答案的格式来解释它。我将尝试做的是在 Visual Studio ATL 向导(就您在标签中提到的 ATL)的帮助下演示本地进程外 COM 服务器和 COM 客户端(尽可能短)的最简单示例大多数代码,这提供了测试 COM 方法和调查样板源的可能性。但为了更好地理解,我建议查找不带 ATL 的 inproc COM 服务器实现 - 仅使用 C++。

  1. 正在创建结构提供程序:

    • 创建名为 COMStructProvider 的新 ATL 项目(select Visual C++ 中的 ATL 项目模板)。在向导中 select “可执行”应用程序类型(不是 DLL)。默认为其他选项。向导将生成项目文件。
    • Select 项目 -> 添加 class -> ATL 简单对象 -> 添加。简称名称字段类型任意名称,例如 MyStruct。单击“完成”。这将为 MyStruct coclass 添加头文件和实现文件。还将添加 MyStruct.rgs 以帮助在注册表中注册您的 coclass。现在你有了最小的 COM 服务器,可以构建解决方案,但要做到这一点,你需要 运行 VS 作为管理员(因为它会在注册表中注册你的服务器),否则注册失败。
    • 向CMyStruct添加两个数据成员(VS默认在class前面加上C)class: 私人的: std::string m_name; 整数 m_age;
    • 在前面的步骤中,向导创建了接口 IMyStruct(您可以在 idl 文件中看到它)。现在我们要向这个接口添加方法:getter 和 setter 到我们的两个私有数据成员。 Select Class 视图选项卡,select IMyStruct 接口(派生自 IDispatch),select 在上下文菜单“添加方法”中。例如带有参数 LONG* 的方法名称 getAge,参数属性“out”和参数名称:age(单击添加以添加参数)。这将新方法添加到 idl 文件以及头文件和 impl 文件。重复添加 setAge(在 LONG 类型的参数中)、getName(out,BSTR*)、setName(in,BSTR)的方法。参数名称无关紧要。

在 idl 文件中,您将拥有类似的东西 - 我将其作为检查点提供,以确保所有步骤都正确完成:

import "oaidl.idl";
import "ocidl.idl";

[
    object,
    uuid(AA2DA48C-CD1E-4479-83D4-4E61A5F188CB),
    dual,
    nonextensible,
    pointer_default(unique)
]
interface IMyStruct : IDispatch{
    [id(1)] HRESULT getAge([out] LONG* age);
    [id(2)] HRESULT setAge([in] LONG age);
    [id(3)] HRESULT getName([out] BSTR* name);
    [id(4)] HRESULT setName([in] BSTR name);
};
[
    uuid(E7A47886-D580-4853-80AE-F10FC69E8D73),
    version(1.0),
]
library COMStructProviderLib
{
    importlib("stdole2.tlb");
    [
        uuid(CC51EFFE-C8F4-40FA-AEA3-EB6D1D89926E)      
    ]
    coclass MyStruct
    {
        [default] interface IMyStruct;
    };
};
  • 添加实现:

STDMETHODIMP CMyStruct::getAge(LONG* age)
{
 *age = m_age;
 return S_OK;
}


STDMETHODIMP CMyStruct::setAge(LONG age)
{
 m_age = age;
 return S_OK;
}


STDMETHODIMP CMyStruct::getName(BSTR* name)
{
 *name = SysAllocString(m_name.c_str());
 return S_OK;
}


STDMETHODIMP CMyStruct::setName(BSTR name)
{
 m_name.assign(name);
 return S_OK;
}

  1. 正在创建客户端。将新的 Win32 控制台应用程序项目 MyStructClient 添加到解决方案(可执行)。添加以下代码:

#include <iostream>
// check for correct path to tlb library. Will create tlh, tli files that provide smart pointers, etc.
#import "..\COMStructProvider\Debug\COMStructProvider.tlb" no_namespace named_guid

using namespace std;

int main()
{
    // initialize COM runtime
 CoInitialize(NULL);
 {
        // smart pointer simplifies work, will invoke CoCreateInstance to activate COM server
  IMyStructPtr spMyStruct(__uuidof(MyStruct));
  BSTR name = SysAllocString(L"John");
  spMyStruct->setName(name);
  SysFreeString(name);

  BSTR retreivedName;
  spMyStruct->getName(&retreivedName);
  wcout << "name " << retreivedName << endl;
  SysFreeString(retreivedName);

  spMyStruct->setAge(5);

  long age = 0;
  spMyStruct->getAge(&age);

  cout << "age " << age << endl;
 }
 CoUninitialize();

 return 0;
}

因此,您同时 运行 有两个进程:提供对结构的访问的服务器和可以访问相同结构的客户端(您可以并行 运行 更多客户端进程。所有客户端访问相同的服务器进程 - 可以被视为单例 - 但可以在每次激活时产生单独的进程)。客户端可以更改并获取此结构的值(根据需要)。在引擎盖下,客户端可以在其自己的地址 space 中访问 c​​oclass 的代理,并且 COM 运行time 支持所有进程间通信。那些 proxy/stubs(以 C/C++ 源的形式)是由 MIDL 编译器从上述接口 idl 文件生成的。就您专注于在两个进程之间传输数据而言,您应该知道存在三种类型的 COM 封送处理:自定义、标准和通用。在此示例中,通用就足够了,因为我仅使用 VARIANT 兼容类型作为方法参数。要传递任意类型,您应该使用标准封送处理(在 proxy/stub dll 的帮助下,在创建 COM 服务器时在第一步的单独项目中生成。项目名称是带有后缀 PS 的服务器的项目名称) .标准封送处理的缺点 - 您应该使用 COM 服务器部署那些 PS dll。

COM exe 进程外服务器特别难以编写,但 Microsoft 已创建 COM+ Component service 来缓解这一问题。

它包含很多服务,但在这里我们对允许您在进程外代理主机中托管进程内服务器 (DLL) 的应用程序服务感兴趣。

非常简单,只需编写一个标准的 ATL DLL(或使用您喜欢的任何其他 language/framework)。我建议对接口使用自动化类型,这样您就不需要特殊的代理,例如使用如下定义的 IDL 接口:

interface ISharedMap : IDispatch{
    [id(1)]
    HRESULT PutData([in] BSTR key, [in] VARIANT value);

    [id(2)]
    HRESULT GetData([in] BSTR key, [out, retval] VARIANT *pValue);
};

然后创建一个新的 COM+ 应用程序,如下所述:Creating COM+ Applications,并将其声明为服务器应用程序。这是完成后您应该看到的内容:

您的 DLL 现在将自动托管在特定进程(著名的 dllhost.exe)中,该进程将在客户端尝试连接时立即启动。默认情况下,同一进程将用于各种进程外 COM 客户端。它会在一段时间后关闭,但您可以通过多种方式配置 COM+ 应用程序,例如设置“空闲 时保留 运行”标志:

现在您将能够为您喜欢的所有 COM 客户端使用跨进程内存缓存,例如,从简单的 javascript .js 代码:

var map = new ActiveXObject("SharedMap");
map.PutData("mykey", "mydata")
var data = map.GetData("mykey")

注意:缓存的实现留给 reader,但它可以重用另一个 COM+ 服务:COM+ Shared Property Manager