什么是“轻量级 COM”?

sta*_*ica 7 .net com direct3d com-interop direct2d

在研究 Direct2D 支持哪些 COM 单元线程模型时,我发现尽管外观和事实可以使用来自 .NET 的 API 使用 COM 互操作性,但Direct2D(与其他 DirectX API 一样)实际上根本不是 COM API(1)无论是维基百科上的Direct2D文章(2) ,以及一个由托马斯·奥尔森MSDN博客文章(3)指的是使用一个“轻量级COM”的方式这些API。

但是,我还没有找到关于这个“轻量级 COM”究竟是什么的任何官方定义。有没有这样的定义(可能是微软)?

脚注:

  1. Mike Danes 对 MSDN 论坛问题“CoInitialize/CoUninitialize,Direct2D 和 DirectWrite 真的需要它们吗?”的回答 . 这是有趣的一点:

    “DirectWrite/Direct2D/Direct3D 是 COM 之类的 API,但它们不使用 COM。它们不像普通的 COM 组件那样在注册表中注册,它们不遵循 COM 线程模型,它们不支持任何类型的编组等。它们”不是 COM。”

  2. 维基百科关于 Direct2D 的文章(概述部分)

    “Direct2D 是一种基于 C++ 的本机代码 API,可由托管代码调用,并使用类似于 Direct3D 的“轻量级 COM”方法,具有最少的抽象。”

  3. Thomas Olsen 的 MSDN 博客文章提到以下作为 Direct2D 的设计目标:

    “轻量级 COM——应该使用 C++ 风格的接口来模拟 Direct3D 的使用。不支持代理、跨进程远程处理、BSTR、VARIANT、COM 注册(例如重量级的东西)。”

sta*_*ica 6

鉴于上述评论以及从未发布过 COM 规范的事实(除了1995 年的 0.9 版草稿),要求定义“轻量级 COM”可能毫无意义:如果“COM”不是精确定义的东西(但更多的是一个想法),那么“轻量级 COM”也许也是如此。理论上,对于使用该想法的不同 API,它可能意味着略有不同的含义。

以下尝试定义 DirectX 风格的 API 使用哪种“轻量级 COM”。我还包括我自己的“轻量级 COM”组件的代码示例。

与 COM 的相似之处:

  • “轻量级 COM” API 看起来像 COM。它们具有相同的“一切都可以通过接口访问”、“接口只有方法”、“所有接口直接或间接继承自IUnknown”、“接口永远不会改变,一旦发布”的世界观。
  • 使用的IUnknown接口与 COM 的IUnknown.
  • 这意味着“轻量级 COM”API 还使用引用计数进行内存管理,QueryInterface并使用 IID 来检索接口指针。
  • “轻量级 COM” API 具有与 COM 相同的应用程序二进制接口 (ABI);这包括对象/虚拟表内存布局、__stdcall调用约定、HRESULT返回值等。
  • 因此,可以通过 COM 互操作从 .NET 使用“轻量级 COM”API。(见下面的例子。)

与COM的区别:

  • 组件未通过 CLSID 在注册表中注册。也就是说,组件不是通过调用来实例化的CoCreateInstance;相反,客户端直接引用 API 的库,该库公开工厂函数(例如 Direct2D 的D2D1CreateFactoryin d2d1.dll)。可以从此“入口点”工厂对象检索其他对象。

  • 由于 DLL 直接加载到客户端进程中,因此“轻量级 COM”API(与 COM 不同)仅支持进程内服务器。因此不需要也不支持远程存根和代理。

  • 理论上,“轻量级COM”库根本不依赖OLE32.dll,即不需要调用CoXXX函数(例如CoInitialize设置线程的单元、CoCreateInstance实例化共同类等)。

  • (但是,如果“轻量级 COM”库与实际的 COM 库或与 .NET 编组器(假定它正在处理 COM 库)互操作,则它可能仍必须使用 COM 内存分配器(CoTaskMemAlloc、、)。)CoTaskMemReallocCoTaskMemFree

  • 由于CoInitialize不需要,因此“轻量级 COM”不使用 COM 的单元线程模型。“轻量级 COM” API 通常实现它们自己的线程模型,例如Direct2D 的多线程模型。(事实上​​,这个页面没有包含任何 Direct2D 支持的 COM 单元模型的提示,这表明 COM 单元根本不适用于 Direct2D!)

“轻量级 COM”组件示例:

下面的 C++ 文件 ( Hello.cc) 实现了一个“轻量级 COM”组件Hello。为了说明这将独立于 COM,我不包括任何 COM 或 OLE 头文件:

#include <cinttypes>
#include <iostream>

// `HRESULT`:

typedef uint32_t HRESULT;
const HRESULT E_OK = 0x00000000;
const HRESULT E_NOINTERFACE = 0x80004002;

// `GUID` and `IID`:    

