winston 发表于 2012-3-17 23:02:04

《Windows核心编程》学习笔记(21)– DLL基础(隐式调用DLL)

.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模块映射到进程的地址空间中。
隐式调用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环境变量中所列出的目录。

页: [1]
查看完整版本: 《Windows核心编程》学习笔记(21)– DLL基础(隐式调用DLL)