经典编程:DLL地狱及其解决方案(2)
1. 编码约定: (1)DLL的每个导出类(或者它的父类)至少包含一个虚函数。这样,这个类就会始终保存一个指向虚函数表的指针成员。这么做可以方便后
1. 编码约定:
(1)DLL的每个导出类(或者它的父类)至少包含一个虚函数。这样,这个类就会始终保存一个指向虚函数表的指针成员。这么做可以方便后来新的虚函数的加入。
(2)如果你要给一个类增加一个虚函数,那么将它加在所有其它虚函数的后面。这样就不会改变虚函数表中原有函数的地址映射顺序。
(3)如果你打算以后给一个类扩充类成员,那么现在预留一个指向一个数据结构的指针。这样的话,增加一个成员直接在这个数据结构中修改,而不是在类中修改。于是,新成员的加入不会导致类尺寸的改变。当然,为了访问新成员,需要给这个类定义几个操作函数。这种情况下,DLL必须是被客户程序隐式(implicitly)连接的。
(4)为了解决前一点的问题,也可以给所有的导出类设计一个纯接口的类,但此时,客户程序将无法从这些导出类继续派生,DLL导出类的层次机构也将无法维持。
(5)发布两个版本的DLL和LIB文件(Debug版本和Release版本)。因为如果只发布Release版本,开发者将无法调试他们的程序,因为Release版与Debug版使用了不同的堆(Heap)管理器,因而当Debug版本的客户程序释放Release版本DLL申请的内存时,会导致运行时错误(Runtime failure)。有一种办法可以解决这个问题,就是DLL同时提供申请和释放内存的函数供客户程序调用;DLL中也保证不释放客户程序申请的内容。通常遵守这个约定不是那么简单!
(6)在编译的时候,不要改变DLL导出类函数的默认参数,如果这些参数将被传递到客户程序的话。
(7)注意内联(inline)函数的更改。
(8)检查所有的枚举没有默认的元素值。因为当增加/删除一个新的枚举成员,你可能移动旧枚举成员的值。这就是为什么每一个成员应该拥有一个唯一标识值。如果枚举可以被扩展,也应该对其进行文档说明。这样,客户程序开发者就会引起注意。
(9)不要改变DLL提供的头文件中定义的宏。
2. 对DLL进行版本控制:如果主要的DLL发生了改变,最好同时将DLL文件的名字也改掉,就象微软的MFC DLL一样。例如,DLL文件可以按照如下格式命名:Dll_name_xx.dll,其中xx就是DLL的版本号。有时候DLL中做了很大的改动,使得向后兼容性问题无法解决。此时应该生成一个全新的DLL。将这个新DLL安装到系统时,旧的DLL仍然保留。于是,旧的客户程序仍然能够使用旧的DLL,而新的客户程序(使用新DLL编译、连接)可以使用新的DLL,两者互不干涉。
3. DLL的向后兼容性测试:还有很多很多中可能会破坏DLL的向后兼容性,因此实施DLL的向后兼容性测试是非常必要的!
接下去,我将来讨论一个虚函数的问题,以及对应的一个解决方案。
虚函数与继承
首先来看一下如下的虚函数和继承结构:
/**********DLL导出的类 **********/
class EXPORT_DLL_PREFIX VirtFunctClass{
public:
VirtFunctClass(){}
~VirtFunctClass(){}
virtual void DoSmth(){
//this->DoAnything();
// Uncomment of this line after the corresponding method
//will be added to the class declaration
}
//virtual void DoAnything(){}
// Adding of this virtual method will make shift in
// table of virtual methods
};
/**********客户程序,从DLL导出类派生一个新的子类**********/
class VirtFunctClassChild : public VirtFunctClass {
public:
VirtFunctClassChild() : VirtFunctClass (){}
~VirtFunctClassChild(){};
virtual void DoSomething(){}
};
(1)DLL的每个导出类(或者它的父类)至少包含一个虚函数。这样,这个类就会始终保存一个指向虚函数表的指针成员。这么做可以方便后来新的虚函数的加入。
(2)如果你要给一个类增加一个虚函数,那么将它加在所有其它虚函数的后面。这样就不会改变虚函数表中原有函数的地址映射顺序。
(3)如果你打算以后给一个类扩充类成员,那么现在预留一个指向一个数据结构的指针。这样的话,增加一个成员直接在这个数据结构中修改,而不是在类中修改。于是,新成员的加入不会导致类尺寸的改变。当然,为了访问新成员,需要给这个类定义几个操作函数。这种情况下,DLL必须是被客户程序隐式(implicitly)连接的。
(4)为了解决前一点的问题,也可以给所有的导出类设计一个纯接口的类,但此时,客户程序将无法从这些导出类继续派生,DLL导出类的层次机构也将无法维持。
(5)发布两个版本的DLL和LIB文件(Debug版本和Release版本)。因为如果只发布Release版本,开发者将无法调试他们的程序,因为Release版与Debug版使用了不同的堆(Heap)管理器,因而当Debug版本的客户程序释放Release版本DLL申请的内存时,会导致运行时错误(Runtime failure)。有一种办法可以解决这个问题,就是DLL同时提供申请和释放内存的函数供客户程序调用;DLL中也保证不释放客户程序申请的内容。通常遵守这个约定不是那么简单!
(6)在编译的时候,不要改变DLL导出类函数的默认参数,如果这些参数将被传递到客户程序的话。
(7)注意内联(inline)函数的更改。
(8)检查所有的枚举没有默认的元素值。因为当增加/删除一个新的枚举成员,你可能移动旧枚举成员的值。这就是为什么每一个成员应该拥有一个唯一标识值。如果枚举可以被扩展,也应该对其进行文档说明。这样,客户程序开发者就会引起注意。
(9)不要改变DLL提供的头文件中定义的宏。
2. 对DLL进行版本控制:如果主要的DLL发生了改变,最好同时将DLL文件的名字也改掉,就象微软的MFC DLL一样。例如,DLL文件可以按照如下格式命名:Dll_name_xx.dll,其中xx就是DLL的版本号。有时候DLL中做了很大的改动,使得向后兼容性问题无法解决。此时应该生成一个全新的DLL。将这个新DLL安装到系统时,旧的DLL仍然保留。于是,旧的客户程序仍然能够使用旧的DLL,而新的客户程序(使用新DLL编译、连接)可以使用新的DLL,两者互不干涉。
3. DLL的向后兼容性测试:还有很多很多中可能会破坏DLL的向后兼容性,因此实施DLL的向后兼容性测试是非常必要的!
接下去,我将来讨论一个虚函数的问题,以及对应的一个解决方案。
虚函数与继承
首先来看一下如下的虚函数和继承结构:
/**********DLL导出的类 **********/
class EXPORT_DLL_PREFIX VirtFunctClass{
public:
VirtFunctClass(){}
~VirtFunctClass(){}
virtual void DoSmth(){
//this->DoAnything();
// Uncomment of this line after the corresponding method
//will be added to the class declaration
}
//virtual void DoAnything(){}
// Adding of this virtual method will make shift in
// table of virtual methods
};
/**********客户程序,从DLL导出类派生一个新的子类**********/
class VirtFunctClassChild : public VirtFunctClass {
public:
VirtFunctClassChild() : VirtFunctClass (){}
~VirtFunctClassChild(){};
virtual void DoSomething(){}
};
- 上一篇:VC/VB 进行图像数据存储数据库心得
- 下一篇:用VC读取和分析格式化文本配置文件
收藏文章
精彩图集