Windows核心编程学习笔记-------19章

第19章 DLL基础

Windows API提供的所有函数都包含在DLL中。三个最重要的DLLKernel32.dll(管理内存、进程和线程)Use32.dll(执行与用户界面相关的任务)GDI32.dll(绘制图像和显示文字)

19.1DLL和进程的地址空间

第一层:DLL定位概要

在应用程序(或其他DLL)能调用一个DLL中函数前,必须将该DLL的文件映像映射到调用进程的地址空间中。两种方式:隐式载入时链接和显示运行时链接。

映射后,当调用DLL中一个函数时,该函数会在线程栈中取得传给他的参数,并用线程栈来存放它需要的局部变量。此外,DLL中函数创建的任何对象都为调用线程或进程所拥有。

19.2、纵观全局

第二层:理论流程概要

若一个EXE需要从另一个DLL模块中导入函数或变量,则需:

先构建DLL

1)先创建一个头文件,包含在DLL中导出的函数原型、结构及符号。为构建该DLLDLL的所有源文件需包含这个头文件。构建EXE时需同一个头文件。

2)创建源文件来实现DLL模块中导出的函数和变量。

3)构建DLL模块时,编译器会对每个源文件处理并生成一个.obj模块。

4)当所有.obj模块都创建完后,链接器将所有.obj模块内容合并,产生一个单独的DLL

5)若链接器检测到DLL源文件输出了至少一个函数或变量,则链接器还会生成一个.lib文件。它只是列出所有被导出的函数或变量的符号名。

再构建EXE(可执行模块)

1)所有引用了导出的函数、变量、数据结构或符号的源文件中,必须包含DLL对应的头文件。

2)构建EXE时,编译器会对每个源文件处理并生成一个.obj模块。

3)编译完后,链接器会将所有.obj模块内容合并生成一个exe。该exe(可执行模块)包含一个导入段,其中列出了所有它需要的DLL模块,以及它从每个DLL模块中引用的符号。执行exeOS的加载程序会执行5)。

4)加载程序先为新的进程创建一个虚拟地址空间,并将可执行模块映射到新进程的地址空间中。加载程序接着解析可执行模块的导入段。对导入段中列出的每个DLL,加载程序会在用户系统中对该DLL模块进行定位,并将该DLL映射到进程的地址空间中。由于DLL模块可从其他DLL模块中导入函数和变量,因此DLL模块可能有自己的导入段并需将它所有的DLL模块映射到进程的地址空间中。

加载程序将EXE和所有DLL映射到进程的地址空间后,进程的主线程可以开始执行。

第三层:实践详细流程

19.2.1、构建DLL模块

一个DLL可导出变量、函数或C++类。应避免导出变量。仅当导出的C++类的模块使用的编译器与导入的C++类的模块使用的编译器由同一厂商提供时,才可导出C++类。

巧妙之处(代码P515)DLL头文件中

#ifdef  MYLIBAPI

#else

#define MYLIBAPI  extern  “C”  _declspec(dllimport)

#endif

//导出的函数或变量

MYLIBAPI  int Add(int nLeft, int nRight);

DLL源文件中

#define  MYLIBAPI  extern  “C”  _declspec(dllexport)   //必须在dll头文件之前

#include “dll头文件

EXE源文件中

#include “dll头文件”       //不能定义MYLIBAPI

如此在DLL源文件中MYLIBAPI被定义为导出,EXE源文件中MYLIBAPI被定义为导入。

_declspec(dllexport):源文件中不必在被导出变量和函数前加此修饰。因编译器在解析头文件时会记住应导出哪些变量和函数。

_declspec(dllimport)DLL的头文件中加在导出的函数、变量或C++类前。非必需,但能略微提高效率。EXE的源文件中,编译器看到此符号,会知道该从DLL模块中导入该符号。

extern “C”:在编写C++代码时才使用(C不该使用)。因C++编译器通常会对函数名和变量名进行改编,链接时会出错。此修饰符是告诉编译器不要对变量名或函数进行改编。

_stdcall:即使是C,当用此约定时,Microsoft的编译器会对函数名进行改编。具体方法是:给函数名添加下划线前缀和一个特殊的后缀。该后缀由一个@符号跟作为参数传给函数的字节数组成。如:_declspec(dllexport) LONG _stdcall MyFunc(int a, int b);导出为_MyFunc@8。所以要防止改编。两种方式:创建一个.def文件,并在.def文件中包含一下类似下面段:

