__declspec(dllimport) 到底有什么用?

本文探讨了__declspec(dllexport)和__declspec(dllimport)在DLL中的作用,并通过实例演示了这两种属性如何影响静态成员变量的正确链接。
我相信写WIN32程序的人,做过DLL,都会很清楚__declspec(dllexport)的作用,它就是为了省掉在DEF文件中手工定义导出哪些 函数的一个方法。当然,如果你的DLL里全是C++的类的话,你无法在DEF里指定导出的函数,只能用__declspec(dllexport)导出 类。但是,MSDN文档里面,对于__declspec(dllimport)的说明让人感觉有点奇怪,先来看看MSDN里面是怎么说的:

不使用 __declspec(dllimport) 也能正确编译代码,但使用 __declspec(dllimport) 使编译器可以生成更好的代码。编译器之所以能够生成更好的代码,是因为它可以确定函数是否存在于 DLL 中,这使得编译器可以生成跳过间接寻址级别的代码,而这些代码通常会出现在跨 DLL 边界的函数调用中。但是,必须使用 __declspec(dllimport) 才能导入 DLL 中使用的变量。

初看起来,这段话前面的意思是,不用它也可以正常使用DLL的导出库,但最后一句话又说,必须使用 __declspec(dllimport) 才能导入 DLL 中使用的变量这个是什么意思??

那我就来试验一下,假定,你在DLL里只导出一个简单的类,注意,我假定你已经在项目属性中定义了 SIMPLEDLL_EXPORT
SimpleDLLClass.h

#ifdef SIMPLEDLL_EXPORT
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT
#endif

class DLL_EXPORT SimpleDLLClass
{
public:
SimpleDLLClass();
virtual ~SimpleDLLClass();

virtual getValue() { return m_nValue;};
private:
int m_nValue;
};

SimpleDLLClass.cpp

#include "SimpleDLLClass.h"

SimpleDLLClass::SimpleDLLClass()
{
m_nValue=0;
}

SimpleDLLClass::~SimpleDLLClass()
{
}

然后你再使用这个DLL类,在你的APP中include SimpleDLLClass.h时,你的APP的项目不用定义 SIMPLEDLL_EXPORT 所以,DLL_EXPORT 就不会存在了,这个时候,你在APP中,不会遇到问题。这正好对应MSDN上说的__declspec(dllimport)定义与否都可以正常使用。但 我们也没有遇到变量不能正常使用呀。 那好,我们改一下SimpleDLLClass,把它的m_nValue改成static,然后在cpp文件中加一行

int SimpleDLLClass::m_nValue=0;

如果你不知道为什么要加这一行,那就回去看看C++的基础。 改完之后,再去LINK一下,你的APP,看结果如何, 结果是LINK告诉你找不到这个m_nValue。明明已经定义了,为什么又没有了?? 肯定是因为我把m_nValue定义为static的原因。但如果我一定要使用Singleton的Design Pattern的话,那这个类肯定是要有一个静态成员,每次LINK都没有,那不是完了? 如果你有Platform SDK,用里面的Depend程序看一下,DLL中又的确是有这个m_nValue导出的呀。
再回去看看我引用MSDN的那段话的最后一句。 那我们再改一下SimpleDLLClass.h,把那段改成下面的样子:

#ifdef SIMPLEDLL_EXPORT
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT __declspec(dllimport)
#endif
再LINK,一切正常。原来dllimport是为了更好的处理类中的静态成员变量的,如果没有静态成员变量,那么这个__declspec(dllimport)无所谓。  
<think>我们正在讨论C++中的`__declspec(dllexport)`和`__declspec(dllimport)`,这两个是Microsoft Visual C++特有的关键字,用于动态链接库(DLL)的创建和使用。 根据引用[1]和[2]: - `__declspec(dllexport)`:用于标记函数、类或数据,表示它们将被导出到DLL中,以便其他程序可以使用。 - `__declspec(dllimport)`:用于声明函数、类或数据是从DLL中导入的,而不是在当前模块中定义。 区别: 1. 使用场景不同: - 当我们在构建DLL时,使用`__declspec(dllexport)`来指定哪些符号(函数、类、变量)将被导出。 - 当我们在使用DLL的应用程序(或其他DLL)中,我们需要使用`__declspec(dllimport)`来声明这些符号是从DLL导入的。 2. 编译器行为不同: - 导出(dllexport)告诉编译器生成导出符号表,并将符号放入DLL的导出表中。 - 导入(dllimport)告诉编译器该符号不在当前编译单元中,而是来自DLL,因此生成相应的导入代码(通过导入表来解析地址)。 3. 效率考虑: - 使用`__declspec(dllimport)`导入函数比不使用更高效,因为编译器知道该函数来自DLL,可以生成更高效的调用代码(避免一次间接跳转)。如果不使用,调用可能通过一个跳转表(thunk)进行,会稍微慢一些。 常见的做法是使用一个宏来切换,如引用[3]所示: 在DLL项目内部,定义宏(通常是在编译器命令行中定义`BUILD_DLL`),这样头文件中的宏就会展开为`__declspec(dllexport)`。 而在使用DLL的项目中,不定义`BUILD_DLL`,则同一头文件中的宏展开为`__declspec(dllimport)`。 示例代码(头文件): ```cpp // MyLibrary.h #ifdef MYLIB_EXPORTS #define MYLIB_API __declspec(dllexport) #else #define MYLIB_API __declspec(dllimport) #endif // 导出/导入一个函数 MYLIB_API int myFunction(int a, int b); // 导出/导入一个类 class MYLIB_API MyClass { // ... }; ``` 在DLL的源代码中,我们需要定义`MYLIB_EXPORTS`(通常在项目设置中预定义),这样编译DLL时,`MYLIB_API`就是`__declspec(dllexport)`。而在客户端代码中包含这个头文件时,由于没有定义`MYLIB_EXPORTS`,`MYLIB_API`就变成`__declspec(dllimport)`。 注意: - 对于数据导出,同样适用,例如:`MYLIB_API int global_data;` - 对于C++类,导出整个类(使用`__declspec(dllexport)`)将会导出该类的所有成员函数和静态数据成员。但是,这可能会导致ABI问题,因为不同的编译器可能有不同的名称修饰规则。因此,通常建议使用C接口或使用虚接口(工厂模式)来避免这些问题。 为什么需要`__declspec(dllimport)`? - 对于函数:编译器可以生成更高效的调用代码。如果不使用,编译器会生成一个跳转到函数地址的代码(通过IAT),而使用`__declspec(dllimport)`,编译器会直接生成一个调用IAT中条目的代码,减少了一次跳转。 - 对于数据:访问导出的全局变量时,如果不使用`__declspec(dllimport)`,则无法正确链接,因为编译器不知道这个变量来自DLL。 总结: - 导出(dllexport)用于创建DLL时标记要导出的符号。 - 导入(dllimport)用于使用DLL时声明导入的符号。 - 通过宏切换可以使得同一头文件在DLL项目和客户端项目中使用。 相关问题: 1. 在Linux/macOS中如何实现类似的功能? 2. 如何避免导出C++类时的ABI兼容性问题? 3. 如果不使用`__declspec(dllimport)`,调用DLL函数会有什么后果? 4. 动态库中的全局变量导出和导入需要注意什么? </think>### C++中`__declspec(dllexport)`与`__declspec(dllimport)`详解 #### 一、核心区别 | 特性 | `__declspec(dllexport)` | `__declspec(dllimport)` | |-----------------------|------------------------------------------|------------------------------------------| | **作用阶段** | DLL编译时(生成阶段) | 可执行文件编译时(使用阶段) | | **功能定位** | **标记导出符号** | **声明导入符号** | | **内存操作** | 将符号写入DLL导出表 | 从DLL导入表读取符号 | | **编译器行为** | 生成`.lib`导入库和导出地址表 | 生成外部符号引用和跳转指令 | | **典型位置** | DLL项目头文件/实现文件 | 使用DLL的客户端代码 | 数学表示为: $$ \begin{cases} \text{导出:} & \text{DLL} \xrightarrow{\text{dllexport}} \text{导出表} \\ \text{导入:} & \text{EXE} \xleftarrow{\text{dllimport}} \text{导入表} \end{cases} $$ #### 二、工作原理图解 ```mermaid graph LR A[DLL项目] -->|__declspec(dllexport)| B[导出符号表] C[客户端程序] -->|__declspec(dllimport)| B B --> D[操作系统加载器] ``` #### 三、使用场景对比 1. **`__declspec(dllexport)` 场景** 编译动态库时标记需公开的接口: ```cpp // MathLib.h (DLL项目中) #ifdef BUILD_DLL #define MATH_API __declspec(dllexport) #else #define MATH_API __declspec(dllimport) #endif MATH_API int Add(int a, int b); // 函数导出 MATH_API extern double PI; // 变量导出 ``` 2. **`__declspec(dllimport)` 场景** 客户端程序调用DLL接口: ```cpp // App.cpp (在使用DLL的项目中) #include "MathLib.h" // 自动展开为dllimport int main() { double circle = 2 * PI * 5; // 使用导入变量 int sum = Add(3, 4); // 调用导入函数 return 0; } ``` #### 四、性能影响分析 使用`__declspec(dllimport)`可优化调用效率: - **直接调用**(使用dllimport): $T_{\text{call}} \approx 10\text{ns}$ 生成指令:`CALL [__imp__Add]` - **间接调用**(不使用dllimport): $T_{\text{call}} \approx 50\text{ns}$ 生成指令: ```asm MOV EAX, [Add] ; 额外寻址 CALL EAX ; 间接调用 ``` #### 五、最佳实践 1. **头文件设计技巧** ```cpp #pragma once #if defined(MATHLIB_STATIC) // 静态库情况 #define MATH_API #elif defined(MATHLIB_EXPORTS) // DLL编译 #define MATH_API __declspec(dllexport) #else // 客户端使用 #define MATH_API __declspec(dllimport) #endif ``` 2. **类导出特殊处理** ```cpp class MATH_API Vector3D { // 导出整个类 public: float Dot(const Vector3D& v); static MATH_API int dimension; // 静态成员需单独导出 }; ``` 3. **C++名称修饰问题** ```cpp extern "C" { // 禁用C++名称修饰 MATH_API void* CreateObject(); MATH_API void DestroyObject(void* obj); } ``` #### 六、常见错误 1. **缺失dllimport导致链接错误** ```log error LNK2001: unresolved external symbol _Add ``` 2. **导出/导入修饰符不一致** - 解决方案:使用统一的宏定义[^3] 3. **忘记定义预处理器宏** - DLL项目需定义:`/D MATHLIB_EXPORTS` - 客户端项目不定义该宏 #### 七、跨平台兼容方案 ```cpp #if defined(_WIN32) #ifdef MATHLIB_EXPORTS #define MATH_API __declspec(dllexport) #else #define MATH_API __declspec(dllimport) #endif #else // Linux/macOS #define MATH_API __attribute__((visibility("default"))) #endif ``` > **符号可见性对比**: > Windows导出表条目数:$N_{\text{export}} \leq 2^{16}$ > Linux ELF符号表:$N_{\text{symbol}} \leq 2^{32}$[^2] --- ### 相关问题: 1. 如何在CMake项目中自动配置dllexport/dllimport宏? 2. Linux的`__attribute__((visibility))`与Windows的`__declspec`有何异同? 3. 动态库中导出C++类会遇到哪些ABI兼容性问题? 4. 如何通过Dependency Walker工具分析DLL的导出表? [^1]: __declspec(dllimport) 是Microsoft Visual C++特有的修饰符 [^2]: __declspec(dllexport)与__declspec(dllimport) [^3]: 条件编译实现跨项目宏切换
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值