鉴于
上述评论和事实上从未发布过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 中的 D2D1CreateFactory
在 d2d1.dll
中)。其他对象可以从这个“入口点”工厂对象检索。
由于 DLL 直接加载到客户端进程中,因此“轻量级 COM” API(与 COM 不同)仅支持进程内服务器。因此,不需要也不支持远程存根和代理。
理论上,“轻量级 COM” 库根本不依赖于 OLE32.dll
,即不需要 / 调用 CoXXX
函数(例如 CoInitialize
以设置线程的公寓、CoCreateInstance
以实例化共享类等)。
(如果与实际的 COM 库或 .NET marshaller 进行交互,则“轻量级 COM”库可能仍然必须使用 COM 内存分配器(CoTaskMemAlloc
、CoTaskMemRealloc
、CoTaskMemFree
)…)
由于不需要 CoInitialize
,因此可以得出结论,“轻量级 COM” 不使用 COM 的公寓线程模型。 “轻量级 COM” API 通常实现自己的线程模型,例如 Direct2D 的多线程模型。(这个页面完全没有提到 Direct2D 支持哪种 COM 公寓模型的提示,这表明 COM 公寓根本不适用于 Direct2D!)
"轻量级COM"组件示例:
以下C++文件 (Hello.cc
) 实现了一个 "轻量级COM" 组件 Hello
。为了说明这将独立于COM,我没有包括任何COM或OLE头文件:
#include <cinttypes>
#include <iostream>
typedef uint32_t HRESULT;
const HRESULT E_OK = 0x00000000;
const HRESULT E_NOINTERFACE = 0x80004002;
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;
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;
};
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;
};
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;
}
};
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");
}
}
D2D1CreateFactory
这样的函数只是CoCreateInstance的包装器-只需定义一个普通函数作为合同而不是GUID。我认为它更像是“仍然是COM”,而不是“像COM”。它是简化的COM,远离较重的东西,因为核心COM概念仍然很强大,而公寓规则实际上对于相关的API并不需要。 - Roman R.D2D1.dll
和D2D1CreateFactory
是否包装了CoCreateInstance
可能会很有趣。如果是这样,那么注册表也必须包含工厂协同类的CLSID和DLL链接。到目前为止,我还没有找到这些信息。 - stakx - no longer contributingD2D1CreateFactory
- 这只是你的例子。但我相信类似的媒体基础函数都有这样的包装器。还有其他相关的API,比如WIC,它们是基于COM的,但不需要完整的COM线程要求。 - Roman R.