EXPORTS

         MyFunc

第二种方法(建议不用)是导出未经改编的函数名。在DLL的源文件中加入:

#pragma comment(linker, “/export:MyFunc=_MyFunc@8”)

.def文件格式:

LIBRARY  XX(dll名称这个并不是必须的,但必须确保跟生成的dll名称一样)

EXPORTS

[函数名] @ [函数序号] 

导出类:注意导出类和使用导出类同导出函数和使用导出函数类似,但在导出类中不可使用extern “C”符号。

导出段:当Microsoft C/C++编译器看到__declspec(dllexport)修饰的变量、函数或C++类时,会在生成的.obj文件中嵌入一些额外的信息。当链接器在链接DLL的所有.obj文件时,会解析这些信息。链接器会在生成的DLL文件中嵌入一个导出符号表。这个导出段列出了导出的变量、函数或类的符号名。还会保存相对虚拟地址,表示每个符号可在DLL模块的何处找到。(DumpBin.exe工具加入-exports可查看一个DLL的导出段)

导入段:当链接器看到__declspec(dllimport)修饰的导入符号时,会在生成的可执行模块中嵌入一个特殊的段,它的名字叫导入段。导入段列出了该模块所需的DLL模块,以及它从每个DLL模块中引用的符号。(DumpBin.exe工具加入-imports可查看一个DLL的导出段)

/***********************************
Moudle: MyLib.h
***********************************/

#ifdef MYLIBAPI
//MYLIBAPI should be defined in all of the DLL's source code 
//moudles before this header file is included.

//All functions/variables are being exported.

#else
//This header file is included by an EXE source code moudle/
//Indicate that all functions/variables are being imported.
#define MYLIBAPI extern "C" _declspec(dllimport)

#endif

//

//Define any data structures and symbols here.

//

//Define exported variables here.(Note:Avoid exporting variables.)
MYLIBAPI int g_nResult;

/

//Define exported function prototypes here.
MYLIBAPI int Add(int nLeft, int nRight);

End of File///

/****************************************
Moudle:MyLibFile1.cpp
****************************************/

#include <windows.h>

#define MYLIBAPI extern "C" _declspec(dllexport)

#include "MyLib.h"

/

int g_nResult;

int Add(int nLeft, int nRight)
{
	g_nResult = nLeft + nRight;
	return g_nResult;
}
///End of File/


19.2.2、构建可执行模块

1)包含dll的导出头文件:#include <>;注意不要定义MYLIBAPI宏。

2)包含lib文件:#pragma comment(lib, “”);为了让链接器确定代码中的导入符号来自哪个DLL

3)直接使用导出的变量、函数或C++类。

19.2.3、运行可执行模块

         OS的加载程序先为进程创建虚拟地址空间,然后将EXE映射到地址空间中,之后加载程序回检查EXE的导入段,对所需DLL进行定位并将它们映射到进程的地址空间中。

         因导入段只包含DLL名称,不包含DLL路径,因此加载程序必须搜索DLL,顺序为:

1)  包含可执行文件的目录

2)  Windows的系统目录,可通过GetSystemDirectory得到

3)  16位的系统目录,即Windows目录的System子目录

4)  Windows目录,可通过GetWindowsDirectory得到

5)  进程的当前目录

6)  PATH环境变量中列出的目录。

比如现在建立好了一个DLL导出了CMyClass类,客户也能正常使用这个DLL,假设CMyClass对象的大小为30字节。如果我们需要修改DLL 中的CMyClass类,让它有相同的函数和成员变量,但是给增加了一个私有的成员变量int类型,现在CMyClass对象的大小就是34字节了。当直接把这 个新的DLL给客户使用替换掉原来30字节大小的DLL,客户应用程序期望的是30字节大小的对象,而现在却变成了一个34字节大小的对象,糟糕,客户程序出错 了。

类似的问题,如果不是导出CMyClass类,而在导出的函数中使用了CMyClass,改变对象的大小仍然会有问题的。这个时候修改这个问题的唯一办法就是替 换客户程序中的CMyClass的头文件,全部重新编译整个应用程序,让客户程序使用大小为34字节的对象。

这就是一个严重的问题,有的时候如果没有客户程序的源代码,那么我们就不能使用这个新的DLL了。

具体用到时再baidu

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值