注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

还东国的博客

行之苟有恒,久久自芬芳

 
 
 

日志

 
 

C++小知识37--__declspec(dllexport) & __declspec(dllimport)  

2013-03-05 13:06:18|  分类: C++(VC)编程 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

C++小知识37--__declspec(dllexport) & __declspec(dllimport)
这个问题是个非常老的问题了,在以前的BLOG中也进行过说明,也在孙鑫老师的书中以及其它书籍中都看到,这个非常的变通,非常的常见,以至于都懒得深究一些问题,但问题终于还是来了,原来写一个DLL的程序,写好就用了起来,也没有什么问题,这两天又要写一个DLL程序,想偷个懒用他原来的架子,结果一下子就发现了一个莫名的问题,原来的DLL中函数出全部用的
#ifdef __SYNCHIRIS_DLL_
#else
#define __SYNCHIRIS_DLL_ extern "C"  __declspec (dllimport)
#endif
在CPP文件中也没有定义:
#define __SYNCHIRIS_DLL_ extern "C"  __declspec (dllexport)
//注意,重定义的宏一定在在头文件的上面,为什么大家请翻看原来的博文“动态库Dll的宏定义头文件双向使用方法小结 ”
http://fpcfjf.blog.163.com/blog/static/554697932009915103127555/
#include "GetConnectString.h"     
结果竟然可以用,而且在现场一直运行的好好的,这是一个问题啊,说明对这两个表达式理解的不深刻。怎么办?度娘,谷歌大神还有MSDN一起上。

首先,发现了一个问题,在原来的程序中,在DEF的文件中是定义了导出函数的,也就是说,这个会和__declspec(dllexport)一样形成导出函数,改,将这个注释,只是使用 __declspec(dllimport) ,结果程序还是运行的好好的,说明__declspec(dllimport)和__declspec(dllexport)对这种形式的DLL导入和导出,表面是造成的结果是一样的,当然你认为里面也是一样的,也可以。
但这就引出了一个问题,存在两个表达式有什么作用呢?仅仅象MFC中某些宏,就是为了更形象的表达这个函数的作用么?
你如果这样想的话,真得是错了。

其实很多种的方法包括使用DEF文件,主要目的只有一个(至于效率什么的问题,个人觉得这个尚不算主要),就是防止不同的平台使用DLL时,C++的改名问题,而且有的时候,在诸如导出类或者类成员变量时,DEF文件是起不到作用的,些时,这两个表达式就开始发挥威力了。这里稍微说一个改名的解决方法:
1、extern "C"
如果导出函数就可以直接用这个,主要是和C语言兼容。
2、使用def文件
如果程序中的调用方式不一致,比如一个使用__stdcall,__cdecl,换句话说,c类型的库,PASCALL调用,就可能会出现问题。那么这时候就可以使用DEF文件来解决。
3、导出类及其成员
这时候儿就只能用这两个表达式:__declspec(dllexport) & __declspec(dllimport),更详细的看下面的总结,这里就不再赘述。

在查资料的时候,在网上看到了一篇帖子,发现他的情况和今天的情况极其相似,只是他只用的__declspec(dllexport)(http://blog.csdn.net/clever101/article/details/5421782
于是他进行了总结:以下为转载

是时候总结一下__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声明一个函数:
__declspec(dllexport) void HelloWorld();

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

void HelloWorld()
{
    AfxMessageBox(_T("HelloWorld"));
}
 
这样外部的应用程序或dll就能调用HelloWorld函数。这里要特别提醒的是:有些网友说要把DllApi.h中的__declspec(dllexport) void HelloWorld();改为__declspec(dllimport) void HelloWorld();

才能提供给外部调用,实际上这并不需要,这个我已经测试过。从那时我就产生一个疑问:照这样,像类似下面的:

#ifdef _EXPORTING
#define API_DECLSPEC    __declspec(dllexport)
#else
#define API_DECLSPEC    __declspec(dllimport)
#endif
 

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

CSDN论坛上发了一个帖子:


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

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

#ifdef _EXPORTING
#define API_DECLSPEC    __declspec(dllexport)
#else
#define API_DECLSPEC    __declspec(dllimport)
#endif
 
可以更好地导出dll中的全局变量,比如按照的宏,可以在dll中这样导出全局变量:

API_DECLSPEC CBtt g_Btt;

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

API_DECLSPEC CBtt g_Btt;

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

CBtt g_Btt;

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

extern CBtt g_Btt;


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

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

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

才能使用CBtt类的GetValue函数: 
#ifdef _EXPORTING
#define API_DECLSPEC    __declspec(dllexport)
#else
#define API_DECLSPEC    __declspec(dllimport)
#endif
class API_DECLSPEC CBtt
{
public:
 CBtt(void);
 ~CBtt(void);
public:
 CString m_str;
 static int GetValue()
 {
  return m_nValue;
 }
private:
 static int m_nValue;
};

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。

转载结束
最后用MSDN的说明来做结尾:
MSDN对_declspec的说明

在更新的编译器版本中,可以使用 __declspec(dllexport) 关键字从 DLL 导出数据、函数、类或类成员函数。__declspec(dllexport) 会将导出指令添加到对象文件中,因此您不需要使用 .def 文件。

当试图导出 C++ 修饰函数名时,这种便利最明显。由于对名称修饰没有标准规范,因此导出函数的名称在不同的编译器版本中可能有所变化。如果使用 __declspec(dllexport),仅当解决任何命名约定更改时才必须重新编译 DLL 和依赖 .exe 文件。

许多导出指令(如序号、NONAME 和 PRIVATE)只能在 .def 文件中创建,并且必须使用 .def 文件来指定这些属性。不过,在 .def 文件的基础上另外使用 __declspec(dllexport) 不会导致生成错误。

若要导出函数,__declspec(dllexport) 关键字必须出现在调用约定关键字的左边(如果指定了关键字)。例如:

 __declspec(dllexport) void __cdecl Function1(void);若要导出类中的所有公共数据成员和成员函数,关键字必须出现在类名的左边,如下所示: 

class __declspec(dllexport) CExampleExport : public CObject
{ ... class definition ... };生成 DLL 时,通常创建一个包含正在导出的函数原型和/或类的头文件,并将 __declspec(dllexport) 添加到头文件中的声明中。若要提高代码的可读性,请为

__declspec(dllexport) 定义一个宏并对正在导出的每个符号使用该宏:

#define DllExport   __declspec( dllexport ) __declspec(dllexport) 将函数名存储在 DLL 的导出表中。如果希望优化表的大小,请参见按序号而不是按名称从 DLL 导出函数。

  将 DLL 源代码从 Win16 移植到 Win32 时,请用 __declspec(dllexport) 替换 __export 的每个实例。

作为参考,请在 Win32 Winbase.h 头文件中搜索。它包含 __declspec(dllimport) 的用法示例。

学习还是对细节要有相当的把握,知由并举。

__declspec(dllimport) 的作用:看下面的帖子
http://blog.csdn.net/Repeaterbin/article/details/4269666
http://blog.chinaunix.net/uid-25498312-id-3476823.html
http://bbs.csdn.net/topics/330169671

  评论这张
 
阅读(991)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017