RTY 实践出真知
posts - 319, comments - 22, trackbacks - 0, articles - 11
C++博客
::
首页
::
新随笔
::
联系
::
聚合
::
管理
DLL导出类的问题
Posted on 2011-08-10 07:23
RTY
阅读(800)
评论(0)
编辑
收藏
引用
所属分类:
C/C++
、
Windows
DLL导出类的问题
http://www.diybl.com/course/3_program/c++/cppjs/200833/102641.html
DLL动态链接库是程序复用的重要方式,DLL可以导出函数,使函数被多个程序复用,DLL中的函数实现可以被修改而无需重新编译和连接使用该DLL的应用程序。作为一名面向对象的程序员,希望DLL可以导出类,以便在类的层次上实现复用。所幸的是,DLL确实也可以导出类。
然而事实却没这么简单,导出类的DLL在维护和修改时有很多地方必需很小心,增加成员变量、修改导出类的基类等操作都可能导致意想不到的后果,也许
用户
更新了最新版本的DLL库后,应用程序就再也不能
工作
了。这就是著名的DLL Hell(DLL地狱)问题。
DLL地狱问题是怎么产生的呢?看下面的例子,假设DLL有一个导出类ClassD1:
class ClassD
{
public:
int GetInt();
private:
int m_i;
};
int ClassD::GetInt()
{
return m_i;
}
应用程序使用现在的代码来使用这个类:
ClassD d;
printf(“%d”, d.GetInt());
程序进行正正常,没有什么问题。后来DLL需要升级,对ClassD进行了修改,增加了一个成员变量,如下:
class ClassD // 修改后
{
public:
int GetInt();
private:
int m_i2;
int m_i;
};
把新的DLL编译连接完成后,复制到应用程序目录,这个倒楣的应用程序调用GetInt方法恐怕再也无法得正确的值了。事实上它还算幸运的,如果GetInt的实现改成如下这样,那么它马上就要出错退出了。
int ClassD::GetInt() // 修改后
{
return m_i++;
}
这样的事情,称它是个地狱(Hell)一点也不夸张。为什么会出错呢?我们要先从类实例的创建开始,看看使用一个类的工作过程。
首先,程序语句“ClassD d;”为这个类申请一块内存。这块内存保存该类的所有成员变量,以及虚函数表。内存的大小由类的声明决定,在应用程序编译时就已经确定。
然后,当调用“d.GetInt()”时,把申请的这一块内存做为this指针传给GetInt函数,GetInt函数从this指向的位置开始,加上m_i应有的偏移量,计算m_i所在的内存位置,并从该位置取数据返回。m_i相对this的偏移量是由m_i在类中定义的位置决定的,定义在前的成员变量在内存中也更靠前。这个偏移量在DLL编译时确定。
当ClassD的定义改为修改后的状态时,有些东西变了。
第一个变的是内存的大小。因为修改后的ClassD多了一个成员变量,所以内存也变大了。然而这一点应用程序并不知道。
第二个变的是m_i的偏移地址。因为在m_i之前定义了一个m_i2,m_i的实现偏移地址实际已经靠后了。所以d.GetInt()访问的将是原来m_i后面的那个位置,而这个位置已经超出原来那块内存的后部范围了。
很显然,在更换了DLL后,应用程序还按原来的大小申请了一块内存,而它调用的方法却访问了比这块内存更大的区域,出错再在所难免。
同样的情形还会发生在以下这些种情况中:
1)
应用程序直接访问类的公有变量,而该公有变量在新DLL中定义的位置发生了变化;
2)
应用程序调用类的一个虚函数,而新的类中,该虚函数的前面又增加了一个虚函数;
3)
新类的后面增加了成员变量,并且新类的成员函数将访问、修改这些变量;
4)
修改了新类的基类,基类的大小发生了变化;
等等,总言而之,一不小心,你的程序就会掉进地狱。通过对这些引起出错的情况进行分析,会
发现
其实只有三点变化会引起出错,因为这三点是使用这个DLL的应用程序在编译时就需要确定的内容,它们分别是:
1)
类的大小;
2)
类成员的偏移地址;
3)
虚函数的顺序。
要想做一个可升级的DLL,必需避免以上三个问题。所以以下三点用来使DLL远离地狱。
1,不直接生成类的实例。对于类的大小,当我们定义一个类的实例,或使用new语句生成一个实例时,内存的大小是在编译时决定的。要使应用程序不依赖于类的大小,只有一个办法:应用程序不生成类的实例,使用DLL中的函数来生成。把导出类的构造函数定义为私有的(privated),在导出类中提供静态(static)成员函数(如NewInstance())用来生成类的实例。因为NewInstance()函数在新的DLL中会被重新编译,所以总能返回大小正确的实例内存。
2,不直接访问成员变量。应用程序直接访问类的成员变量时会用到该变量的偏移地址。所以避免偏移地址依赖的办法就是不要直接访问成员变量。把所有的成员变量的访问控制都定义为保护型(protected)以上的级别,并为需要访问的成员变量定义Get或Set方法。Get或Set方法在编译新DLL时会被重新编译,所以总能访问到正确的变量位置。
3,忘了虚函数吧,就算有也不要让应用程序直接访问它。因为类的构造函数已经是私有(privated)的了,所以应用程序也不会去继承这个类,也不会实现自己的多态。如果导出类的父类中有虚函数,或设计需要(如类工场之类的框架),一定要把这些函数声明为保护的(protected)以上的级别,并为应用程序重新设计调用该虑函数的成员函数。这一点也类似于对成员变量的处理。
如果导出的类能遵循以上三点,那么以后对DLL的升级将可以认为是安全的。
如果对一个已经存在的导出类的DLL进行维护,同样也要注意:不要改动所有的成员变量,包括导出类的父类,无论定义的顺序还是数量;不要动所有的虚函数,无论顺序还是数量。
总结起来,其实是一句话:导出类的DLL不要导出除了函数以外的任何内容。听起来是不是有点可笑呢!
事实上,建议你在发布导出类的DLL的时候,重新定义一个类的声明,这个声明可以不管原来的类里的成员变量之类的,只把接口函数列在类的声明里,如下面的例子:
class ClassInterface
{
privated:
ClassInterface();
public:
static ClassInterface * NewInstance();
int GetXXX();
void SetXXX();
void Function();
};
使用该DLL的应用程序用上面的定义作为ClassInterface的头文件,便不会有任何可能导致的安全问题。
DLL地狱问是归根结底是因为DLL当初是作为函数级共享库设计的,并不能真正提供一个类所必需的信息。类层上的程序复用只有Java和C#生成的类文件才能做到。
只有注册用户
登录
后才能发表评论。
【推荐】100%开源!大型工业跨平台软件C++源码提供,建模,组态!
相关文章:
Dynamic Library Design Guidelines (Xcode)
Qt internal error: qt_menu.nib could not be loaded.
Cocoa读取和写入plist文件
什么叫IOC(编程术语)
otool 与dylib
找不到 dirent.h 文件
vc调试窗口表达式格式化资料
Context Operator (C/C++ Language Expressions)
Managed Expressions in C++ (VC 2010 调试)
Find path of an application
网站导航:
博客园
IT新闻
BlogJava
知识库
博问
管理
Powered by:
C++博客
Copyright © RTY
日历
<
2011年8月
>
日
一
二
三
四
五
六
31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1
2
3
4
5
6
7
8
9
10
常用链接
我的随笔
我的评论
我参与的随笔
留言簿
(12)
给我留言
查看公开留言
查看私人留言
随笔分类
(538)
C/C++(70)
CSS(41)
JavaScript(6)
Linux(7)
Lua专题(3)
Mac os(34)
Python(33)
QML(21)
Qt(65)
TBB(4)
Windows(38)
XML
编程常识(37)
软件(38)
数据库
需要注意的(14)
质量保障(14)
转载随笔(113)
随笔档案
(319)
2013年3月 (2)
2013年2月 (5)
2013年1月 (4)
2012年12月 (2)
2012年11月 (7)
2012年10月 (1)
2012年9月 (1)
2012年7月 (1)
2012年6月 (2)
2012年5月 (5)
2012年4月 (15)
2012年3月 (13)
2012年2月 (5)
2012年1月 (1)
2011年12月 (1)
2011年11月 (5)
2011年10月 (5)
2011年9月 (26)
2011年8月 (51)
2011年7月 (23)
2011年6月 (22)
2011年5月 (82)
2011年4月 (38)
2010年12月 (2)
文章分类
(11)
C/C++文章收集
Linux(3)
QT相关资料收集(8)
文章档案
(11)
2010年12月 (11)
官方教程
知识共享链接
搜索
最新评论
1. re: OSGi开发起步
评论内容较长,点击标题查看
--M.I
2. re: iOS中plist的创建,数据写入与读取
评论内容较长,点击标题查看
--泰国
3. re: Windows 7使用技巧:在当前路径下打开命令行(cmd)命令窗口
给力啊
--gy
4. re: codesign CSSMERR_TP_NOT_TRUSTED
你好,能告诉我你QQ么,我遇到了这问题,但是安装了证书后还是这样,能帮我看看么
--潮
5. re: Windows 7使用技巧:在当前路径下打开命令行(cmd)命令窗口
好用
--xxoo
阅读排行榜
1. 找不到 dirent.h 文件(9613)
2. 实现Qt日志功能并输出到文件(qDebug\qWarning\ qCritical\qFatal)(9204)
3. VirtualBox虚拟机安装Mac OS X Lion(8891)
4. Windows 7使用技巧:在当前路径下打开命令行(cmd)命令窗口(8107)
5. iOS中plist的创建,数据写入与读取(7677)
评论排行榜
1. 字符集编码与 C/C++ 源文件字符编译乱弹(收集转载)(4)
2. Windows 7使用技巧:在当前路径下打开命令行(cmd)命令窗口(4)
3. Intel Parallel Studio XE 2011(3)
4. QML与c++交互学习笔记(八) qt c++直接调用QML中的函数, 直接设置属性 (2)
5. VirtualBox虚拟机安装Mac OS X Lion(2)