DLL编程技术

来源:互联网 发布:世界地图编辑软件 编辑:程序博客网 时间:2024/05/29 03:08
一、DLL原理
DLL(Dynamic Link Library)也就是“动态链接库”,是一个可以被其它应用程序共享的程序模块,其中封装了一些可以被共享的程序或资源。它虽然包含了可执行代码却不能单独执行,而应由相应的应用程序直接或间接调用。在 Windows 32 中,可以将 DLL 标记为共享以导致相同的行为。但是,每个进程的默认设置是拥有 DLL 数据的专用副本。以.dll、.DRV、.FON、.SYS为扩展名和许多以 .EXE 为扩展名的系统文件都可以是 DLL。


DLL可以有自己的数据段,但没有自己的堆栈,使用与调用它的应用程序相同的堆栈模式;一个DLL在内存中只有一个实例;DLL实现了代码封装性;DLL的编制与具体的编程语言及编译器无关,可以通过DLL来实现混合语言编程。DLL函数中的代码所创建的任何对象(包括变量)都归调用它的线程或进程所有。


二、调用约定和名字修饰约定
调用约定(Calling convention):决定函数参数传送时入栈和出栈的顺序,由调用者还是被调用者把参数弹出栈,以及编译器用来识别函数名字的修饰约定。
1.__stdcall 是 Pascal 程序的缺省调用方式,通常用于 Win32 API 中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。VC 将函数编译后会在函数名前面加上下划线前缀,在函数名后加上 "@" 和参数的字节数。
2.__cdecl 是 C 和 C++ 程序缺省的调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用 _stdcall 函数的大。函数采用从右到左的压栈方式,由调用者把参数弹出栈。VC 将函数编译后仅仅在函数名前面加上下划线前缀。 它是 MFC 缺省调用约定。 
3.__fastcall方式的函数采用寄存器传递参数(实际上,它用 ECX 和 EDX 传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈),VC 将函数编译后会在函数名前面加上"@"前缀,在函数名后加上"@"和参数的字节数。
4.thiscall 仅仅应用于 "C++" 成员函数。this 指针存放于 CX 寄存器,参数从右到左压。thiscall 不是关键词,因此不能被程序员指定。
5.naked call采用 1-4 的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。naked call不是类型修饰符,故必须和__declspec 共同使用。


关键字 __stdcall、__cdecl 和 __fastcall 可以直接加在要输出的函数前,也可以在编译环境的 Setting...\C/C++ \Code Generation 项选择。当加在输出函数前的关键字与编译环境中的选择不同时,直接加在输出函数前的关键字有效。它们对应的命令行参数分别为/Gz、/Gd 和 /Gr。缺省状态为/Gd,即_cdecl。


"C" 或者 "C++" 函数在内部(编译和链接)通过修饰名识别。修饰名是编译器在编译函数定义或者原型时生成的字符串。
a、C编译时函数名修饰约定规则: 
__stdcall 调用约定在输出函数名前加上一个下划线前缀,后面加上一个"@"符号和其参数的字节数,格式为 _functionname@number。 
__cdecl调用约定仅在输出函数名前加上一个下划线前缀,格式为 _functionname。 
__fastcall调用约定在输出函数名前加上一个"@"符号,后面也是一个"@"符号和其参数的字节数,格式为@functionname@number。
b、C++编译时函数名修饰约定规则:
__stdcall调用约定的格式为"?functionname@@YG*****@Z"或"?functionname@@YG*XZ",以"?"标识函数名的开始,后跟函数名;函数名后面以"@@YG"标识参数表的开始,后跟参数表;参数表以代号表示:X——void,D——char,E——unsigned char,……,PA——表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以"0"代替,一个"0"代表一次重复;参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前;参数表后以"@Z"标识整个名字的结束,如果该函数无参数,则以"XZ"标识结束。 
__cdecl调用约定规则同上面的__stdcall调用约定,只是参数表的开始标识由上面的"@@YG"变为"@@YA"。 
__fastcall调用约定规则同上面的__stdcall调用约定,只是参数表的开始标识由上面的"@@YG"变为"@@YI"。


三、DLL工程
VC6中可以直接使用Win32 DLL开始DLL的编写。这种方法是没有MFC支持,直接使用win32 API来完成的。
VC6中有三种形式的MFC DLL可供选择,即Regular statically linked to MFC DLL(标准静态链接MFC DLL)、Regular using the shared MFC DLL(标准动态链接MFC DLL)以及Extension MFC DLL(扩展MFC DLL)。第一种DLL在编译时把使用的MFC代码链接到DLL中,执行程序时不需要其他MFC动态链接类库的支持,但体积较大;第二种DLL在运行时动态链接到MFC类库,因而体积较小,但却依赖于MFC动态链接类库的支持;这两种DLL均可被MFC程序和Win32程序使用。第三种DLL的也是动态连接,但做为MFC类库的扩展,只能被MFC程序使用。 


