从static变量导出问题解析 __declspec(dllexport) 和 __declspec(dllimport)的作用

从static变量导出问题解析 __declspec(dllexport) 和 __declspec(dllimport)的作用

转自:http://www.cnblogs.com/darknightsnow/archive/2012/09/25/2701389.html

 这段时间要把tinyxml从静态库弄成动态库,要用到__declspec(dllexport)和__declspec(dllimport)来导出dll和lib文件。终于弄明白了export和import的作用,下面从使用的角度来说明一下他们的功能。

  首先要知道,头文件是C++的接口文件,不仅本工程需要使用头文件来进行编译,给其他工程提供dll的时候也要提供此dll的头文件才能让其他人通过编程的方式来使用dll。记住:头文件要给自己用还要给别人用。

  比如一个项目中的Class中含有一个静态变量,生成dll的时候只采用了__declspec(dllexport) 如下:

dll工程

A1.h:

  #define OS_API_EXPORT __declspec(dllexport)

  class OS_API_EXPORT A {static int a;}

 

A.cpp:

  #include “A.h”

  static A::a=0; //静态变量的初始化要写在cpp文件中

 

  这样做的时候编译dll工程的时候没有问题,但是如果把dll和头文件提供给别人使用的时候就会出“unsloved symbol a”的问题。

  原因是静态成员如果不import,是不能够被编译器从lib文件里找到的。

  使用dll的工程在编译时也会将dll相关的头文件列入编译对象,而不会理会dll的cpp文件中的初始化过程,因此会出现a没有定义的情况,这时

__declspec(dllimport)就派上用场了,他会告诉使用dll的工程去lib中找到这个静态变量的定义。提供给别人使用的dll头文件应当写成:

A2.h:

  #define OS_API_IMPORT __declspec(dllimport)

  class OS_API_IMPORT A {static int a;}

 

   当使用A.dll的工程链接上A2.h后,就不会出现“unsloved symbol a”的问题了。

最终为了方便程序的开发,不用分别写出dll工程的头文件和使用dll工程的头文件,头文件可以写为如下形式:

A.h

复制代码
    #define OS_API_IMPORT __declspec(dllimport)
      #define OS_API_EXPORT __declspec(dllexport)

      #ifdef BUILD_DLL
      #define OS_API OS_API_EXPORT //如果是生成dll工程,那么导出
      #else
      #define OS_API OS_API_IMPORT //如果是生成使用dll的工程,那么导入
      #endif 

      class OS_API A{static int a;}
复制代码

          同时别忘了在dll工程属性下设置预处理器定义BUILD_DLL

 

参考:

这是由于导出类的修饰错误,对于dll本身来讲,修饰应该是__declspec(dllexport),而对于调用者来讲,应该是__declspec(dllimport)。而为了正确的编译修饰,可以声明如下:
#ifdef MY_DLL_EXPORT
#define _DLL_EXPORT_ __declspec(dllexport) 
#else
#define _DLL_EXPORT_ __declspec(dllimport) 
#endif
将MY_DLL_EXPORT加到dll 工程的预编译定义中。
现在你可以使用_DLL_EXPORT_宏来修饰你任何想导出的类或函数了。

 

总结一下__declspec(dllimport)的作用

转自:http://blog.csdn.net/clever101/article/details/5421782

作者:朱金灿
来源:http://blog.csdn.net/clever101/

 

      是时候总结一下__declspec(dllimport)的作用了。可能有人会问:__declspec(dllimport)和__declspec(dllexport)是一对的,在动态链接库中__declspec(dllexport)管导出,__declspec(dllimport)管导出,就像一个国家一样,有出口也有进口,有什么难理解的呢?这是一种很自然的思路,开始我也是这样理解。

 

     但是在两年前的一个项目中,我发现不用__declspec(dllimport)似乎也可以。比如现在我新建一个使用共享MFC DLL的规则DLL工程:DllDlg。然后我新建两个文件:DllApi.h和DllApi.cpp。DllApi.h作为接口文 件,DllApi.cpp作为实现文件。

 

     接着在DllApi.h声明一个函数:

 

[cpp] view plain copy
 
  1. __declspec(dllexport) void HelloWorld();  

 

 

在DllApi.cpp写这个函数的实现:

 

 

[cpp] view plain copy
 
  1. void HelloWorld()  
  2. {  
  3.     AfxMessageBox(_T("HelloWorld"));  
  4. }  

 

 

        这样外部的应用程序或dll就能调用HelloWorld函数。这里要特别提醒的是:有些网友说要把DllApi.h中的__declspec(dllexport) void HelloWorld();改为__declspec(dllimport) void HelloWorld();才能提供给外部调用,实际上这并不需要,这个我已经测试过。从那时我就产生一个疑问:照这样,像类似下面的:

 

 

