刚学dllimport 和dllexport,对于这两个关键字的使用,网上真是众说纷纭,总之让人导入导出傻傻分不清楚,所以在此整理一下自己的所学所得,方便日后查看。
对于dllimport 和dllexport的使用,首先要说的就是形如下面的这段宏定义:
#ifdef _EXPORTING #define API_DECLSPEC __declspec(dllexport) #else #define API_DECLSPEC __declspec(dllimport) #endif
该宏定义用来确定函数是导入还是导出。这段宏定义的意义如下:当创建dll时,肯定是想从外部调用dll中现成的函数、变量等,因此在新建dll项目时勾选附加选项中的“导出符号”,这样在dll的.cpp和.h文件中就会自动给出导出函数、变量、类的模板,而且会自动将_EXPORTING宏定义为1。dll创建完成后,如果直接在创建dll的项目中添加调用程序来调用dll,则创建dll时自动完成的_EXPORTING宏定义仍有效,就会执行__declspec(dllexport);但很多情况下,我们创建的dll很可能是给别人用或者是给自己的其他项目用,我们只给别人提供头文件、.dll和.lib,别人不会也去预定义一个_EXPORTING,所以很明显在他们的程序调用dll中的函数时就会执行#define API_DECLSPEC __declspec(dllimport),也就是说,该宏定义使得函数、变量等在dll的提供者和使用者之间的声明方式不同,对提供者来说,该函数被声明为__declspec(dllexport),而对使用者来说,该函数被声明为__declspec(dllimport)。当然让别人的程序在调用dll时使用_declspec(dllimport) 有其好处,下面的代码示例就显示了 _declspec(dllimport)是如何影响外部应用程序调用dll的。假定 func1 是某个 DLL 中的函数,此 DLL 与调用程序是分开的。
调用程序中的代码如下:
int main(void) { func1(); }
如果不使用 __declspec(dllimport),则上面的程序在调用此函数时编译器生成类似下面的代码:
call func1链接器再翻译成下面的内容:
call 0x4000000 ; The address of 'func1'.如果 func1 存在于另一个 DLL 中,链接器将无法直接解析此函数,因为它无法得知 func1 的地址。在 16 位环境中,链接器将此代码地址添加到 .exe 文件中的某个列表中,而加载程序在运行时会用正确的地址修补该列表。在 32 位和 64 位环境中,链接器可生成一个知道其地址的 thunk。在 32 位环境中,thunk 类似如下所示:
0x40000000: jmp DWORD PTR __imp_func1其中,imp_func1 是 func1 的槽在 .exe 文件的导入地址表中的地址。这样,链接器就知道了所有的地址。加载程序只需在加载时更新 .exe 文件的导入地址表,一切就会正常进行。
__declspec(dllimport) void func1(void); int main(void) { func1(); }
则调用过程在内部会生成如下指令:
call DWORD PTR __imp_func1因此,使用 __declspec(dllimport) 更好,因为链接器不会生成不必要的 thunk和 jmp 指令。thunk 会使代码变大(在 RISC 系统上代码它可能是若干指令),并且会降低缓存性能。所以如果通过__declspec(dllimport) 通知编译器函数在 DLL 中,则编译器会为您生成间接调用,代码更小且更快。
利用http://blog.csdn.net/Repeaterbin/article/details/4269666中的例子来说明导出dll中类的静态变量的情况,使用vs2010和c++。
当使用__declspec(dllimport)时:
首先要创建一个dll,新建解决方案命名为Test,项目名为CreatDll,勾选”导出符号“附加选项,CreatDll.h中的代码为:
// 下列 ifdef 块是创建使从 DLL 导出更简单的 // 宏的标准方法。此 DLL 中的所有文件都是用命令行上定义的 CREATDLL_EXPORTS // 符号编译的。在使用此 DLL 的 // 任何其他项目上不应定义此符号。这样,源文件中包含此文件的任何其他项目都会将 // CREATDLL_API 函数视为是从 DLL 导入的,而此 DLL 则将用此宏定义的 // 符号视为是被导出的。 #ifdef CREATDLL_EXPORTS #define CREATDLL_API __declspec(dllexport) #else #define CREATDLL_API __declspec(dllimport) #endif // 此类是从 CreatDll.dll 导出的 class CREATDLL_API CCreatDll { private: static int m_nValue; public: CCreatDll(); ~CCreatDll(); int changeValue(int x) { return m_nValue+=x; } int getValue() { return m_nValue;} };
#include "stdafx.h" #include "CreatDll.h" // 这是已导出类的构造函数。 // 有关类定义的信息,请参阅 CreatDll.h CCreatDll::CCreatDll(){} int CCreatDll::m_nValue=0; CCreatDll::~CCreatDll(){}新建一个项目来调用生成的dll,将生成的CreatDll.dll,CreatDll.lib和CreatDll.h拷至该项目目录下,该项目的main.cpp的代码为:
#include <iostream> #include "CreatDll.h" using namespacestd; #pragma comment(lib, "CreatDll.lib") int main( int argc, char** argv ){ CCreatDll a,b; a.changeValue(2); b.changeValue(3); cout<<a.getValue()<<endl; system("pause"); }程序成功调用dll中的类,输出结果5。
当不使用__declspec(dllimport)时,则修改CreatDll.h,去掉__declspec(dllimport)的宏定义,其他不变,CreatDll.h中的代码变为:
// 下列 ifdef 块是创建使从 DLL 导出更简单的 // 宏的标准方法。此 DLL 中的所有文件都是用命令行上定义的 CREATDLL_EXPORTS // 符号编译的。在使用此 DLL 的 // 任何其他项目上不应定义此符号。这样,源文件中包含此文件的任何其他项目都会将 // CREATDLL_API 函数视为是从 DLL 导入的,而此 DLL 则将用此宏定义的 // 符号视为是被导出的。 #ifdef CREATDLL_EXPORTS #define CREATDLL_API __declspec(dllexport) #else //#define CREATDLL_API __declspec(dllimport) #define CREATDLL_API #endif // 此类是从 CreatDll.dll 导出的 class CREATDLL_API CCreatDll { private: static int m_nValue; public: CCreatDll(); ~CCreatDll(); int changeValue(int x) { return m_nValue+=x; } int getValue() { return m_nValue;} };重新生成dll,还是用刚才的main.cpp去调用新生成的dll,结果出现错误,显示错误信息为:
其他的一些有价值的参考资料:extern "C" __declspec(dllexport) __declspec(dllimport) 和 def
__declspec(dllimport)
总结一下__declspec(dllimport)的作用