所有内容摘自《windows核心编程》
一:DLL基础
1.dll有助于节省内存,如果两个或两个以上的应用程序使用同一个dll,那么该dll只需载入内存一次,以后所有的应用程序就可以共享该dll在内存中的页面。C/C++运行库就是一个绝佳的例子。许多应用程序都会用到C/C++运行库,如果所有的应用程序都链接到C/C++运行库的静态版本,那么诸如_tcscpy,malloc之类的函数会在内存中出现多次。但是,如果这些应用程序链接到C/C++运行库的DLL版本,那么这些函数在内存中只会出现一次,这意味着内存的使用率更高。
二:DLL和进程的地址空间
1.在应用程序能够调用一个DLL中的函数之前,必须将该DLL的文件映像映射到调用进程的地址空间中。可以通过隐式载入时链接或显示运行时链接。
2.一旦系统将一个DLL的文件映像映射到调用进程的地址空间中之后,进程中的所有县城就可以调用该DLL中的函数了。
三:纵观全局
构建DLL:
1.头文件,其中包含待导出函数的原型、结构和符号申明
2.C/C++源文件,其中包含待导出函数的实现和变量
3.编译器为每个C/C++源文件生成.obj文件
4.链接器将每个.obj模块合并,从而生成DLL
5.如果至少导出了一个函数/变量,那么链接器会同时生成.lib文件
构建EXE
1.头文件,其中包含待导出函数的原型、结构和符号的声明
2.C/C++源文件,其中包含待导出函数的实现和变量
3.编译器为每个C/C++源文件生成.obj文件
4.链接器将每个.obj模块合并,并使用.lib文件来解析对导入的函数/变量的引用,从而生成了.exe(它包含一个导入表,其中列出了必须的dll和导入的符号)
构建DLL需要以下步骤:
1.必须先创建一个头文件,在其中包含我们想要在dll中导出的函数原型、结构以及符号。为了构建该dll,dll的所有源文件需要包含这个头文件。在构建可执行文件的时候需要用到同一个头文件。
2.创建C/C++源文件来实现想要在dll模块中导出的函数和变量。
3.在构建dll模块的时候,编译器会对每个源文件进行处理并产生一个.obj模块,每个源文件对应一个.obj模块。
4.当所有的.obj模块都创建完毕后,链接器将对所有.obj模块的内容合并起来,产生一个单独的dll映像文件。这个映像文件或模块包含dll中所有的二进制代码以及全局/静态变量。
5.如果链接器检测到dll的源文件输出了至少一个函数或变量,那么链接器还会产生一个.lib文件。这个.lib文件非常小,这是因为它并不包含任何函数或变量。它只是列出了所有被导出的函数和变量的符号名。为了构建可执行模块,这个文件是必须的。
构建可执行模块步骤:
1.在所有引用了dll导出的函数、变量、数据结构或符号的源文件中,必须包含由dll的开发人员所创建的头文件。
2.创建C/C++源文件来实现想要包含在可执行模块中的函数和变量。
3.在构建可执行模块的时候,编译器会对每个源文件进行处理并为每一个源文件产生一个.obj文件
4.当所有.obj模块都创建完毕后,链接器会将所有.obj模块的内容合并起来,产生一个单独的可执行映像文件。这个映像文件包含了可执行文件中所有的二进制代码以及全局/静态变量。该可执行模块还包含一个导入段(import section),其中列出了所有它需要的dll模块的名称。此外,对列出的每个dll,该段还记录了可执行文件的二进制代码从中引用的函数和变量的符号名。
一旦dll和可执行模块都构建完毕,进程就可以执行了。当我们试图运行可执行模块的时候,操作系统的加载程序会执行下面的步骤。
5.加载程序先为新的进程创建一个虚拟地址空间,并将可执行模块映射到新进程的地址空间中。加载程序接着解析可执行模块的导入段,对导入段中列出的每个dll,加载程序会在用户的系统中对该dll模块进行定位,并将该dll映射到进程的地址空间中。注意,由于dll模块可以从其他dll模块中导入函数和变量,因此dll模块可能有自己的导入段并需要将它所需的dll模块映射到进程的地址空间中。
构建DLL模块:
1.在导出符号、变量、函数、类的头文件中,对要导出的变量(不建议)或函数要用extern "C" __declspec(dllexport)来修饰。
extern "C":