Windows下的动态链接

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文件。

小结

原文地址:https://www.cnblogs.com/fr-ruiyang/p/14527909.html