条目1、WINDOWS中最重要的三个包含API提供函数的DLL:Kernel32.dll、User32.dll、GDI32.dll。(P509)
条目2、DLL通常由一组可供任何应用程序使用的独立函数组成,因此它通常比应用程序更容易创建。(P510)
条目3、在应用程序(或其他DLL)能够调用一个DLL中的函数之前,必须将该DLL的文件映像映射到调用进程的地址空间中。DLL中的函数创建的任何对象都为调用线程或调用进程所拥有--DLL绝对不会拥有任何对象。(P510)
条目4、要导出的函数可以通过__declspec(dllexport)来修饰,或在.def文件中给出。
条目5、C++编译器通常会对函数名和变量名进行改编(mangle),这在链接的时候会导致严重的问题。因此我们需要extern "C" 修饰符来避免这种改编。
验证:
//mangle.cpp //compile: cl /c mangle.cpp, link /DLL mangle.obj #include <windows.h> __declspec(dllexport) int fun(int a,int b) { return a + b; }
通过dumpbin.exe -exports mangle.dll 查看:
Dump of file mangle.dll
File Type: DLL
... ...
ordinal hint RVA name
1 0 00001000 ?fun@@YAHHH@Z <---- 这里!!! 导出的函数名已经被更改!!!
... ...
条目6、当函数使用__stdcall(WINAPI)调用约定时,即使根本没有用到C++,微软的C编译器也会对C函数的名称进行改编。(P518)
换句话说,即便使用C语言编写DLL,如果导出函数采用的是__stdcall调用约定,那么微软的C编译器也会对该函数名称进行改编。
验证:
//mangle.c //compile:cl /c mangle.c , link /DLL mangle.obj #include <windows.h> __declspec(dllexport) int __stdcall fun(int a,int b) { return a + b; }
通过dumpbin.exe -exports mangle.dll 查看:
Dump of file mangle.dll
File Type: DLL
... ...
ordinal hint RVA name
1 0 00001000 _fun@8 <---- 这里!!! 导出的函数名已经被更改!!!
... ...
像这种情况下,如果函数名称不想被编译器改编,可以通过2种方式解决这个问题:
a、创建.def文件,并在.def文件中包含一个EXPORTS段,写明导出函数。如:
EXPORTS
fun
b、在源文件中添加下面一行代码:
#pragma comment(linker,"/export:fun=_fun@8")
方式b实际上让DLL导出了2个符号。一个为fun,一个为_fun@8。
条目7、在构建可执行模块的时候,可通过__declspec(dllimport)修饰符或extern关键字来标识一个导入函数。通常使用__declspec(dllimport)会使编译器产生略微高效的代码。(P520)
条目8、加载程序对可执行模块中导入段所描述的DLL采取如下搜索顺序(P522):
(1) 包含可执行文件的目录
(2) WINDOWS的系统目录,该目录可以通过GetSystemDirectory得到
(3) 16位的系统目录、即Windows目录中的System子目录
(4) Windows目录,该目录可以通过GetWindowsDirectory得到
(5) 进程的当前目录
(6) PATH环境变量中所列出的目录
注意:对应用程序当期目录的搜索位于Windows目录之后。这个改变始于WINDOWS XP SP2,其目的是为了防止加载程序在应用程序的当前目录中找到伪造的系统DLL并将它们载入。
条目9、加载程序会对载入的DLL模块进行记录,因此即便多个模块用到了同一个模块,该模块也只会被载入和映射一次。(P522)