[cpp] view plain copy
 
  1. #ifdef _EXPORTING  
  2. #define API_DECLSPEC    __declspec(dllexport)  
  3. #else  
  4. #define API_DECLSPEC    __declspec(dllimport)  
  5. #endif  

 

      是不是就只剩下一种作用:让外部调用者看得更自然些,知道哪些接口是自己工程需要导入的?__declspec(dllimport)是不是一点实际作用都没有呢?这个疑问一直盘旋在我的脑海。直到最近,我在CSDN论坛上发了一个帖子:

 

__declspec(dllimport) 的作用到底在哪里呢? 

总结了各位大虾的发言,特得出如下结论: 
1. 在导入动态链接库中的全局变量方面起作用:
使用类似

 

[cpp] view plain copy
 
  1. #ifdef _EXPORTING  
  2. #define API_DECLSPEC    __declspec(dllexport)  
  3. #else  
  4. #define API_DECLSPEC    __declspec(dllimport)  
  5. #endif  

 

 

可以更好地导出dll中的全局变量,比如按照的宏,可以在dll中这样导出全局变量:

 

 

[cpp] view plain copy
 
  1. API_DECLSPEC CBtt g_Btt;  

 

 

然后在调用程序这样导入:

 

 

[cpp] view plain copy
 
  1. API_DECLSPEC CBtt g_Btt;  

 

 

当然也可以使用extern关键字,比如在dll中这样导出全局变量:

 

 

[cpp] view plain copy
 
  1. CBtt g_Btt;  

 

 

然后在调用程序这样导入:

 

 

[cpp] view plain copy
 
  1. extern CBtt g_Btt;  

 


但据说使用__declspec(dllimport)更有效。

 

2. __declspec(dllimport)的作用主要体现在导出类的静态成员方面,
比如在动态链接库中定义这样一个导出类:

 

 

[cpp] view plain copy
 
  1. class __declspec(dllexport) CBtt  
  2. {  
  3. public:  
  4.     CBtt(void);  
  5.     ~CBtt(void);  
  6. public:  
  7.     CString m_str;  
  8.     static int GetValue()  
  9.     {  
  10.         return m_nValue;  
  11.     }  
  12. private:  
  13.     static int m_nValue;  
  14. };  

 

 

照上面这样声明,外部虽然可以使用CBtt类,但不能使用CBtt类的GetValue函数,一使用就会出现无法解析的外部符号 "public: static int CBtt::m_nValue" (?m_nValue@CBtt@@2HA)。只有如下声明才能使用CBtt类的GetValue函数:

 

 

[cpp] view plain copy
 
  1. #ifdef _EXPORTING  
  2. #define API_DECLSPEC    __declspec(dllexport)  
  3. #else  
  4. #define API_DECLSPEC    __declspec(dllimport)  
  5. #endif  
  6. class API_DECLSPEC CBtt  
  7. {  
  8. public:  
  9.     CBtt(void);  
  10.     ~CBtt(void);  
  11. public:  
  12.     CString m_str;  
  13.     static int GetValue()  
  14.     {  
  15.         return m_nValue;  
  16.     }  
  17. private:  
  18.     static int m_nValue;  
  19. };  

 



3. 使用隐式使用dll时,不加__declspec(dllimport)完全可以,使用上没什么区别,只是在生成的二进制代码上稍微有点效率损失。

 

a、 不加__declspec(dllimport)时,在使用dll中的函数时,编译器并不能区别这是个普通函数,还是从其它dll里导入的函数,所以其生 成的代码如下:

call 地址1

地址1:
jmp 实际函数地址

 

b、有 __declspec(dllimport)时,编译器知道这是要从外部dll导入的函数,从而在生成的exe的输入表里留有该项,以便在运行 exe,PE载入器加载exe时对输入地址表IAT进行填写,这样生成的代码如下:

call dword ptr[输入表里哪项对应的内存地址] (注意:现在就不需要jmp stub了)。这里
有兴趣的朋友可以参看《编译原理》和 PE文件格式。

 

4.使用__declspec(dllimport)体现了语言的一种对称美,比如虽然!true就是表示false,但是我们还是需要false这个关键字,这里体现了一种对称美。

在此特别感谢CSDN的众位大侠:superdiablo、wltg2001、ccpaishi、jszj、WizardK、hurryboylqs、jingzhongrong、jameshooo、glacier3d、winnuke、starnight1981、laiyiling、yang79tao、ForestDB、zhouzhipen、lxlsymbome、Beyond_cn。

 

参考文献:

 

1. __declspec(dllimport) 到底有什么用? 

 

posted @ 2016-02-04 13:28  flylong0204  阅读(3110)  评论(0)    收藏  举报