DLL动态链接库是程序复用的重要方式,DLL可以导出函数,使函数被多个程序复用,DLL中的函数实现可以被修改而无需重新编译和连接使用该DLL的应用程序。作为一名面向对象的程序员,希望DLL可以导出类,以便在类的层次上实现复用。所幸的是,DLL确实也可以导出类。

然而事实却没这么简单,导出类的DLL在维护和修改时有很多地方必需很小心,增加成员变量、修改导出类的基类等操作都可能导致意想不到的后果,也许用户更新了最新版本的DLL库后,应用程序就再也不能工作了。这就是著名的DLLHell(DLL地狱)问题。

DLL地狱问题是怎么产生的呢?看下面的例子,假设DLL有一个导出类ClassD1:

class ClassD

{

public:

int GetInt();

private:

int m_i;

};

intClassD::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的实现改成如下这样,那么它马上就要出错退出了。

intClassD::GetInt() // 修改后

{

return m_i++;

}

这样的事情,称它是个地狱(Hell)一点也不夸张。为什么会出错呢?我们要先从类实例的创建开始,看看使用一个类的工作过程。

首先,程序语句“ClassDd;”为这个类申请一块内存。这块内存保存该类的所有成员变量,以及虚函数表。内存的大小由类的声明决定,在应用程序编译时就已经确定。

然后,当调用“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的时候,重新定义一个类的声明,这个声明可以不管原来的类里的成员变量之类的,只把接口函数列在类的声明里,如下面的例子:

classClassInterface

{

privated:

ClassInterface();

public:

static ClassInterface * NewInstance();

int GetXXX();

void SetXXX();

void Function();

};

使用该DLL的应用程序用上面的定义作为ClassInterface的头文件,便不会有任何可能导致的安全问题。

DLL地狱问是归根结底是因为DLL当初是作为函数级共享库设计的,并不能真正提供一个类所必需的信息。类层上的程序复用只有Java和C#生成的类文件才能做到。

DLL Hell(DLL地狱)浅谈相关推荐

  1. 监控某个dll被修改_浅谈动力环境监控系统技术标准

    动力环境监控系统技术标准规定了监控系统各因素指标.功能,对实现机房设备故障自动检测.无人化值守.降低维护保养成本的动环系统有重要的作用和意义.运用动环监控,能解决成本高.效率低.告警慢.信息落后能问题 ...

  2. 浅谈ASP.NET的内部机制(一)

    浅谈ASP.NET的内部机制(一) 前言:当一个Http请求发送给一个aspx页面时,服务器进行了哪些操作?又如何来解析这个请求?ASP.NET在接收请求后是怎么运行的,如怎么编译以及怎么样用托管的代 ...

  3. linux 易语言窗口程序_浅谈Linux入门的基本知识

    浅谈Linux入门的基本知识 图形模式与文字模式的切换方式Linux预设提供了六个命令窗口终端机让我们来登录. 默认我们登录的就是第一个窗口,也就是tty1,这个六个窗口分别为tty1.tty2 - ...

  4. 浅谈 Python 程序和 C 程序的整合

    浅谈 Python 程序和 C 程序的整合 Python 是一种用于快速开发软件的编程语言,它的语法比较简单,易于掌握,但存在执行速度慢的问题,并且在处理某些问题时存在不足,如对计算机硬件系统的访问, ...

  5. 浅谈SQL Server 对于内存的管理

    简介 理解SQL Server对于内存的管理是对于SQL Server问题处理和性能调优的基本,本篇文章讲述SQL Server对于内存管理的内存原理. 二级存储(secondary storage) ...

  6. 浅谈“三层结构”原理与用意(转帖)

    浅谈"三层结构"原理与用意 序 在刚刚步入"多层结构"Web应用程序开发的时候,我阅读过几篇关于"asp.net三层结构开发"的文章.但其多 ...

  7. 浅谈 Windows API 编程

    原文地址:http://blog.sina.com.cn/s/blog_46d85b2a01010qpt.html http://blog.sina.com.cn/s/articlelist_1188 ...

  8. build 之前执行task_浅谈VS编译自定义编译任务—MSBuild Task(csproject)-阿里云开发者社区...

    在上一篇浅谈.NET编译时注入(C#-->IL)中我们简单的反编译查看了几种c#语法糖和PostSharp在编译成IL时为我做的MSIL注入.紧接着在这节,要来看的就是MSBuild Task. ...

  9. Linux位置无关代码实现,浅谈位置无关代码

    原标题:浅谈位置无关代码 引言 最近参与的一个项目涉及到了二进制重写相关的问题,也因此看了几篇相关工具的论文.与之前曾经一直想做的动态装载有不少重合,因此在此做一个整理. 本文主要整理了动态库装载地址 ...

  10. 浅谈ASP.NET的内部机制(二)

    浅谈ASP.NET的内部机制(二)         前言:大家知不知道,一个Http Request是如何被传递给ASP.NET的?而且ASP.NET是如何知道一个 Http Request是请求的. ...

最新文章

  1. 是什么专业_聚焦专业:什么是好专业?考古专业明年会成为热门吗?
  2. python解决open()函数、xlrd.open_workbook()函数文件名包含中文,sheet名包含中文报错的问题
  3. idea启动java Maven项目,出现“ java: 程序包xxxx不存在“
  4. 多线程编程(9) - 认识等待函数 WaitForSingleObject
  5. TCP中间件_Delphi_client
  6. 雷达模糊函数 matlab_全场通用 | 雷达通信电子战,专业知识服务
  7. Mendix的Hybrid App本地开发最佳实践
  8. Python:自适应滤波器简介及其实现方法
  9. python除法运算定律有哪些_小学数学最重要的7个运算定律,都在这里了
  10. 全球及中国EOG放大器行业运营前景与发展动态研究报告2022版
  11. 【Vim】No write since last change
  12. 安卓bmi项目_BMI计算器安卓版下载
  13. 旧手机怎么当文件服务器,用旧手机做云存储服务器
  14. js身份证号码带*号处理
  15. OpenCV图像高光
  16. 微信小程序开发实战(24):选择图像
  17. PMBOK(第六版) PMP笔记——《第九章 项目资源管理》
  18. 拯救纠结症 选iPhone SE还是iPhone6?
  19. 使用阿里云的短信服务发送短信
  20. 高功率DC-DC4.5V-18V同步降压转换器18V600kHz2A输出电流参考资料

热门文章

  1. java ee 与se区别_Java SE和Java EE之间的主要区别是什么?
  2. 如何把heic格式转换为jpg?这几种方法建议收藏
  3. C++ 函数模板 懒人必备!
  4. MAPICS系统简单概述(zt)
  5. 同样的内核,为何linux干净稳定,而android臃肿又乌烟瘴气
  6. css实现虚线表格样式
  7. android 百度地图 505 问题
  8. m3 pcb开孔 螺丝_一种结构定位螺丝孔PAD的设计方法和PCB与流程
  9. 计算机专业英语词语缩略,计算机专业英语缩略词.doc
  10. ubuntu错误dpkg:error processing realplay