.DLL和进程的地址空间
创建DLL通常比创建应用程序容易,因为DLL通常由一组可供任何应用程序使用的独立函数组成。在DLL中,通常没有用来处理消息循环或创建窗口的代码。DLL只不过是一组源代码模块,每个模块包含一些可供应用程序(可执行文件)或其他DLL调用的函数。
在应用程序(或其他DLL)能够调用一个DLL中的函数之前,必须将该DLL的文件映像映射到调用进程的地址空间中。我们可以通过两种方法来达到这一目的:隐式载入时连接 (implicit load-time linking)或显示运行时链接 (explicit run-time linking)。
一旦系统将一个DLL的文件映像映射到调用进程的地址空间中之后,进程中的所有线程就可以调用该DLL种的函数了。事实上,该DLL几乎完全丧失了它的DLL身份:对进程中的线程来说,该DLL中的代码和数据就像是一些附加的代码和数据,碰巧被放在进程地址空间中。
注意:当一个模块提供一个内存分配函数的时候,它必须同时提供另一个用来释放内存的函数。
例如:
VOID EXEFunc()
{
PVOID pv = DLLFunc();
……
DLLFreeFunc(pv);
}
PVOID DLLFunc()
{
PVOID pv = malloc(100);
return (pv);
}
BOOL DLLFreeFunc(PVOID pv)
{
return(free(pv));
}
构建DLL需要的步骤
(1)必须先创建一个头文件,在其中包含我们想要在DLL中导出的函数原型、结构以及符号。在构建可执行文件的时候需要用到同一个头文件。
(2)创建C/C++源文件来实现想要在DLL模块中导出的函数和变量。由于在构建可执行模块的时候不需要这些源文件,因此创建该DLL得公司可以将这些源代码作为公司的机密。
(3)在构建该DLL模块的时候,编译器会对每个源文件进行处理并产生一个.obj模块(每个源文件对应一个.obj模块)。
(4)当所有.obj模块都创建完毕后,连接器会将所有.obj模块的内容合并起来,产生一个单独的DLL映像文件。这个映像文件包含DLL中所有的二进制代码以及全局静态变量。为了执行可执行模块,这个文件时必须的。
(5)如果链接器检测到DLL的源文件输出了至少一个函数或变量,那么链接器还会产生一个.lib文件。这个.lib文件非常小,这是因为它并不包含任何函数或变量。它只是列出了所有被导出的函数和变量的符号名。为了构建可执行模块,这个文件时必须的。
一旦创建了DLL模块,我们将可以通过下列步骤来构建可执行模块:
(1)在所有引用了导出的函数、变量、数据结构或符号的源文件中,必须包含由DLL的开发人员所创建的头文件。
(2)创建C/C++源文件来实现想要包含在可执行模块中的函数和变量。当然,代码可以引用在DLL的头文件中定义的函数和变量。
(3)在构建可执行模块的时候,编译器会对每个源文件进行处理并产生一个.obj模块(每个源文件对应一个.obj模块)。
(4)链接器将所有.obj模块的内容合并起来,产生一个单独的可执行映像文件。这个映像文件包含了可执行文件中所有的二进制代码以及全局/静态变量。该可执行模块还包含一个导入段(import section),其中列出了所有它需要的DLL模块的名称。
一旦DLL和可执行模块都已构建完毕,进程就可以执行了。当我们试图运行可执行模块的时候,操作系统的加载程序会执行下面的步骤。
(5)加载程序先为新的进程创建一个虚拟地址空间,并将可执行模块映射到新进程的地址空间中。加载程序接着解析可执行模块的导入段。对导入段中列出的每个DLL,加载程序会在用户的系统中对该DLL模块进行定位,并将该DLL映射到进程的地址空间中。注意,由于DLL模块可以从其它DLL模块中导入函数和变量,因此DLL模块可能有自己的导入段并需要将它所需的DLL模块映射到进程的地址空间中。
上述过程简述如下:
1 ) 建立带有输出原型 / 结构 / 符号的头文件。
2 ) 建立实现输出函数 / 变量的C / C ++ 源文件。
3 ) 编译器为每个C / C ++ 源文件生成.obj模块。
4 ) 链接程序将生成DLL的.obj模块链接起来。
5 ) 如果至少输出一个函数 / 变量,那么链接程序也生成lib 文件。
创造EXE:
6 ) 建立带有输入原型 / 结构 / 符号的头文件。
7 ) 建立引用输入函数 / 变量的C / C ++ 源文件。
8 ) 编译器为每个C / C ++ 源文件生成.obj源文件。
9 ) 链接程序将各个.obj模块链接起来,产生一个.exe文件
(它包含了所需要DLL模块的名字和输入符号的列表)。
运行应用程序:
10 ) 加载程序为.exe 创建地址空间。
11 ) 加载程序将需要的DLL加载到地址空间中进程的主线程开始执行;
应用程序启动运行。
二.创建\使用动态链接库
隐式调用DLL
代码如下:
1. DLL文件:
/*********************************************************************
Mylib.h
*************************************************************************/
#ifdef _EXPORTDLL
#define MYLIBAPI extern "C" __declspec(dllexport)
#else
#define MYLIBAPI extern "C" __declspec(dllimport)
#endif
MYLIBAPI int g_nResult;
MYLIBAPI int Add(int a, int b);
/*********************************************************************
Mylib.cpp
*************************************************************************/
#include <windows.h>
#define _EXPORTDLL //需要在#include "Mylib.h" 前面定义
#include "Mylib.h"
int g_nResult;
int Add(int a, int b)
{
g_nResult = a + b;
return g_nResult;
}
DLL文件编译完成后可以利用VS的DUMPBIN工具查看导出函数:
2. EXE文件(必须把DLL工程的.h, .lib, .dll文件放在EXE工程文件夹下):
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include "Mylib.h" //包含Dll的头文件
#pragma comment(lib,"Mylib.lib") //链接.lib文件
int main(void)
{
int a = Add(1,2);
printf("Add() = %d\n", a); //可以像平常一样调用函数和变量
printf("g_nResult = %d\n", g_nResult);
Sleep(10000);
}
EXE文件运行结果:
由于导入段只包含DLL的名称,不包含DLL的路径,因此加载程序必须在用户磁盘上搜索DLL。下面是加载程序的搜索顺序:
- 包含可执行文件的目录;
- Windows的系统目录,该目录可以通过GetSystemDirectory得到;
- 16位的系统目录,即Windows目录中的System子目录;
- Windows目录,该目录可以通过GetWindowsDirectory得到;
- 进程的当前目录;
- PATH环境变量中所列出的目录。
延迟加载Dll
说实话以前没有注意过这么一个好用的特性。看过本节后我知道了它。延迟加载用于隐式链接Dll的程序中。
要使一个dll延迟加载,就要在.exe上做手脚。需要修改工程属性,添加如下命令,则在链接时在输入节中将不包含该Dll的任何信息,从而导致.exe加载时不会加载该Dll.
/ DelayLoad:MyDll.dll
这样,基本上就已经完成了对一个Dll延迟加载的处理。关于具体原理,书中是这样说的:
•Remove MyDll.dll from the executable module ' s import section so that the operating system loader does not implicitly load the DLL when the process initializes.
•Embed a new Delay Import section (called .didata) in the executable indicating which functions are being imported from MyDll.dll.
•Resolve calls to the delay - loaded functions by having calls jump to the _ _delayLoadHelper function.
如果短期内不想使用延迟加载的dll,那么可以暂时卸载它,需要的操作是添加一个链接开关:
/Delay:unload
然后在代码中,需要想要卸载Dll的地方调用 _ _FUnloadDelayLoadedDLL ,传递的名字必须与 /DelayLoad 开关所传递的名字完全相同(包括大小写)。不要使用FreeLibrary代替 _ _FUnloadDelayLoadedDLL 。如果不添加/Delay:unload开关而调用 _ _FUnloadDelayLoadedDLL ,那么这个函数会返回FALSE而不卸载任何dll.
from: http://blog.youkuaiyun.com/lostmyka/article/details/8696132