DLL库的编写(导出、导入)与使用
相关说明:
(1) 编写dll时,为什么用 extern “C”:
因为C和C++的重命名规则是不一样的。这种重命名称为“Name-Mangling”。据说,C++标准并没有规定Name-Mangling的方案,所以不同编译器使用的是不同的,例如:Borland C++跟Microsoft C++就不同,而且可能不同版本的编译器他们的Name-Mangling规则也是不同的。这样的话,不同编译器编译出来的目标文件.obj 是不通用的,因为同一个函数,使用不同的Name-Mangling在obj文件中就会有不同的名字。如果DLL里的函数重命名规则跟DLL的使用者采用的重命名规则不一致,那就会找不到这个函数。
C标准规定了C语言Name-Mangling的规范。这样就使得,任何一个支持C语言的编译器,它编译出来的obj文件可以共享,链接成可执行文件。这是一种标准,如果DLL跟其使用者都采用这种约定,那么就可以解决函数重命名规则不一致导致的错误。
影响符号名的除了C++和C的区别、编译器的区别之外,还要考虑调用约定导致的Name Mangling。如extern “c” _stdcall的调用方式就会在原来函数名上加上表示参数的符号,而extern “c” _cdecl则不会附加额外的符号。
dll中的函数在被调用时是以函数名或函数编号的方式被索引的。
所以综上。若采用某编译器的C++的Name-Mangling方式产生的dll文件可能不通用。因为它们的函数名重命名方式不同。为了使得dll可以通用些,很多时候都要使用C的Name-Mangling方式,即是对每一个导出函数声明为extern “C”,而且采用_stdcall调用约定,接着还需要对导出函数进行重命名,以便导出不加修饰的函数名。
注意到extern “C”的作用是为了解决函数符号名的问题,这对于动态链接库的制造者和动态链接库的使用者都需要遵守的规则。但extern "C"只解决了C和C++语言之间调用的问题(extern “C”是告诉编译器,让它按C的方式编译),它只能用于导出全局函数这种情况而不能导出一个类的成员函数。
(2)_declspec(dllexport)和_declspec(dllimport)的作用:
_declspec还有另外的用途,这里只讨论跟dll相关的使用。正如括号里的关键字一样,导出和导入。
因为dll中必须说明函数要用于导出,所以_declspec(dllexport)很有必要。但是可以换一种方式,可以使用def文件来说明哪些函数用于导出,同时def文件里边还有函数的编号。而使用_declspec(dllimport)却不是必须的,但是建议这么做。因为如果不用_declspec(dllimport)来说明该函数是从dll导入的,那么编译器就不知道这个函数到底在哪里,生成的exe里会有一个call XX的指令,这个XX是一个常数地址,XX地址处是一个jmp dword ptr[XXXX]的指令,跳转到该函数的函数体处【可理解为先到一个函数表里找到具体的函数体地址,然后跳转到该地址执行】,显然这样就无缘无故多了一次中间的跳转。如果使用了_declspec(dllimport)来说明,那么就直接产生call dword ptr[XXX],这样就不会有多余的跳转了。
(3)_sdtcall: 这是一种函数的调用方式。默认情况下VC使用的是_cdecl的函数调用方式,如果产生的dll只会给C/C++程序使用,那么就没必要定义为_stdcall调用方式,如果要给Win32汇编使用(或者其他的_stdcall调用方式的程序),那么就应该使用_stdcall。这个可能不是很重要,因为可以自己在调用函数的时候可以设置函数调用的规则。像VC就可以设置函数的调用方式,所以可以方便的使用win32汇编产生的dll。不过_stdcall这调用约定会Name-Mangling,所以我觉得用VC默认的调用约定简便些。但是,如果既要_stdcall调用约定,又要函数名不给修饰,那可以使用*.def文件,或者在代码里#pragma的方式给函数提供别名(这种方式需要知道修饰后的函数名是什么)。
举例:
·extern “C”_declspec(dllexport) bool _stdcall cswuyg();
·extern “C”_declspec(dllimport) bool _stdcall cswuyg();
·#pragma comment(linker,"/export:cswuyg=_cswuyg@0")
(4).def文件的规则为:
①LIBRARY语句说明.def文件相应的DLL;
②EXPORTS语句后列出要导出函数的名称。可以在.def文件中的导出函数名后加@n,表示要导出函数的序号为n(在进行函数调用时,这个序号将发挥其作用);
③.def 文件中的注释由每个注释行开始处的分号 (;) 指定,且注释不能与语句共享一行。
*.def文件只负责修改函数名称,不负责调用约定。
举例def文件格式:
LIBRARY XX(dll名称这个并不是必须的,但必须确保跟生成的dll名称一样)
EXPORTS
[函数别名]=[函数名](要导出的函数) @ [函数序号]
如果把*.def文件加入到工程之后,链接的时候还需把它加进去。那么可以这样做:
手动的在link添加:
1)工程的properties→Configuration Properties→Linker→Command Line→在 “Additional options”里加上:/def:[完整文件名].def
2)工程的properties→Configuration Properties→Linker→Input→Module Definition File 里加上[完整文件名].def
附:*.exe找dll的顺序是1)进程的当前目录;2)windows目录下的系统目录是c:/windows/system32;3)Windows目录;4)PATH环境变量中列出的目录