Windows下的动态链接
DLL简介
DLL的设计目的与共享对象有些出入,DLL更加强调模块化,即微软希望通过DLL机制加强软件的模块化设计,使得各种模块之间能够松散地组合、重用和升级。
-
进程地址空间和内存管理:进程拥有独立的地址空间
-
基地址(Base Address)和相对地址(RVA, Relative Virtual Address)
-
DLL共享数据段
-
导出:__declspec(dllexport) 修饰
-
导入:__declspec(dllexport) 修饰
-
通过externel "C"修饰来使用C语言的符号修饰
-
使用.def文件中的IMPORT和EXPORTS段来声明导入导出符号,好处是可以控制符号的符号名。
-
导入库(Import Library):.lib文件用来描述导出符号,并不包含代码和数据。
-
DLL显式运行时链接
-
LoadLibrary:与dlopen类似
-
GetProcAddress:与dlsym类似
-
FreeLibrary:与dlclose类似
-
符号导出导入表
-
导出表
-
导出地址表
-
符号名表
-
名字序号对应表
-
-
EXP文件:导出表的临时文件
-
导出重定向:正常情况下,导出表的地址数组中包含的是函数的RVA,但是如果这个RVA指向的位置位于导出表中,那么表示这个符号被重定向了。它是指向一个ASCII的字符串,是符号重定向后的DLL文件名和符号名。
-
导入表:Windows将会保证依赖关系的正确和所有的导入符号都被正确地解析,如果某个模块无法正确加载,系统将会提示错误,终止运行该程序。
-
延迟载入
-
导入函数的调用
DLL优化
-
重定基地址:不同于代码段地址无关,它采用的是装载时重定位的方法。
-
序号
-
导入函数绑定
C++与动态链接
使用C++编写共享库要复杂的多,并且难以更新。因为C++标准值规定了语言层面的规则,而对二进制级别却没有任何规定。为了解决类似的兼容性问题,微软公司很早就开始了组件对象模型(COM,Component object model)的开发工作
-
所有的接口函数都应该是抽象的。所有的方法都应该是纯虚的。
-
所有的全局函数都应该使用extern "C"来防止名字修饰的不兼容。并且导出函数的都应该是__stdcall调用规范的。这样即使用户本身的程序是默认以__cdecl方式编译的,对于DLL的调用也能够正确。
-
不要使用C++标准库STL。
-
不要使用异常
-
不要使用虚析构函数。可以创建一个destroy()方法并且重载delete操作符并且调用destroy()。
-
不要在DLL里面申请内存,而且在DLL外释放。不同的DLL和可执行文件可能使用不同的堆,在一个堆里面申请内存而在另外一个堆里面释放会导致错误。对于内存分配相关的函数不应该是inline的,以防止它在编译时被展开到不同的DLL和可执行文件。
-
不要在接口中使用重载方法(一个方法多重参数)。因为不同的编译器对于vtable的安排可能不同。
DLL HELL
-
产生原因:
-
使用旧版本的DLL替代原来一个新版本的DLL
-
新版DLL中的函数无意发生改变
-
新版DLL的安装引入一个新BUG
-
-
解决方法:
-
静态链接
-
防止DLL覆盖:使用Windows文件保护(WFP)技术来缓解。它能阻止未经授权的应用程序覆盖系统的DLL。第三方应用程序不能覆盖操作系统DLL文件。
-
避免DLL冲突:让每个应用程序拥有一份自己依赖的DLL,并且把问题DLL的不同版本放到该应用程序的文件夹中,而不是系统文件夹中。
-
.NET:Manifest文件描述程序集的名字,版本号以及各种资源,同时也描述了该程序集的运行所依赖的资源,包括DLL以及其他资源文件等。它是一个XML的描述文件,每个DLL和EXE都有自己的Manifest文件。
-