【问题标题】:Custom COM Implementation?自定义 COM 实现?
【发布时间】:2011-01-10 03:48:00
【问题描述】:

我希望在 UNIX 类型平台上用 C++ 实现 COM 的自定义实现,以允许我动态加载和链接面向对象的代码。我认为这将基于 POSIX 提供的一组类似的功能来加载和调用 dll,即 dlopen、dlsym 和 dlclose。

我了解 COM 的一般概念是您在一个通用 dll (Kernel32.dll) 中链接到几个函数,即 QueryInterface、AddRef 和 Release,然后允许您访问接口,这些接口只是封装的函数指针表带有指向应该调用函数指针的对象的指针。这些函数通过您必须继承的 IUnknown 公开。

那么这一切是如何运作的呢?有没有更好的方法来动态链接和加载到面向对象的代码?从 dll 继承是如何工作的 - 对基类的每次调用都必须是对公开的成员函数,即 private/protected/public 被简单地忽略?

我非常精通 C++ 和模板元编程,并且已经拥有一个完全反射的 C++ 系统,即使用 boost 的成员属性、成员函数和全局/静态函数。

【问题讨论】:

    标签: c++ com


    【解决方案1】:

    有几点要记住:

    • COM 的强大功能主要来自 IDL 和 midl 编译器。它允许使用为您生成的所有 C/C++ 样板文件对对象和接口进行非常简洁的定义。

    • COM 注册。在 Windows 上,类 ID (CLSID) 记录在与可执行文件关联的注册表中。您必须在 UNIX 环境中提供类似的功能。

    • 除了QueryInterface 在 C 中实现(即没有 RTTI)时,整个 IUnknown 实现相当简单。

    • COM 的另一个方面是 IDispatch - 即后期绑定方法调用和发现(只读反射)。

    看看XPCOM,因为它是一个类似 COM 的多平台环境。这确实是您最好利用其他技术的事情之一。它可以把很多时间花在其他地方。

    【讨论】:

      【解决方案2】:

      我希望在 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”的伙伴方法。

      【讨论】:

        【解决方案3】:

        要真正了解 COM 的工作原理,我建议阅读 Don Box 的“Essential COM”。

        【讨论】:

          【解决方案4】:

          查看 CORBA 文档,位于 sscli 中的 System.ComponentModel,它是 Mozilla 代码库的 XPCOM 部分。 Miguel de Icaza 在 GNOME 中实现了类似 OLE 的东西,称为 Bonobo,它可能也很有用。

          不过,根据您使用 C++ 所做的工作,您可能希望查看适用于 C++ 的插件框架,例如 Yehia。我相信 Boost 也有类似的东西。

          编辑:pugg 目前似乎比 Yehia 维护得更好。不过我没试过。

          【讨论】:

            【解决方案5】:

            COM 的基本设计非常简单。

            1. 所有 COM 对象都通过一个或多个接口公开其功能
            2. 所有接口都派生自 IUnknown 接口,因此所有接口都有 QueryInterface、AddRef 和 Release 方法作为它们 virtual 的前 3 个方法 已知顺序的函数表
            3. 所有对象都实现 IUnknown
            4. 可以从任何其他接口查询对象支持的任何接口。
            5. 接口由全局唯一标识符标识,这些是 IID GUID 或 CLSID,但它们实际上都是同一个东西。 http://en.wikipedia.org/wiki/Globally_Unique_Identifier

            COM 变得复杂的地方在于它如何处理允许从对象所在的进程外部调用接口。 COM 编组是一种讨厌的、毛茸茸的、野兽。 COM 支持单线程和多线程编程模型这一事实更是如此。

            COM 的 Windows 实现允许注册对象(Windows 注册表的最初用途是用于 COM)。 COM 注册表至少包含 COM 对象的唯一 GUID 与包含其代码的库 (dll) 之间的映射。

            为此工作。实现 COM 对象的 DLL 必须具有 ClassFactory - DLL 中具有标准名称的入口点,可以调用该入口点来创建 DLL 实现的 COM 对象之一。 (实际上,Windows COM 从这个入口点获取一个 IClassFactory 对象,并使用它来创建其他 COM 对象。

            这就是 10 美分之旅,但要真正理解这一点,您需要阅读 Don Box 的 Essential COM

            【讨论】:

              【解决方案6】:

              您可能对(尚未)Boost.Extension 库感兴趣。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2014-02-01
                • 2014-10-14
                • 2011-06-04
                • 2010-10-29
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多