DLL 导出类时DLL HELL错误及其解决方案

DLL除了可以导出函数之外,还可以导出类。
然而,导出类的DLL在维护和修改时有很多地方必需很小心,增加成员变量、修改导出类的基类等操作都可能导致意想不到的后果。当用户更新了最新版本的DLL库后,应用程序可能再也不能工作了。这就是DLL Hell问题。
如导出如下类

Class A
{
public:
    int get_a();
private:
    int a;
}

当需要再新版本的DLL导出类中作如下修改

Class A
{
public:
    int get_a();
private:
    int b;
    int a;
}

应用程序使用的代码如下

A a;
...

当更新DLL后,由于应用程序编译A a;时已经给a按原始dll中A的大小分配了内存。类内成员变量a的类内偏移地址发生了改变,导致无法正确访问到a.
另一个问题在于,新的类A的内存大小比原始类A大,而调用语句A a;的内存是应用程序根据原始类A分配的。导致新A的多出内容并没有被分配内存!

总结一下,在如下情况下,(可能)会发生DLL hell;
1) 应用程序直接访问类的公有变量,而该公有变量在新DLL中定义的位置发生了变化;
2) 应用程序调用类的一个虚函数,而新的类中,该虚函数的前面又增加了一个虚函数;
3) 新类的后面增加了成员变量,并且新类的成员函数将访问、修改这些变量;
4) 修改了新类的基类,基类的大小发生了变化;

从如上情况可以看出,当
(1)类的大小,
(2)类成员的偏移地址,
(3)虚函数的顺序
以上三者发生变化时,会触发Dll hell.

那么应该如何避免DLL hell呢?
编写dll调用程序时应该:
1,不直接生成类的实例。对于类的大小,当我们定义一个类的实例,或使用new语句生成一个实例时,内存的大小是在编译时决定的。要使应用程序不依赖于类的大小,只有一个办法:应用程序不生成类的实例,使用DLL中的函数来生成。把导出类的构造函数定义为私有的(privated),在导出类中提供静态(static)成员函数(如NewInstance())用来生成类的实例。因为 NewInstance()函数在新的DLL中会被重新编译,所以总能返回大小正确的实例内存。
2,不直接访问成员变量。应用程序直接访问类的成员变量时会用到该变量的偏移地址。所以避免偏移地址依赖的办法就是不要直接访问成员变量。把所有的成员变量的访问控制都定义为保护型(protected)以上的级别,并为需要访问的成员变量定义Get或Set方法。Get或Set方法在编译新DLL时会被重新编译,所以总能访问到正确的变量位置。
3,忘了虚函数吧,就算有也不要让应用程序直接访问它。因为类的构造函数已经是私有 (privated)的了,所以应用程序也不会去继承这个类,也不会实现自己的多态。如果导出类的父类中有虚函数,或设计需要(如类工场之类的框架),一定要把这些函数声明为保护的(protected)以上的级别,并为应用程序重新设计调用该虑函数的成员函数。这一点也类似于对成员变量的处理。
而导出类不要导出除了函数以外的任何内容!!
至于为什么虚函数重排序会导致Dl hell而普通函数不会,这可能和C++中类的内存模型有关系,有待后面补充。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值