DLL和进程的地址空间

版权声明:本文为博主原创文章,如需转载请注明出处 https://blog.csdn.net/bajianxiaofendui/article/details/83745160


     DLL是Windows开发人员经常使用到的一种技术,比如我们经常会把相同功能的代码封装到一个模块中,然后供其他需要使用该模块的程序共同调用,可以降低代码的复用性,使用起来非常方便;而且,当我们需要对外部提供自己公司的接口时,也会考虑到使用dll,它可以将我们内部实现的代码进行封装保护,而不会暴露给使用者。本文中,主要讨论DLL和调用DLL的进程的地址空间的关系,避免使用DLL的过程中,造成难以预测的内存泄漏问题,以及程序崩溃问题。

一,MT和MD的区别

     大家在使用windows编程时,都会发现有一个运行库的编译选项,MT和MD(MTD和MDD只是对应的debug调试模式)。我们以c/c++运行库为例,如果我们的应用程序选择连接到C/C++运行库的静态版本,那么诸如_tcscpy,malloc之类的函数会在内存中出现多次;但是如果连接到C/C++运行库的DLL版本,那么这些函数就只是在内存中出现一次,这意味着内存的使用率非常高,而这个也是windows操作系统从诞生之初就推出DLL的主要原因。
1,使用MD的场景:
(1)程序就不需要静态链接运行时库,可以减小软件的大小;
(2)所有的模块都采用/MD,使用的是同一个堆,不存在A堆申请,B堆释放的问题;
(3)用户机器可能缺少我们编译时使用的动态运行时库。(补充:如果我们软件有多个DLL,采用/MT体积增加太多,则可以考虑/MD + 自带系统运行时库)
2,使用MT的场景:
(1)有些系统可能没有程序所需要版本的运行时库,程序必须把运行时库静态链接上。
(2)减少模块对外界的依赖。

二,显示链接与隐式链接

     在应用程序能够调用一个DLL中的函数之前,必须将该DLL的文件映像映射到调用进程的地址空间中。我们可以通过两种方法来达到这一目的:隐式载入时链接或显示运行时链接。
1,显示链接
 通过WIN32 API来实现的一种链接方式。
LoadLibraryA将对应DLL映射到当前调用进程的地址空间中
GetProcAddress获取DLL中的导出函数地址,通过函数地址调用函数。
FreeLibrary从调用进程的地址空间中撤销对DLL的映射。
2,隐式链接
     当编写DLL工程代码时,如果链接器检测到DLL的源文件输出了至少一个变量或函数时时,那么链接器或会生成一个.lib文件,这个.lib文件非常小,这是因为它并不包含任何函数或者变量,它只是列出了所有被导出的函数和变量的符号名。
     把DLL隐式链接到调用进程时,,我们需要用到三个文件,DLL相关的.h文件,.lib文件和.dll文件,前两个文件需要通过配置工程项目属性,当编译的时候链接器把导出函数和变量相关信息加入到调用进程的可执行模块的导入段中,当可执行模块运行时,根据导入段的信息去加载对应的DLL。

三,DLL和进程的地址空间

     一旦系统将一个DLL的文件映像映射到调用进程的地址空间后,进程中的所有线程都可以调用该DLL中的函数了。此时,该DLL中的代码和数据全部存放在进程的地址空间中,且DLL中的函数创建的任何对象都为调用线程或调用进程所拥有-DLL绝对不会拥有任何对象。
     举个例子,如果DLL中的一个函数调用了VirtualAlloc,系统就会从调用进程的地址空间中预定地址空间区域(即申请内存)。如果稍后从进程的地址空间中撤销对DLL的映射,那么这块地址区域仍然被保持为预定状态(DLL被从调用进程中取消映射, 并不会主动释放动态分配的内存)。被预定的空间区域的拥有者是进程,只有当线程调用了VirtualFree函数或者当进程终止时,该区域才会被释放。
     也就是说,当一个DLL被加载到调用进程的地址空间内,DLL内所有申请的内存空间都是属于调用进程的,静态变量和全局变量都会是一份全新的实例。对于DLL中申请的内存要记得释放掉,并且严格遵循“谁申请谁释放”的原则,尽量不要dll中申请的内存,然后在调用线程中直接通过操作符或者API函数进行删除(当dll和调用进程使用的运行库编译选项不是同时为MD时,会导致程序崩溃)
     写了一个简单例子,来验证每次DLL映射到进程地址空间中时,DLL中的全局变量的实例都是重新创建的。
     DLL代码很简单,就是一个全局变量
在这里插入图片描述
     调用进程代码,就是不停地加载DLL,卸载DLL,加载DLL…
在这里插入图片描述
     初始运行时,程序的内存大小如图所示:
在这里插入图片描述
     10秒时间不到,内存增长到30多兆
在这里插入图片描述
     因此,在编写DLL工程时,对于new出来的全局变量,一定要在DLL从调用进程中撤销映射时,进行释放,避免引起内存泄漏。

猜你喜欢

转载自blog.csdn.net/bajianxiaofendui/article/details/83745160