A simple cross-platform COM

前两天和一个同事聊起COM(Component Object Model)来,想起了以前项目中做的一个非常简单的跨平台的COM,其实就是简化了微软的COM,并且加入了对Linux的支持。

从光盘上找到当时的源码和论文,总结一下,感觉写成Blog比当时写论文的感觉好多了,呵呵。

1)构件的概念

构件其实是个很老的概念了,构件其实还是要从根本上解决软件复用得问题,而为了解决这个问题,我们已经经历了三个阶段:面向过程-面向对象-面向构件。

从抽象程度来看,面向对象技术已达到了类级重用(源代码级别的重用),它以类为封装的单位。这样的重用粒度还太小,不足以解决异构互操作和效率更高的重用。面向构件的技术实现了更高的层次的抽象,它对一组类的组合进行封装,并代表完成一个或多个功能的特定服务,也为用户提供了多个接口。整个构件隐藏了具体的实现,只用接口提供服务。这样,在不同层次上,构件均可以将底层的多个逻辑组合成高层次上粒度更大的新构件,从代码级、对象级、架构级甚至到系统级都能实现模块的重用,从而使得希望软件像硬件一样任意装配定制的梦想能够得以实现。

构件的主要特性:

  1. 二进制特性:构件是可单独生产、使用的二进制单元,软件复用是建立在二进制级而不是源代码级的基础上;
  2. 符合构件模型:符合一定的构件模型和组装标准,客户程序可以根据这个标准实施对构件的调用;
  3. 允许第三方定制:构件可以被第三方独立的部署和组装,构件的更新不需要客户程序的变化。

2)MS COM

BTW: 如果想深入了解COM技术,强烈推荐Don Box的大作 – "Essential COM",尤其是第一、二章,看过之后,就会对COM为什么这样设计有非常深刻的认识了。

由于C++的封装性主要表现在语义方面(比如:私有、保护、公开),而在二进制方面缺少统一的标准,因此无法直接进行构件的开发。COM首先要解决的就是定义一个通用的二进制标准,来规范各个构件第三方的开发。

了解构件开发还需要了解的一个概念就是接口(Interface),大到航天飞机的对接,小到我们平时所用的电源插座、网线插口,这些都是接口的应用。而软件开发如果要实现构件化,也需要接口与实现的分离。

C++的类无法实现接口与实现的分离,因为类既有接口,也包括实现。而Java中引入的Interface可以很好的解决这个问题。因此,在C++中如果希望得到接口和实现的分离,就需要定义一种特殊的标准。

构造一个模型,把两个抽象概念(即接口和实现)做成两个分离的实体,即C++类。定义一个C++类使它代表指向一定数据类型的接口;定义另一个C++类作为数据类型的实际实现,于是从理论上讲,对象的实现者可以修改实现类的细节,而接口类保持不变。 – "Essential COM"

由于接口没有暴露任何实现细节,因此C++接口类不应该包含任何用于对象实现的数据成员。相反,接口类应该只包含对象的每个操作的方法声明。 – "Essential COM"

COM采用抽象基类来作为二进制接口。

Vtable

当时只所以做那个简单的COM主要是因为:

  1. COM有些过于复杂化,缺乏对特定应用的支持;
  2. COM库没有跨平台性,代码无法移植到其它平台(也可以参考XPCOM – Mozilla的组件标准);
  3. MFC和ATL对COM的支持各有不足,同时由于缺乏有效的组件开发工具,使得COM的开发还是比较困难。

为了项目的需要,抽取COM的精华,做了一个最简单的COM,一为学习,二为己用。

3)自己动手,做一个最简单的跨平台COM

COM平台的基接口叫做IBase,其实就是IUnknown的一个复制(改用标准库),完全学习而已。

  1. interface IBase
  2.     {
  3.         virtual void LEO_INTERFACE_CALL QueryInterface(const std::string& sName, IBase** ppIf) = 0;
  4.         virtual void LEO_INTERFACE_CALL AddRef() = 0;
  5.         virtual void LEO_INTERFACE_CALL Release() = 0;
  6.     };

真正的一些修改包括构件数据库、构件类厂等。

