当线程调用调用DLL中的一个函数的时候,该函数会在线程栈中取得传给它的参数,并使用线程来存放它需要的局部变量。
此外,该DLL中的函数创建的任何对象都为调用线程或调用线程所拥有——DLL绝对不会拥有任何对象。
这里有个疑问:如果DLL内有静态变量,那这个DLL由线程A与B加载了,那A对线程数据的修改,B是否知道?如果知道,那这个全局变量是存储在哪里的呢?如果不知道,这个静态变量的体现是什么呢?
答案是:如果运行同一个可执行文件的的多个实例,这些实例将不会共享可执行文件中的全局变量和静态变量。Windows通过写时复制机制来保证这一点。DLL中的全局变量和局部变量也是通过完全相同的方法来处理的。
.lib:如果构建DLL至少导出了一个函数/变量,那么链接器就会同时生成.lib文件。在构建EXE文件时,连接器将每个.obj模块合并,并使用.lib文件来解析对导入的函数/变量的引用,从而生成.exe(它包含一个导入表,其中列出了必需的DLL和导入的符号)。
lib文件列出了该DLL导出的符号。另外,除了创建这个.lib文件之外,链接器还会在生成的DLL文件中嵌入一个导出符号表。这个(按字母顺序排列的)导出段列出了导出的变量、函数和类的符号名。链接器还会保存相对虚拟地址(RVA),表示每个符号可以在DLL模块中的何处可以找到
构建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模块映射到进程的地址空间中。
DLL注意事项:
在实际开发中,我们应该避免从DLL中导出变量,因为这等于是去掉了代码的一个抽象层,从而使得DLL的代码更加难以维护。
只有当导出C++类的模块使用的编译器与导入C++类的模块使用的编译器是由同一家厂商提供时,我们才可以导出C++类。
导入:
#ifdef MYLIBAPI
#define MYLIBAPI __declspec(dllexport)
#else
#define MYLIBAPI __declspec(dllimport)
#endif
可执行模块的源文件不应该在包含DLL的头文件之前定义MYLIBAPI。在编译前面这个可执行模块的源文件时,MYLIBAPI被定义为__declspec(dllimport),如果编译器看到一个变量、函数或C++类是用__declspec(dllimport)来修饰的,那么它会知道应该从某个DLL模块中(不需要知道具体的DLL)导入该符号。
接下来,为了创建可执行模块,链接器必须将所有的.obj模块合并到一起。链接器根据.lib文件得知被应用的符号确实存在,以及符号来自哪个DLL模块。
导入段:列出了该模块所需的DLL模块,以及它从每个DLL模块中引用的符号。DumpBin.exe(加上-imports开关)来查看一个模块的导入段。
可得知(个人认为,别当真):
__declspec(dllexport),是生成DLL时候,在可执行模块中生成导出段,从该导出段中可了解到DLL文件的导出的类、函数、变量。
__declspec(dllimport),编译器就知道该可执行文件的源文件要从DLL模块中导入的一些变量和函数。