您的位置:首页 > 编程语言 > C语言/C++

C++ DLL __declspec(dllexport) & __declspec(dllimport) 解析

2013-09-29 17:12 513 查看
__declspec(dllexport)

声明一个导出函数,是说这个函数要从本DLL导出。我要给别人用。一般用于dll中

省掉在DEF文件中手工定义导出哪些函数的一个方法。当然,如果你的DLL里全是C++的类的话,你无法在DEF里指定导出的函数,只能用__declspec(dllexport)导出类

__declspec(dllimport)

声明一个导入函数,是说这个函数是从别的DLL导入。我要用。一般用于使用某个dll的exe中

不使用 __declspec(dllimport) 也能正确编译代码(除了导出类中有静态变量),但使用 __declspec(dllimport) 使编译器可以生成更好的代码。编译器之所以能够生成更好的代码,是因为它可以确定函数是否存在于 DLL 中,这使得编译器可以生成跳过间接寻址级别的代码,而这些代码通常会出现在跨 DLL 边界的函数调用中。但是,必须使用 __declspec(dllimport) 才能导入 DLL 中使用的变量。

如下面所示:

#ifndef TEST_CLASS_H__
#define TEST_CLASS_H__

#include <iostream>
using namespace std;

#ifdef SERVERDLL_EXPORTS //在创建产生DLL的工程中先把 SERVERDLL_EXPORTS 预定义上
#define SERVERDLL_API __declspec(dllexport)
#else
#define SERVERDLL_API
#endif

class SERVERDLL_API TestClass
{
public:
virtual void VirtualFunction(void);

void NormalFunction(void);
public:
/*static*/ int m_data;
};

SERVERDLL_API void func(void);
#endif


注意上面的#else 只是为SERVERDLL_API 做了个空宏 ,还有此时的m_data 并非是静态数据。在你的APP中include SimpleDLLClass.h时,如果你的APP的项目不定义SIMPLEDLL_EXPORT,也就是引入的是#else 宏 则DLL_EXPORT不存在。此时APP仍可以正常运行。但是如果你把m_data
声明为静态的数据,并在cpp中做实现,如果你的APP的项目不定义SIMPLEDLL_EXPORT,则DLL_EXPORT不存在。此时APP无法LINK。原因是找不到m_nValue。(原因:静态变量m_nValue已被DLL导出,但SimpleDLLClass无法访问m_nValue)。

总结:

dllimport是为了更好的处理类中的静态成员变量(或者其他...)的,如果没有静态成员变量(或者其他...),那么这个__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可能需要使用指针间接访问变量,不确定什么时候会出问题。

//////////////

我相信写WIN32程序的人,做过DLL,都会很清楚__declspec(dllexport)的作用,它就是为了省掉在DEF文件中手工定义导出哪些 函数的一个方法。当然,如果你的DLL里全是C++的类的话,你无法在DEF里指定导出的函数,只能用__declspec(dllexport)导出 类。但是,MSDN文档里面,对于__declspec(dllimport)的说明让人感觉有点奇怪,先来看看MSDN里面是怎么说的:

不使用 __declspec(dllimport) 也能正确编译代码,但使用 __declspec(dllimport) 使编译器可以生成更好的代码。编译器之所以能够生成更好的代码,是因为它可以确定函数是否存在于 DLL 中,这使得编译器可以生成跳过间接寻址级别的代码,而这些代码通常会出现在跨 DLL 边界的函数调用中。但是,必须使用 __declspec(dllimport) 才能导入 DLL 中使用的变量。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: