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

clever101
博客专家认证
2010-03-22 12:02:09
加精
我一直搞不明白__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声明:

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


API_DECLSPEC void HelloWorld();



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

void HelloWorld()
{
AfxMessageBox(_T("HelloWorld"));

}


最后在DllDlg工程里添加预处理器:_EXPORTING。这等于告诉大家:定义了这个预处理器的就是把函数导出,不定义这个预处理器的就是把函数导入。

但是比较这两种做法,效果有区别吗?说实话我看不出有任何区别。那为什么我们还是热衷于定义一个导出宏,然后添加预处理器的做法吗?__declspec(dllimport)的作用到底在哪里?

附msdn 2005对__declspec(dllimport)的解释:

如果一个程序使用 DLL 定义的公共符号,就说该程序是在导入公共符号。为使用 DLL 生成的应用程序创建头文件时,在公共符号的声明上使用 __declspec(dllimport)。不论是用 .def 文件导出还是用 __declspec(dllexport) 关键字导出,__declspec(dllimport) 关键字均有效。

若要提高代码的可读性,请为 __declspec(dllimport) 定义一个宏,然后使用此宏声明每个导入的符号:

复制代码
#define DllImport __declspec( dllimport )

DllImport int j;
DllImport void func();


在函数声明上使用 __declspec(dllimport) 是可选操作,但如果使用此关键字,编译器将生成更有效的代码。但是,为使导入的可执行文件能够访问 DLL 的公共数据符号和对象,必须使用 __declspec(dllimport)。请注意,DLL 的用户仍然需要与导入库链接。

对 DLL 和客户端应用程序可以使用相同的头文件。为此,请使用特殊的预处理器符号来指示是生成 DLL 还是生成客户端应用程序。例如:

复制代码
#ifdef _EXPORTING
#define CLASS_DECLSPEC __declspec(dllexport)
#else
#define CLASS_DECLSPEC __declspec(dllimport)
#endif

class CLASS_DECLSPEC CExampleA : public CObject
{ ... class definition ... };
















...全文
17116 112 打赏 收藏 转发到动态 举报
写回复
用AI写文章
112 条回复
切换为时间正序
请发表友善的回复…
发表回复
SurgePing 2012-08-30
  • 打赏
  • 举报
回复
[Quote=引用 38 楼 的回复:]
引用 28 楼 jingzhongrong 的回复:

1、变量需要显示地导入。
2、如果不使用 __declspec(dllimport),那么编译生成的代码最终会类似:
call 0xXXXXXXXX
0xXXXXXXXX: jmp dword ptr __imp_function
而如果使用了dllimport,连接器会直接生成如下代码,省去中间的间接调用:
call dwor……
[/Quote]
UP
godspeed330 2011-10-31
  • 打赏
  • 举报
回复 1
#ifdef _EXPORTING
#define API_DECLSPEC __declspec(dllexport)
#else
#define API_DECLSPEC __declspec(dllimport)
#endif

这个宏,是为了便于开发和使用自己写的dll库的。
用同样的 API_DECLSPEC 类型声明,在dll的.cpp文件里,添加
#define _EXPORTING
则 API_DECLSPEC 为输出库

在调用dll方,无 #define _EXPORTING,则 API_DECLSPEC 为输入库。
这样,用于dll的.h文件,可以直接用于调用dll的项目中。
soartomato 2011-09-27
  • 打赏
  • 举报
回复
99 lou zheng jie
Zeng_HB 2011-09-01
  • 打赏
  • 举报
回复
mark
mql0127 2011-08-31
  • 打赏
  • 举报
回复
良好习惯
Guccang 2011-07-28
  • 打赏
  • 举报
回复
学习了。谢谢lz and 大虾米们
ITlittlebirde 2011-04-20
  • 打赏
  • 举报
回复
一点也不多余哇 MFC扩展库中 如果你想使用导出类中的静态变量 而你在调用的头文件中不使用_declspec(dllimport) 你根本无法执行链接 你可以尝试下
kakaximodo 2010-11-23
  • 打赏
  • 举报
回复
真如醍壶灌顶啊!!!高人真多
clever101 2010-11-11
  • 打赏
  • 举报
回复
[Quote=引用 101 楼 erike888 的回复:]

引用windows核心变成上面的一段话
“ 在导入符号的时候,不必使用_declspec(dllimport)关键字,而可以直接使用标准C语言的extern关键字。但是,如果编译器能够提前知道我们引用的符号是从一个DLL的.LIB文件中导入的,那么他能产生略微高效的代码。有鉴于此,建议读者再导入函数和数据符号的时候使用_declspec(dllimport)关键字,那么Microsfot已经替……
[/Quote]

多谢指教。
ggvvcc 2010-11-10
  • 打赏
  • 举报
回复
Learning...
erike888 2010-11-09
  • 打赏
  • 举报
