Chap 19 动态链接库
一.只有在其他模块调用动态链接库中的函数时,它才发挥作用。Win32 API 中所有的函数都在dll 中,其中最重要的三个dll :
1. kernel32.dll: 所有与内存操作,线程,进程相关的操作。
2. user32.dll: 所有与执行用户界面相关的操作。
3. gdi32.dll: 所有用于画图和显示文本的操作。
二.动态链接库分为隐式调用和显式调用:
(1) 隐式调用
a. 用 DEF 的方法制作 dll ,则不需要加 dllexport 于 dllimport 来声明导出函数,而是在 def 文件中指明导出函数
LIBRARY “dll”
EXPORTS
Add
Sub
这里指明的导出函数的名字要和程序里的函数名一致,或者不一致时,可以用
Exportedname = InFuncName 来导出不同于程序中函数名的函数名。
这个方法比较简单,而且不会进行名字改编。
b. 用 dllexport 和 dllimport 的方法制作 dll
在 dll.h 文件中:
#ifdef DLL_EXPORTS
#define DLL_API extern “C” _declspec (dllexport )
#else
#define DLL_API extern “ C ” _declspec (dllimport)
#endif
DLL_API int add(int a,int b)
在制作 dll 的工程中加上预处理器 DLL_EXPORTS ,这样在制作 dll 的工程时,会把 add 函数导出,在使用 dll 的工程中由于没有定义 DLL_EXPORTS ,会把 add 函数当成是导入函数,告诉编译器这个函数是从其他的 dll 中导入的。其实不加 dllimport 也可以编译通过并成功运行,但是加了可以使运行效率更高。
(2) 显式调用
这种方式调用 dll 时,那么只有在程序需要加载 dll 的时候才会将 dll 加入内存,而不会
像第一种方法,在程序开始的时候就加载 dll 到内存中,这样,可以节省内存,提高效率。调用方法:
HINSTANCE hInst;
hInst = LoadLibrary (“dll.dll”);
typedef int (*ADDPROC)(int a,int b); // 定义函数指针,获取函数地址
ADDPROC add = (ADDPROC)GetProcAddress (hInst, “add”); // 注意 add 为函数的名称,如果不是使用 def 方式得到的 dll ,那么函数名字发生了改编,前后加了些符号,这样就不能得到 add 函数的地址了。
add(1,2);
FreeLibaray (hInst);
当一个 dll 使用 _stdcall 的调用约定时,调用它的时候也要使用 _stdcall 的调用约定,这样就可以将上述一行改为 :
typedef int (_stdcall *ADDPORC)(int a,int b);
三.程序加载 dll 时搜索 dll 的路径顺序:
1. 程序的执行目录 (exe 所在目录 )
2. 当前目录( vcproj 所在目录)
3. 系统目录: c:/windows/system32,c:windows/system,c:/windows
4. 环境变量 path 中所列出的路径
四.名字改编和调用约定:
1. C++ 和 C
C++ 和 C 语言在编译时编译器会做不同的处理:由于 C++ 有函数重载的机制,所以在 C++ 语言中 void f1(int a) 在编译完后进行名字改编为 f1_int, 而 C 语言中同样的函数在编译完后就是 f1, 即不进行名字改编,所以当 C++ 调用 C 语言生成的函数或函数库时链接器会无法正确的链接相应的函数,同样, C 调用 C++ 生成的函数库时链接器也无法解析相应的函数。为了解决这个问题, C++ 提供了限定符 extern “C” 来解决这个问题:
假设 cfile.h( 内含 f1 函数 ),cfile.c 是 C 文件,那么在 cplusplus.cpp 中引用 cfile.h 时,应该:
extern “C”
{
#include “cfile.h”
}
int main()
{
extern “C”
{
f1();
}
}
这样,编译器在编译 cplusplus.cpp 的时候,知道按照 C 的方式去处理 f1 函数,及不进行名字改编,那么就能够识别 C 语言的 f1 函数。
2. 调用约定
VC++ 中默认的调用约定是 C 调用约定 _cdecl ,而标准调用约定是 _stdcall 。在调用约定
改变时,名字改编也不一致。例如,对于C 语言的 add(int,int) 函数,如果用 _cdecl 编译时不进行名字改编,但是用 _stdcall 时,会改变成 _add@8 ,这样,别的用 _stdcall 调用约定的编译器就无法调用 add 函数, DEF 文件能够解决这个问题,用 def 文件导出的 dll ,函数名字不进行改编,也就是说,用 DEF 制作的 dll ,不管是什么调用约定,都不发生名字改编。但是,函数用什么调用约定编译的,调用者就应该使用什么调用方式来调用函数,否则即使函数名字不发生改编,也无法正确调用。也就是说,名字改编与否和调用约定一致与否,这两个条件一起决定了函数能否被其他模块正确调用。
另外, main 函数只能用 _cdecl 调用约定,这个不可更改。