输出函数的方法有以下几种: 
1、传统的方法 
在模块定义文件的 EXPORT 部分指定要输入的函数或者变量。语法格式如下: 
entryname[=internalname] [@ordinal[NONAME]] [DATA] [PRIVATE] 
其中: 
entryname 是输出的函数或者数据被引用的名称; 
internalname 同 entryname; 
@ordinal 表示在输出表中的顺序号(index); 
NONAME 仅仅在按顺序号输出时被使用(不使用 entryname );
DATA 表示输出的是数据项,使用 DLL 输出数据的程序必须声明该数据项为 _declspec(DLLimport)。
上述各项中,只有 entryname 项是必须的,其他可以省略。
对于"C"函数来说,entryname 可以等同于函数名;但是对 "C++" 函数(成员函数、非成员函数)来说,entryname 是修饰名。可以从 .map 映像文件中得到要输出函数的修饰名,或者用VC的bin目录中的DUMPBIN /SYMBOLS 得到,然后把它们写在 .def 文件的输出模块。 
如果要输出一个 "C++" 类,则把要输出的数据和成员的修饰名都写入 .def 模块定义文件。
 
2、在命令行输出 
对链接程序 LINK 指定 /EXPORT 命令行参数,输出有关函数。 


3、使用 MFC 提供的修饰符号 _declspec(DLLexport) 
在要输出的函数、类、数据的声明前加上 _declspec(DLLexport) 修饰符表示输出。__declspec(DLLexport) 在 C 调用约定、C 编译情况下可以去掉输出函数名的下划线前缀。extern "C" 使得在 C++ 中使用 C 编译方式成为可能。在"C++"下定义"C"函数需要加 extern "C" 关键词。用 extern "C" 来指明该函数使用 C 编译方式。输出的 "C" 函数可以从 "C" 代码里调用。 
例如,在一个 C++ 文件中,有如下函数: 
extern "C" {void __declspec(DLLexport) __cdecl Test(int var);} 
其输出函数名为:Test 


MFC提供了一些宏,就有这样的作用。
AFX_CLASS_IMPORT:__declspec(DLLexport) 
AFX_API_IMPORT:__declspec(DLLexport) 
AFX_DATA_IMPORT:__declspec(DLLexport) 
AFX_CLASS_EXPORT:__declspec(DLLexport) 
AFX_API_EXPORT:__declspec(DLLexport) 
AFX_DATA_EXPORT:__declspec(DLLexport) 
AFX_EXT_CLASS: #ifdef _AFXEXT 
AFX_CLASS_EXPORT 
#else 
AFX_CLASS_IMPORT 
AFX_EXT_API:#ifdef _AFXEXT 
AFX_API_EXPORT 
#else 
AFX_API_IMPORT 
AFX_EXT_DATA:#ifdef _AFXEXT 
AFX_DATA_EXPORT 
#else 
AFX_DATA_IMPORT 


四、调用DLL
可以通过下列方式调用 DLL 中的函数:
1.静态调用,也称为隐式调用,是在程序加载时加载DLL。这种方式由编译系统完成对DLL的加载和当应用程序结束时DLL卸载的编码(Windows系统负责对DLL调用次数的计数),调用方式简单,能够满足通常的要求。通常采用的调用方式是把产生动态连接库时产生的.LIB文件加入到应用程序的工程中,想使用DLL中的函数时,只须在源文件中声明一下。
LIB文件包含了每一个DLL导出函数的符号名和可选择的标识号以及DLL文件名,不含有实际的代码。
2.动态调用,即显式调用方式,是在程序运行时加载DLL。这种方式是由编程者用API函数加载和卸载DLL来达到调用DLL的目的,比较复杂,但能更加有效地使用内存,是编制大型应用程序时的重要方式。
在Windows系统中,与动态库调用有关的函数包括: 
LoadLibrary(或MFC 的AfxLoadLibrary),装载动态库。
GetProcAddress,获取要引入的函数,将符号名或标识号转换为DLL内部地址。需要函数的修饰名为参数,可以使用VC自带的工具Depends查看相应的DLL来得到。
FreeLibrary(或MFC的AfxFreeLibrary),释放动态链接库。


在链接时,Windows 首先搜索预安装的一组 DLL,例如性能库 (Kernel32.dll) 和安全库 (User32.dll)。然后,Windows 按以下顺序搜索 DLL: 
1. 当前进程的可执行程序所在的目录。 
2. 当前目录。 
3. Windows 系统目录。(GetSystemDirectory 函数获取 Windows 系统目录的路径。) 
4. Windows 目录。(GetWindowsDirectory 函数获取 Windows 目录的路径。) 
5. PATH 环境变量中列出的目录。 
原创粉丝点击