回复 1
引用windows核心变成上面的一段话
“ 在导入符号的时候,不必使用_declspec(dllimport)关键字,而可以直接使用标准C语言的extern关键字。但是,如果编译器能够提前知道我们引用的符号是从一个DLL的.LIB文件中导入的,那么他能产生略微高效的代码。有鉴于此,建议读者再导入函数和数据符号的时候使用_declspec(dllimport)关键字,那么Microsfot已经替我们准备好了”
clever101 2010-09-21
  • 打赏
  • 举报
回复
[Quote=引用 99 楼 lj19881024 的回复:]

下面的代码示例显示如何使用 _declspec(dllimport) 将函数调用从 DLL 导入到应用程序中。假定 func1 是驻留在某个 DLL 中的函数,而此 DLL 与包含“主”函数的 .exe 文件是分开的。

不使用 __declspec(dllimport),给出此代码:

C/C++ code

int main(void)
{
func1();
}



……
[/Quote]

谢谢兄弟!受教了!
lj19881024 2010-09-21
  • 打赏
  • 举报
回复 1
下面的代码示例显示如何使用 _declspec(dllimport) 将函数调用从 DLL 导入到应用程序中。假定 func1 是驻留在某个 DLL 中的函数,而此 DLL 与包含“主”函数的 .exe 文件是分开的。

不使用 __declspec(dllimport),给出此代码:


int main(void)
{
func1();
}


编译器生成类似下面的代码:


call func1
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) 更好,因为链接器不生成不必要的 thunk。thunk 使代码变大(在 RISC 系统上代码它可能是若干指令),并且会降低缓存性能。如果通知编译器函数在 DLL 中,编译器会为您生成间接调用。

因此,现在此代码:

__declspec(dllimport) void func1(void);
int main(void)
{
func1();
}




生成此指令:

call DWORD PTR __imp_func1


没有 thunk 和 jmp 指令,所以代码更小且更快。

另一方面,对于 DLL 内部的函数调用,您希望不必使用间接调用。您已经知道函数的地址。由于间接调用前需要时间和空间来加载和存储函数的地址,因此直接调用总是更快,而且所需的空间也总是更小。当从 DLL 本身外部调用 DLL 时,您仅希望使用 __declspec(dllimport)。生成某个 DLL 时不要对该 DLL 的内部函数使用 __declspec(dllimport)。
clever101 2010-07-06
  • 打赏
  • 举报
回复
[Quote=引用 97 楼 njutzyg 的回复:]
请教下,问个与dllimport无关的话题
前面在讨论dllimport时,提到了预编译问题,并举了如下例子
#ifdef _EXPORTING
#define API_DECLSPEC __declspec(dllexport)
#else
#define API_DECLSPEC __declspec(dllimport)
#endif

我想问的是这个_EXPORTING宏为……
[/Quote]

预编译嘛,编译之前的设置。

njutzyg 2010-07-05
  • 打赏
  • 举报
回复
请教下,问个与dllimport无关的话题
前面在讨论dllimport时,提到了预编译问题,并举了如下例子
#ifdef _EXPORTING
#define API_DECLSPEC __declspec(dllexport)
#else
#define API_DECLSPEC __declspec(dllimport)
#endif

我想问的是这个_EXPORTING宏为什么要在vc的“预编译”选项里定义,而不是在头文件里定义?
谢谢
lp2007 2010-04-15
  • 打赏
  • 举报
回复
也正为此迷惑呢,学习了
celiacute 2010-03-30
  • 打赏
  • 举报
回复
學習了
clever101 2010-03-28
  • 打赏
  • 举报
回复
[Quote=引用 91 楼 wo65432519 的回复:]

__declspec(dllexport) 你可以不加试试效果啊~~特别是P/Invoke的方式里面就知道有用了
[/Quote]

大虾,这个我还真没想到。谢谢指教啊!
clever101 2010-03-28
  • 打赏
  • 举报
回复
[Quote=引用 90 楼 an_bachelor 的回复:]

主要作用是你可以导入/导出用同一个符号来表示
[/Quote]

对,在导出dll中的全局变量有这方面的作用。
oneatree 2010-03-28
  • 打赏
  • 举报
回复
mark
加载更多回复(91)

16,535

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC相关问题讨论
社区管理员
  • 基础类社区
  • AIGC Browser
  • encoderlee
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

        VC/MFC社区版块或许是CSDN最“古老”的版块了,记忆之中,与CSDN的年龄几乎差不多。随着时间的推移,MFC技术渐渐的偏离了开发主流,若干年之后的今天,当我们面对着微软的这个经典之笔,内心充满着敬意,那些曾经的记忆,可以说代表着二十年前曾经的辉煌……
        向经典致敬,或许是老一代程序员内心里面难以释怀的感受。互联网大行其道的今天,我们期待着MFC技术能够恢复其曾经的辉煌,或许这个期待会永远成为一种“梦想”,或许一切皆有可能……
        我们希望这个版块可以很好的适配Web时代,期待更好的互联网技术能够使得MFC技术框架得以重现活力,……

试试用AI创作助手写篇文章吧