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

本文详细介绍了在Windows环境下如何使用__declspec(dllexport)和__declspec(dllimport)进行DLL的导出与导入操作,包括函数与变量的具体处理方式,以及它们如何影响编译器生成的代码质量。

__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 中使用的变量。


&lt;think&gt;好的,我现在要解决用户的问题:&ldquo;C1_DLL_IMPORT __declspec(dllimport)使用场景&rdquo;。首先,我需要理解用户的问题到底是什么。看起来用户是在问在C/C++中使用__declspec(dllimport)的场景,以及它和C1_DLL_IMPORT的关系。 首先,我应该回忆一下DLL(动态链接库)的基本知识。在Windows平台上,DLL允许代码在多个程序之间共享。当创建一个DLL时,通常会使用__declspec(dllexport)来导出函数或类,这样其他程序才能使用它们。而__declspec(dllimport)则是在使用DLL时,告诉编译器这些符号是从DLL中导入的,而不是在当前模块中定义的。 接下来,用户提到的C1_DLL_IMPORT可能是一个宏定义。通常,在编写跨平台或可移植的代码时,开发者会使用宏来简化导入导出的处理。例如,在DLL的实现项目中,可能会定义C1_DLL_EXPORT为__declspec(dllexport),而在使用该DLL的项目中,C1_DLL_IMPORT可能被定义为__declspec(dllimport)。这样做的好处是方便切换,比如在同一个源文件中,根据是否定义了DLL_EXPORTS宏,自动选择导出或导入。 现在,用户的问题是关于使用场景,所以我需要详细说明在哪些情况下需要使用__declspec(dllimport)。例如,当开发一个DLL,并希望其他程序调用其中的函数或类时,需要在DLL的头文件中使用__declspec(dllexport)来标记导出的符号。而当另一个项目引用这个DLL时,必须使用__declspec(dllimport)来声明这些符号是从DLL中导入的。否则,链接器可能无法正确解析符号,导致链接错误。 另外,需要注意的是,对于类和变量,使用__declspec(dllimport)时有一些特殊要求。比如,导入类时,必须确保类的定义在DLL内部和外部是一致的,否则可能导致类型不匹配的问题。此外,对于静态成员变量,可能需要显式地在DLL中进行定义,否则在导入时可能会遇到未解析的符号错误。 可能用户还想知道如何实际应用这个宏。比如,在DLL的项目设置中定义一个宏,比如DLL_EXPORTS,然后在头文件中使用: #ifdef DLL_EXPORTS #define C1_DLL_IMPORT __declspec(dllexport) #else #define C1_DLL_IMPORT __declspec(dllimport) #endif 这样,当编译DLL时,使用dllexport导出符号,而其他项目在包含头文件时,会使用dllimport导入。 还需要提到的是,虽然__declspec(dllimport)主要用于Windows平台,但在其他平台上可能有不同的机制,比如使用__attribute__((visibility(&quot;default&quot;)))或者动态加载库的方式。不过用户的问题集中在Windows上的使用,所以重点应放在那里。 最后,总结一下使用场景:当需要从DLL导入函数、类或变量时,使用__declspec(dllimport)来声明这些符号,确保编译器生成正确的导入代码,并且链接器能够正确解析符号。同时,结合宏定义可以更方便地管理导入导出的符号,提高代码的可维护性。&lt;/think&gt;在Windows平台开发动态链接库(DLL)时,`__declspec(dllimport)` 用于声明从DLL中导入的符号(如函数、类或变量),其典型使用场景如下: ### 使用场景说明 1. **DLL使用者** 当你的项目需要引用其他DLL提供的功能时,需在头文件中通过 `__declspec(dllimport)` 声明这些符号,告知编译器它们的定义不在当前模块中。 2. **宏定义简化** 通常与宏(如 `C1_DLL_IMPORT`)结合使用,通过条件编译区分导出/导入逻辑。例如: ```cpp #ifdef C1_DLL_EXPORTS // 定义在DLL项目中 #define C1_DLL_IMPORT __declspec(dllexport) #else // 定义在使用DLL的项目中 #define C1_DLL_IMPORT __declspec(dllimport) #endif ``` 3. **类/结构体导入** 若DLL导出类或结构体,需在头文件中使用 `__declspec(dllimport)` 修饰类定义,确保成员函数和静态变量的正确解析。 4. **避免手动维护导入库** 相比传统的 `.lib` 导入库文件,`__declspec(dllimport)` 可直接在代码中控制符号导入,减少对 `.def` 文件的依赖。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值