|
DLL导出函数的两种方式
DLL可以使用两种方法将公共符号导入到应用程序中或从 DLL 导出函数: 生成 DLL 时使用模块定义 (.DEF) 文件;在主应用程序的函数定义中使用 __declspec(dllimport) 或 __declspec(dllexport) 关键字。
使用 .DEF 文件:模块定义 (.DEF) 文件是包含一个或多个描述各种 DLL 属性的 Module 语句的文本文件。如果不使用 __declspec(dllimport) 或 __declspec(dllexport) 导出 DLL 函数,则 DLL 需要 .DEF 文件。
使用 __declspec:32 位版的 Visual C++ 用 __declspec(dllimport) 和 __declspec(dllexport) 取代以前在 16 位版的 Visual C++ 中使用的 __export 关键字。
_declspec(dllexport)的详解
写WIN32程序或做过DLL的人,都会很清楚__declspec(dllexport)的作用,它就是为了省掉在DEF文件中手工定义导出哪些函数的一个方法。当然,如果你的DLL里全是C++的类的话,你无法在DEF里指定导出的函数,只能用__declspec(dllexport)导出类。
MSDN文档里面对于__declspec(dllimport)的说明:
不使用__declspec(dllimport) 也能正确编译代码,但使用__declspec(dllimport)使编译器可以生成更好的代码。编译器之所以能够生成更好的代码,是因为它可以确定函数是否存在于DLL 中,这使得编译器可以生成跳过间接寻址级别的代码,而这些代码通常会出现在跨DLL边界的函数调用中。但是,必须使用__declspec(dllimport)才能导入 DLL 中使用的变量。
初看起来,这段话前面的意思是,不用它也可以正常使用DLL的导出库,但最后一句话又说,必须使用__declspec(dllimport) 才能导入 DLL 中使用的变量。
在Windows DLL编程时,可使用__declspec(dllimport)关键字导入函数或者变量。
函数的导入
当你需要使用DLL中的函数时,往往不需要显示地导入函数,编译器可自动完成。但如果你显示地导入函数,编译器会产生质量更好的代码。由于编译器确切地知道了一个函数是否在一个DLL中,它就可以产生更好的代码,不再需要间接的调用转接。
Win32的PE格式(Portable Executable Format)把所有导入地址放在一个导入地址表中。下面用一个具体实例说明使用__declspec(dllimport)导入函数和不使用的区别:
假设func是一个DLL中的函数,现在在要生成的.exe的main函数中调用func函数,并且不显示地导入func函数(即没有:__declspec(dllimport)),代码示例如下:
int main()
{
func();
}
编译器将产生类似这样的调用代码:
call func
然后,链接器把该调用翻译为类似这样的代码:
call 0x40000001 ; ox40000001是"func"的地址
并且,链接器将产生一个Thunk,形如:
0x40000001: jmp DWORD PTR __imp_func
这里的imp_func是func函数在.exe的导入地址表中的函数槽的地址。然后,加载器只需要在加载时更新.exe的导入地址表即可。
而如果使用了__declspec(dllimport)显示地导入函数,那么链接器就不会产生Thunk(如果不被要求的话),而直接产生一个间接调用。因此,下面的代码:
__declspec(dllimport) void func1(void);
void main(void)
{
func1();
}
将调用如下调用指令:
call DWORD PTR __imp_func1
因此,显示地导入函数能有效减少目标代码(因为不产生Thunk)。另外,在DLL中使用DLL外的函数也可以这样做,从而提高空间和时间效率。
变量的导入
与函数不同的是,在使用DLL中的变量时,需要显示地导入变量。使用__declspec(dllimport)关键字导入变量。若在DLL中使用.def导出变量,则应使用DATA修饰变量,而不是使用已经被遗弃的CONSTANT。因为CONSTANT可能需要使用指针间接访问变量,不确定什么时候会出问题。
|
|