科技报道|exe调用DLL的方式

假设被调用的DLL存在一个导出函数 , 原型如下:
voidprintN(int);1|0三种方式从DLL导入导出函数生成DLL时使用模块定义(.def)文件在主应用程序的函数定义中使用关键字__declspec(dllimport)或__declspec(dllexport)利用#pragmacomment(linker,"/export:[ExportsName]=[ManglingName]"def编写规范:参考模块定义(.Def)文件
基本规则:
LIBRARY语句说明.def?件相应的DLL;EXPORTS语句后列出要导出函数的名称 。 可以在.def?件中的导出函数名后加@n , 表示要导出函数的序号为n(在进?函数调?时 , 这个序号将发挥其作?);.def?件中的注释由每个注释?开始处的分号(指定 , 且注释不能与语句共享?? 。 2|0编写dll注意点编写dll时 , 有个重要的问题需要解决 , 那就是函数重命名——Name-Mangling 。 解决方式有两种 , 一种是直接在代码里解决采用extent”c”、_declspec(dllexport)、#pragmacomment(linker,"/export:[ExportsName]=[ManglingName]") , 另一种是采用def文件 。
2|1编写dll时 , 为什么有extern“C”原因:因为C和C++的重命名规则是不一样的 。 这种重命名称为“Name-Mangling”(名字修饰或名字改编、标识符重命名 , 有些人翻译为“名字粉碎法” , 这翻译显得有些莫名其妙)
据说 , C++标准并没有规定Name-Mangling的方案 , 所以不同编译器使用的是不同的 , 例如:BorlandC++跟MircrosoftC++就不同 , 而且可能不同版本的编译器他们的Name-Mangling规则也是不同的 。 这样的话 , 不同编译器编译出来的目标文件.obj是不通用的 , 因为同一个函数 , 使用不同的Name-Mangling在obj文件中就会有不同的名字 。 如果DLL里的函数重命名规则跟DLL的使用者采用的重命名规则不一致 , 那就会找不到这个函数 。
影响符号名的除了C++和C的区别、编译器的区别之外 , 还要考虑调用约定导致的NameMangling 。 如extern“c”__stdcall的调用方式就会在原来函数名上加上写表示参数的符号 , 而extern“c”__cdecl则不会附加额外的符号 。
dll中的函数在被调用时是以函数名或函数编号的方式被索引的 。 这就意味着采用某编译器的C++的Name-Mangling方式产生的dll文件可能不通用 。 因为它们的函数名重命名方式不同 。 为了使得dll可以通用些 , 很多时候都要使用C的Name-Mangling方式 , 即是对每一个导出函数声明为extern“C” , 而且采用_stdcall调用约定 , 接着还需要对导出函数进行重命名 , 以便导出不加修饰的函数名 。
注意到extern“C”的作用是为了解决函数符号名的问题 , 这对于动态链接库的制造者和动态链接库的使用者都需要遵守的规则 。
动态链接库的显式装入就是通过GetProcAddress函数 , 依据动态链接库句柄和函数名 , 获取函数地址 。 因为GetProcAddress仅是操作系统相关 , 可能会操作各种各样的编译器产生的dll , 它的参数里的函数名是原原本本的函数名 , 没有任何修饰 , 所以一般情况下需要确保dll里的函数名是原始的函数名 。 分两步:一 , 如果导出函数使用了extern”C”_cdecl , 那么就不需要再重命名了 , 这个时候dll里的名字就是原始名字;如果使用了extern”C”_stdcall , 这时候dll中的函数名被修饰了 , 就需要重命名 。 二、重命名的方式有两种 , 要么使用*.def文件 , 在文件外修正 , 要么使用#pragma , 在代码里给函数别名 。
2|2_declspec(dllexport)和_declspec(dllimport)的作用_declspec还有另外的用途 , 这里只讨论跟dll相关的使用 。 正如括号里的关键字一样 , 导出和导入 。 _declspec(dllexport)用在dll上 , 用于说明这是导出的函数 。 而_declspec(dllimport)用在调用dll的程序中 , 用于说明这是从dll中导入的函数 。
因为dll中必须说明函数要用于导出 , 所以_declspec(dllexport)很有必要 。 但是可以换一种方式 , 可以使用def文件来说明哪些函数用于导出 , 同时def文件里边还有函数的编号 。


推荐阅读