typedef struct
{
    uint32_t Data1;
    uint16_t Data2;
    uint16_t Data3;
    uint8_t  Data4[8];
} GUID;

bool operator ==(const GUID &left, const GUID &right)
{
    return memcmp(&left, &right, sizeof(GUID)) == 0;
}

typedef GUID IID;

// `IUnknown`:

const IID IID_IUnknown = { 0x00000000, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 };

class IUnknown
{
public:
    virtual HRESULT  __stdcall QueryInterface(const IID *riid, void **ppv) = 0;
    virtual uint32_t __stdcall AddRef() = 0;
    virtual uint32_t __stdcall Release() = 0;
};

// `IHello`:    

const IID IID_IHello = { 0xad866b1c, 0x5735, 0x45e7, 0x84, 0x06, 0xcd, 0x19, 0x9e, 0x66, 0x91, 0x3d };

class IHello : public IUnknown
{
public:
    virtual HRESULT __stdcall SayHello(const wchar_t *name) = 0;
};

// The `Hello` pseudo-COM component:

class Hello : public IHello
{
private:
    uint32_t refcount_;

public:
    Hello() : refcount_(0) { }

    virtual HRESULT __stdcall QueryInterface(const IID *riid, void **ppv)
    {
        if (*riid == IID_IUnknown)
        {
            *ppv = static_cast<IUnknown*>(this);
        }
        else if (*riid == IID_IHello)
        {
            *ppv = static_cast<IHello*>(this);
        }
        else
        {
            *ppv = nullptr;
            return E_NOINTERFACE;
        }
        reinterpret_cast<IUnknown*>(*ppv)->AddRef();
        return E_OK;
    }

    virtual uint32_t __stdcall AddRef()
    {
        return ++refcount_;
    }

    virtual uint32_t __stdcall Release()
    {
        auto refcount = --refcount_;
        if (refcount == 0)
        {
            delete this;
        }
        return refcount;
    }

    virtual HRESULT __stdcall SayHello(const wchar_t *name)
    {
        std::wcout << L"Hello, " << name << L"!" << std::endl;
        return E_OK;
    }
};

// Factory method that replaces `CoCreateInstance(CLSID_Hello, …)`:

extern "C" HRESULT __stdcall __declspec(dllexport) CreateHello(IHello **ppv)
{
    *ppv = new Hello();
    return E_OK;
}
Run Code Online (Sandbox Code Playgroud)

我用 Clang 编译了上面的代码(链接到 Visual Studio 2015 的 C++ 标准库),同样没有链接到任何 COM 或 OLE 库:

clang++ -fms-compatibility-version=19 --shared -m32 -o Hello.dll Hello.cc
Run Code Online (Sandbox Code Playgroud)

针对上述组件的 .NET 互操作示例:

现在,鉴于生成的 DLL 位于我的 .NET 代码的搜索路径中(例如在bin\Debug\bin\Release\目录中),我可以使用 COM 互操作在 .NET 中使用上述组件:

using System.Runtime.InteropServices;

[ComImport]
[Guid("ad866b1c-5735-45e7-8406-cd199e66913d")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IHello
{
    void SayHello([In, MarshalAs(UnmanagedType.LPWStr)] string name);
}

class Program
{
    [DllImport("Hello.dll", CallingConvention=CallingConvention.StdCall)]
    extern static void CreateHello(out IHello outHello);

    static void Main(string[] args)
    {
        IHello hello;
        CreateHello(out hello);
        hello.SayHello("Fred");
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 嗯,不完全是,它在客户端不是轻量级的。例如,您必须在服务器端实现 IMarshal 以弥补缺少的 COM 管道。您也可以在客户端使其轻量级,但它是有效的。 (3认同)

Sim*_*ier 5

当您使用VTable 绑定(也曾被称为“早期绑定”)+ IUnknown定义时,您会执行轻量级 COM 。就是这样。你永远不会在古老的微软出版物中找到它的定义,因为它从来没有用这个名字存在过。

作为一个 API 开发者,当你声明你做“轻量级 COM”时,你基本上是在声明你不关心以下内容:

  • 对象定义(ODL/IDL、元数据、TLB、类型系统等)
  • 对象激活(registry、progids、CoCreateInstance 等)
  • 对象 RPC(跨线程或进程编组、代理/存根等)
  • 自动化(VARIANT、BSTR、通用封送拆收器、后期绑定、脚本语言支持等)
  • 组件服务(对象池、代理、DLLHost、MTC/DTC 等)

请注意,这并不意味着你不会有吧,其实,你拥有它是免费的,具体取决于您使用的工具(即Visual Studio中的工具,例如,它在某种程度上更容易依靠(M)IDL的抽象它提供),只是您喜欢拥有可扩展 API 的想法(使用 COM,您可以在二进制组件之间“转换”对象)而无需支持所有这些服务。

还要注意,“轻量级 COM”在任何平台和任何语言(称之为“开放”)上都非常非常便携,这在今天更有趣。