我希望在 UNIX 类型平台上用 C++ 实现 COM 的自定义实现,以允许我动态加载和链接面向对象的代码。我认为这将基于 POSIX 提供的一组类似的功能来加载和调用 dll,即 dlopen、dlsym 和 dlclose。
在最简单的层面上,COM 是通过接口实现的。在 c++ 中,如果您对纯虚拟或抽象基类的概念感到满意,那么您已经知道如何在 c++ 中定义接口
struct IMyInterface {
void Method1() =0;
void Method2() =0;
};
COM 运行时提供了许多适用于 windows 环境的额外服务,但在单个应用程序中实现“迷你”COM 作为动态链接到比 dlopen 传统上允许的更多 OO 接口的一种手段时并不真正需要, dlsym 等。
COM 对象在 .dll、.so 或 .dylib 文件中实现,具体取决于您的平台。这些文件至少需要导出一个标准化的函数:DllGetClassObject
在您自己的环境中,您可以根据需要对其进行原型设计,但要与 Windows 上的 COM 运行时互操作,显然名称和参数需要符合 com 标准。
基本思想是,它传递一个指向 GUID 的指针 - 唯一分配给特定对象的 16 个字节,它创建(基于 GUID)并返回工厂对象的 IClassFactory*。
然后,COM 运行时使用工厂对象在调用 IClassFactory::CreateInstance 方法时创建对象的实例。
所以,到目前为止你已经
- 导出至少一个符号的动态库,名为“DllGetClassObject”(或其一些变体)
- 一个 DllGetClassObject 方法检查传入的 GUID 以查看是否请求了哪个对象,然后执行“新 CSomeObjectClassFactory”
- 实现(派生自)IClassFactory 的 CSomeObjectClassFactory 实现,并将 CreateInstance 方法实现为 CSupportedObject 的“新”实例。
- CSomeSupportedObject 实现了从 IUnknown 派生的自定义或 COM 定义的接口。这很重要,因为向 IClassFactory::CreateInstance 传递了一个 IID(同样,这次是定义接口的 16 字节唯一 id),它需要在对象上进行 QueryInterface 处理。
我了解 COM 的一般概念是您在一个通用 dll (Kernel32.dll) 中链接到几个函数,即 QueryInterface、AddRef 和 Release,然后允许您访问只是一个表的接口函数指针封装了一个指向应该调用函数指针的对象的指针。这些函数通过您必须继承的 IUnknown 公开。
实际上,COM 是由 OLE32.dll 实现的,它公开了一个名为 CoCreateInstance 的“c”api。该应用程序向 CoCreateInstance 传递了一个 GUID,它在 Windows 注册表中查找该 GUID - 它具有 GUID 的 DB ->“dll 路径”映射。 OLE/COM 然后加载(dlopen)dll,调用它的 DllGetClassObject (dlsym) 方法,再次传入 GUID,假设成功,OLE/COM 然后调用 CreateInstance 并将结果接口返回给应用程序。
那么这一切是如何运作的呢?有没有更好的方法来动态链接和加载到面向对象的代码?从 dll 继承如何工作 - 对基类的每次调用都必须是对公开的成员函数,即 private/protected/public 被简单地忽略?
从 dll/so/dylib 隐式继承 c++ 代码的工作原理是将类中的每个方法导出为“装饰”符号。方法名称由类和每个参数的类型修饰。这与从静态库(.a 或 .lib 文件 iirc)导出符号的方式相同。静态或动态库,“私有、受保护等”总是由编译器强制执行,解析头文件,而不是链接器。
我非常精通 C++ 和模板元编程,并且已经拥有一个完全反射的 C++ 系统,即使用 boost 的成员属性、成员函数和全局/静态函数。
c++ 类通常只能从具有静态链接的 dll 导出 - 在加载时加载的 dll,而不是在运行时通过 dlopen。 COM 通过确保 COM 中使用的所有数据类型要么是 pod 类型,要么是纯虚拟接口,从而允许动态加载 c++ 接口。如果您违反此规则,通过定义一个尝试传递 boost 或任何其他类型对象的接口,您将很快陷入编译器/链接器需要的不仅仅是头文件来弄清楚发生了什么以及您的精心准备的“com”dll 必须静态或隐式链接才能正常工作。
COM 的另一条规则是,永远不要将对象的所有权传递给动态库边界。即永远不要从 dll 返回接口或数据,并要求应用程序将其删除。接口都需要实现 IUnknown,或者至少是 Release() 方法,允许对象执行删除 this。任何返回的数据类型同样必须有一个众所周知的解除分配器 - 如果您有一个带有名为“CreateBlob”的方法的接口,则可能应该有一个名为“DeleteBlob”的伙伴方法。