什么是“轻量级COM”?

时间:2016-07-03 20:34:42

标签: .net com com-interop direct3d direct2d

在研究Direct2D支持哪些COM公寓线程模型的过程中,我发现尽管有外观并且可以使用来自.NET的API使用COM互操作性,Direct2D (like other DirectX APIs) is not actually a COM API at all(1) Wikipedia article on Direct2D (2)以及MSDN blog post by Thomas Olsen (3)都使用“轻量级COM”方法引用这些API。

但是,我还没有找到任何关于这个“轻量级COM”究竟是什么的官方定义。是否有任何此类定义(可能由Microsoft提供)?

脚注:

  1. Mike Danes对MSDN论坛问题'CoInitialize/CoUninitialize, are they really needed for Direct2D and DirectWrite?'的回答。这是有趣的一点:

      

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

  2. Wikipedia article on Direct2D (Overview section)

      

    “Direct2D是一个基于C ++的本机代码API,可以通过托管代码调用,并使用像Direct3D一样的”轻量级COM“方法,只需要很少的抽象。”

  3. Thomas Olsen's MSDN blog post提到以下作为Direct2D的设计目标:

      

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

2 个答案:

答案 0 :(得分:3)

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

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

与COM的相似之处:

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

与COM的区别:

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

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

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

  • (如果它与实际的COM库互操作,“轻量级COM”库可能仍然必须使用COM内存分配器(CoTaskMemAllocCoTaskMemReallocCoTaskMemFree) ...或者使用.NET marshaller,它假定它正在处理COM库。)

  • 由于不需要CoInitialize,因此“轻量级COM”不使用COM的公寓线程模型。 “轻量级COM”API通常实现自己的线程模型,例如, Direct2D's multithreading model。 (事实上​​,这个页面不包含任何COM公寓模型Direct2D支持的提示,暗示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");
    }
}

答案 1 :(得分:2)

当您使用 VTable绑定(也曾经被称为&#34;早期绑定&#34;) + IUnknown 定义时,您可以使用轻量级COM。这是关于它的。 您永远不会在古老的Microsoft出版物中找到对此的定义,因为它从未以此名称存在。

作为API开发人员,当您宣布执行轻量级COM&#34;时,您基本上声明您不关心以下事项:

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

请注意,并不意味着您不会拥有它,事实上,您免费拥有它,具体取决于您使用的工具(例如,Visual Studio工具,例如,它&# 39;以某种方式更容易依赖(M)IDL来提供它所提供的抽象),它只是你喜欢拥有可扩展API的想法(使用COM,你可以&#34;投射&#34;对象在二进制组件之间)没有支持所有这些服务的负担。

还要注意&#34;轻量级COM&#34;在任何平台和任何语言(称之为&#34; open&#34;)都非常非常便携,这在今天更有趣。