《Windows核心编程》学习笔记(1)– 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) 建立带有输出原型/结构/符号的头文件。
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.

     /Lib:DelayImp.lib
     
/DelayLoad:MyDll.dll 

这样,基本上就已经完成了对一个Dll延迟加载的处理。关于具体原理,书中是这样说的:

The /Lib switch tells the linker to embed a special function, _ _delayLoadHelper, into your executable. The second switch tells the linker the following things:
•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.

posted @ 2011-08-08 15:29 飞翔荷兰人 阅读(...) 评论(...) 编辑 收藏

I Love Lina~