“轻量级COM”究竟是什么?

7

在研究哪些COM公寓线程模型被Direct2D支持时,我发现尽管表面上看起来可以使用.NET通过COM互操作使用API,但Direct2D(像其他DirectX API一样)实际上根本不是COM API(1) Direct2D的维基百科文章(2)以及Thomas Olsen的MSDN博客文章(3)都使用“轻量级COM”方法引用这些API。

然而,我没有找到任何官方定义“轻量级COM”的确切含义。是否有这样的定义(可能由微软提供)?

脚注:

  1. Mike Danes在MSDN论坛问题的回答中提到,'CoInitialize/CoUninitialize, are they really needed for Direct2D and DirectWrite?'。以下是有趣的部分:

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

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

    "Direct2D是一个基于C++的本地代码API,可以被托管代码调用,并使用“轻量级COM”方法,就像Direct3D一样,具有最少量的抽象。"

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

    "轻量级COM-应该使用C ++风格的接口来模拟Direct3D的使用。不支持代理、跨进程远程、BSTRs、VARIANTs、COM注册(例如重型stuff)。"


我认为它是进程内的+两种线程模型+行为类似于自由线程封送程序,即公寓和线程无关。然后没有调度接口。然而,我认为在某些甚至许多情况下,像D2D1CreateFactory这样的函数只是CoCreateInstance的包装器-只需定义一个普通函数作为合同而不是GUID。我认为它更像是“仍然是COM”,而不是“像COM”。它是简化的COM,远离较重的东西,因为核心COM概念仍然很强大,而公寓规则实际上对于相关的API并不需要。 - Roman R.
有趣的是,AVI和DirectShow API是90年代早期的COM API之一,它们没有遵循一些提到的COM规则。我认为这个新的“轻量级COM”只是回到了那些API所在的地方... - Roman R.
@RomanR,我猜看看D2D1.dllD2D1CreateFactory是否包装了CoCreateInstance可能会很有趣。如果是这样,那么注册表也必须包含工厂协同类的CLSID和DLL链接。到目前为止,我还没有找到这些信息。 - stakx - no longer contributing
1
我并不坚持使用 D2D1CreateFactory - 这只是你的例子。但我相信类似的媒体基础函数都有这样的包装器。还有其他相关的API,比如WIC,它们是基于COM的,但不需要完整的COM线程要求。 - Roman R.
1
而且,我认为"使用或不使用COM"并不适用。 COM中的"M"代表模型。所有这些API都以稍微松散的方式遵循此模型。它们是基于COM的API。 .NET COM互操作将接口视为常规的COM接口。其中一些甚至可能有类型库。 - Roman R.
@RomanR:同意。也许应该将COM的边界视为逐渐的。很明显,“IUnknown”及其相关协议和所需的Vtable布局将是COM的核心。支持基础设施(CLSID注册表和OLE32.dll中的功能)可能会接近COM定义的外围。很难说,因为从未编写过COM的规范(除了某些0.9版本草案)。 - stakx - no longer contributing
2个回答

6
鉴于上述评论和事实上从未发布过COM规范(除了这篇1995年的0.9版本草案),要求定义“轻量级COM”可能是毫无意义的:如果“COM”不是一个精确定义的东西(而是更像一个想法),那么“轻量级COM”也可能对使用该想法的不同API有稍微不同的含义。
以下是试图定义DirectX风格API所使用的“轻量级COM”的种类。我还包括了我自己的“轻量级COM”组件的代码示例。

与COM的相似之处:

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

与COM的区别:

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

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

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

  • (如果与实际的 COM 库或 .NET marshaller 进行交互,则“轻量级 COM”库可能仍然必须使用 COM 内存分配器(CoTaskMemAllocCoTaskMemReallocCoTaskMemFree)…)

  • 由于不需要 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;
}

我使用Clang编译了上述内容(链接对Visual Studio 2015的C ++标准库),同样没有链接任何COM或OLE库:
clang++ -fms-compatibility-version=19 --shared -m32 -o Hello.dll Hello.cc

.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");
    }
}

3
并不完全如此,在客户端方面它不是轻量级的。例如,您需要在服务器端实现IMarshal来弥补缺失的COM管道。您也可以使其在客户端方面成为轻量级的,但这需要付出努力。 - Hans Passant

5
当您使用 VTable绑定(也曾被称为“早期绑定”)+ IUnknown 定义时,您会进行轻量级COM。这就是全部内容。
在古老的Microsoft出版物中,您永远不会找到这个名字的定义,因为它从未以此名称存在过。
作为API开发人员,当您声明自己在做“轻量级COM”时,基本上意味着您不关心以下内容:
- 对象定义(ODL/IDL、元数据、TLB、类型系统等) - 对象激活(注册表、ProgIDs、CoCreateInstance等) - 对象RPC(跨线程或进程封送、代理/存根等) - 自动化(VARIANT、BSTR、通用封送程序、延迟绑定、支持脚本语言等) - 组件服务(对象池、代理、DLLHost、MTC/DTC等)
请注意,这并不意味着您不需要这些内容,实际上,取决于您使用的工具(例如Visual Studio工具),您会免费获得它们(例如,对于提供的抽象,依赖(M)IDL更容易)。只是您喜欢拥有可扩展的API(使用COM,可以在二进制组件之间“强制转换”对象)而无需支持所有这些服务。
还要注意,“轻量级COM”在任何平台和任何语言中都非常轻便且易于移植(称之为“开放”),这是现在更有趣的地方。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接