COM入门笔记

前序:COM的基础:动态链接和二进制代码的封装。


为了能够讲硬件和软件联系起来,我们将经常对照两个硬件组件来进行说明:一台TV和一台VCR。只考虑在这两个组件间视频信号的相互作用,以此来简化这个示例。使这两个硬件组件称为独立设备的优势是明显的:如果在VCR中发生错误,就只需要现场替换VCR就可以了;当VCR升级时,不用讲TV也升级;在TV以及VCR都具有标准的视频输入/输出插口时,任何品牌的TV和VCR都可以相匹配一起工作。

下面写一段程序来模拟一下VCR-TV之间进行连接的情况:

假设VCR厂商已经签署了一个协议提供一个C++类(CVcr)给这个TV制造商。这个类有一个称为GetSignalValue的共有方法。当这个共有方法被TV端调用时,它将返回一个当前的视频信号值。这个的最大信号值设定为40.

//vcr.h文件

class CVcr

{public:

    CVcr(void);

    ~CVcr(void);

    long GetSignalValue(void);

private:

    long m_lCurValue;

};

//vcr.cpp文件

#include "StdAfx.h"

#include "Vcr.h"

CVcr::CVcr(void)

: m_lCurValue(5)

{

}

CVcr::~CVcr(void)

{

}

long CVcr::GetSignalValue(void)

{   long lReturnValue=m_lCurValue;

    m_lCurValue+=10;

    return lReturnValue;

}

1. 动态链接:

链接程序以及操作系统装载程序的作用:

当一个程序模块被编译时,这个编译器讲生成一个目标文件,该目标文件包含有这个程序设计逻辑的机器语言描述。然后,这个编译器不能编译这个模块的任何外部符号。这些符号就作为外部值存在于这个目标文件中(这里C编译器和C++编译器不一样:C++编译器存在name-mangling问题,C编译器不会)。

一旦目标模块生成了,链接程序就组合所有的目标模块以及库(一个库是一个目标模块的链接)由此生成最终可执行文件,链接程序负责解析所有外部符号,并且用相应的过程入口来替换它们。如果有任何外部的符号不能够被解析的话,那么这个链接程序将会终止这个进程。

执行可执行文件时:操作系统的装载程序将这个可执行文件的映像装载到内存,并且从这个主入口启动这个可执行代码。

 

DLL:是可执行文件,当一个使用DLL的程序被执行时,这个DLL文件就被装载,被使用它的们的应用程序独立进行编译和链接,能够在无需应用程序进行重新编译以及链接的条件下进行更新。

我们为了使用DLL而不是一个静态链接库,需要做到:

1.阻止链接程序被没有解析的外部符号所困扰。

2.提供足够的信息给装载程序,以便这个装载程序能够动态地装载相应的DLL,并且能够在执行这个应用程序之前,解析这些符号到内存中相应的位置。

使用一个称为导入库的库文件可以满足上诉两个要求。这个二进制文件并没有包含任何代码:它包含的是对函数的所有引用以及一些其他的声明,这些声明用来使链接程序的条件得到满足以及帮助这个装载程序实时地对符号进行解析。

当这些应用程序对象模块与导入库链接在一起时,这个链接程序就不会被没有解析的符号困扰。它讲生成最终的可执行文件,当这个可执行文件运行时,这个装载程序就为所有需要被装载的DLL检查映像文件,并且尽力装载它们。装载了每一个DLL后,这个装载程序讲解析所有的外部符号,并且用实际的过程入口修补这个程序的映像。


2.      二进制代码的封装性(针对接口类而言):

C++封装性:是进行语义分析(按照公用和私有成员)的,不是二进制文件分析的。改变私有成员变量和C++类的方法都不需要用户修改她们的代码。

当这个TV代码被编译时,这个编译器使用了来自于接口类CVcr定义中的两部分信息:

  1. 接口协议:确保这个TV程序仅仅调用来自于CVcr类的能够使用的共有方法,并且确保参数被传送到这些方法中,以便来匹配这个协议。
  2. 实现细节:这个编译器需要知道这个类的二进制文件的大小,也就是这个类的一个实例到底占多大内存。这样做将帮助编译器在生成类的一个实例时能够分配相应大小的内存。