先说说构件数据库,基于构件的软件开发必然导致存在大量可重用的构件,这样应用程序系统就能够根据这些可重用构件迅速建立。然而,这将导致构件系统中一些关键问题,即:如何才能快速、正确的获得当前工程所需的构件;当单个构件被更新或升级后如何更新整个应用系统。解决这些问题的较好方法是拥有一个强大的构件数据库。它能够对可重用的构件进行描述、管理、存储和检索,帮助开发人员理解构件,是构件化软件开发的重要部分。

比如说,微软的COM平台利用注册表来作为构件数据库,并且采用一个全球唯一的GUID来索引每个构件。这种结构对于一些小型、要求绿色软件、要求跨平台的应用就不合适。

如果看过JDK、Lucene这样的代码应该会有很深的体会,一种更好的方式是规范Interface,而对具体的实现方式(比如是用数据库还是用注册表来存储、是用GUID还是用字符串来索引)留给用户去选择,这样,不同用户可以根据不同情况采用不同的策略。

这是一个构件数据库的类图(Bridge模式)。

Plugin Database

构件类厂用来创建构件,根据构件名称从构件数据库中取得构件的物理位置,把构件引入到客户的进程空间,并实现对构件的运行期管理。这部分也是对操作系统耦合最紧密的部分。

类厂首先需要查询构件数据库,根据构件名称信息定位到构件的物理位置,然后再得到构件的基指针。为了实现构件基指针的引出,构件必须实现一个事先定义好的回调函数 ,这个回调函数为PluginGetBase,它的原型定义为:

  1. EXTERN_C void PluginGetBase(const std::string& sName, Leo::IBase** ppIf);

在COM中,COM组件是以DLL(动态链接库)的形式来发布的,而在Linux上,则利用动态链接的SO文件。而不同平台上构件创建的基本步骤是相同的,如下图所示:

Plugin Factory

因此采用Template Method模式[2]来设计构件创建的具体过程。Template Method模式定义了一个操作的算法骨架,而将一些步骤延迟到子类中。这使得子类可以不改变一个算法的结构即可以重新定义该算法的某些特定步骤。

在这种类层次结构中,PluginImp定义了获取构件操作(LoadPlugin)的基本步骤:首先获得构件句柄,然后获得PluginGetBase函数地址,最后执行该函数。基类PluginImp通过使用抽象操作定义了LoadPlugin算法的基本步骤,使用模板方法定义了这些步骤的基本顺序,而具体的子类负责实现具体算法。

Template Pattern

比如说,在Windows平台上面:

  1. class PluginImpWindows : public PluginImp
  2. {
  3. public:
  4.     void LoadPlugin(const std::string& sPluginPath, const std::string& sInterface, IBase** ppIf)
  5.     {
  6.         HINSTANCE hModule = ::LoadLibrary(sPluginPath.c_str());
  7.         if(hModule == null)
  8.             throw LibraryLoadException()
  9. ;
  10.         static void (*pfn)(const std::string& sName, Leo::IBase** ppIf) = null;
  11.         *(FARPROC*)&pfn = ::GetProcAddress(hModule, "PluginGetBase");
  12.  
  13.         if(!pfn)
  14.             throw LibraryFuncException();
  15.         //    Run PluginGetBase() func in the Plugin to get the base interface
  16.         pfn(sInterface, ppIf);
  17.         };
  18. }

而对应于在Linux平台,就应该用这些函数

dlopen 将共享目标文件打开并且映射到内存中,并且返回句柄
dlsym返回一个指向被请求入口点的指针
dlerror 返回 NULL 或者一个指向描述最近错误的 ASCII 字符串的指针
dlclose关闭句柄并且取消共享目标文件的映射

可见,dlopen – LoadLibrary, dlsym – GetProcAddress。

最后,说说构件的注册与卸载,其实就是将构件登记到构件数据库中,很简单。一般也是采用回调函数的方式来工作。

而构件的另一个常用概念 – 双向连接,也很简单。就是采用连接点机制来实现,简单的说,客户负责实现一个简单的连接点源接口,并把接口指针告诉构件对象。构件对象通过这个指针与客户进行通信。连接点的实现参考了设计模式中的Observer模式,Observer模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

Reference:

《Essential COM》 – Don Box

Popularity: 12% [?]

Related entries:

  • No Related Posts

Leave a comment

(required)

(required)


Information for comment users
Line and paragraph breaks are implemented automatically. Your e-mail address is never displayed. Please consider what you're posting.

Use the buttons below to customise your comment.


RSS feed for comments on this post | TrackBack URI