如果添加新的变量到CVcr类时,就改变了它的大小。也就是第一次TV程序利用开始时的CVcr头文件的信息分配了4个字节的空间给添CVcr类,而加新的变量到CVcr类后CVcr类已经变为8个字节,而我们又不会重新编译TV程序,所以在程序执行过程中对新加变量的操作会引来任何不可知的问题。所以二进制文件的封装性被破坏的话,结果完全不可预测。


第一步目标:将VCR的编程实现与TV的编程实现分离开来:

解决办法:讲接口协议从实现的细节中分离出来。将原来的类分为两个类来做到这一点(一个接口类,一个实现类)。接口类应当只定义允许客户调用的方法,它应当不要展示任何实现的细节看,当我们添加或删去实现类的成员变量时,都不会改变接口类的二进制布局。而这个实现类将包含完整的实现细节。

具体实现:最简单的办法是:在接口类中定义一个不透明的指针作为成员变量。一个不透明的指针需要的仅仅是它指向的这个类得前置声明,而不是这个类的一个完整定义。


第二步目标:将VCR编程实现编译程序的独立性:

为了能够确保所有的编译器都能生成对于一个接口的客户端方法调用的等价的机器语言代码,这个接口定义应当满足下面的要求:

1, 仅仅包含纯虚方法(可以摆脱name_mangling的问题,因为客户调用一个类的虚方法时生成的机器语言代码并不是用它自己的符号名称来指向这个方法的,而是用this指针和vtbl机制来调用这个方法的)。

2, 不包含任何成员变量(vptr的位置可能在成员变量之前也可能在之后,没有统一标准,当通过this指针和vptr调用虚拟方法时可能出错,所以不能跨编译器)。

3, 不包含任何重载的虚方法(无虚函数重载时,vtbl中的入口顺序和同虚函数的声明顺序一样,有重载后不同的编译器排列顺序不一样,所以不能跨编译器)。

4, 最多从一个基类派生出一个派生类(这个类的内存布局中包含不止一个vptr,而vptr的排放顺序没有统一标准),这个基类也应当满足上面的约束条件。

满足上面要求的类称为一个抽象基类。相应的C++实现类必须由接口类派生出来,必须用有意义的实现覆盖掉这些纯虚方法。

但是现在有个新问题:编译器不会允许你讲一个抽象基类实例化,只有具体类才能够被实例化。将实现类定义的细节展现给客户又将破坏这个接口的二进制封装性。对于客户而言,将一个基类实例化的一个合理技术就是从DLL中导出一个全局函数,这样的话,就可以返回这个基类的一个新实例。只要这个函数被声明为extern”C”,那么这个客户端代码将不会遇到任何name-mangling的问题。

客户端再采用一个factory方法获得这个接口,最终就可实现编译程序的独立性。


第三部目标:组件的动态选择:

现在我们最新的接口定义,就将发现这个客户程序的唯一外部符号就是这个factory方法CreateVcr.因此,如果我们自己能够在这个客户程序内部装载DLL以及解析这个符号,就不必让操作系统装载程序装载这个DLL以及为我们解析这个符号,也就不必要将我们的程序与这个导入库链接在一起,从而可以动态选取一个DLL。只要满足接口协议。

解决办法:微软提供了一个API:LoadLibrary,这个API可以动态地装载一个DLL;

       GetProcAddress可以用来解析过程入口。用这个API,就可以编写一个称为CreateInstance的客户端程序,这个函数能做到:装载指定DLL,解析外部符号CreateVcr.调用方法CreateVcr,并且返回这个接口指针。(这里有个问题需要说明下,貌似在VS2008下,想要这样调用DLL的话,导出DLL就必须要DEF导出,而不能用

__declspec( dllexport )的方式,原因未知。)

到这一步为止,我们定义了一个接口,具有二进制封装性的接口,既没有指定编译器也不用指定具体DLL。但是还不能改变扩展这个接口。


 第四步目标:如果我们想扩展接口功能怎么办?

扩展接口功能:

定义三个接口文件:三个都为虚拟基类:Igeneral,IVideo,ISVideo其中第一个又为后面二者的基类,里面包含两个虚函数,一个是Delete,一个是Probe探索版本类型;后两个分别包含各自的虚方法。实现类Cvcr : pulic  Ivideo,pulic ISVideo具体实现前面的所有方法,这样可以在实现类里面选择是否支持特定版本。应用程序里面必须包含三个文件,调用Probe找出支持的版本调用。这样对只支持Ivideo和支持ISVideo的DLL都能成功运行。

具体代码等我上传到CSDN后,会发个链接出来。


主要参考资料:COM+编程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值