Windows动态库,从__declspec(dllexport)与__declspec(dllimport)说开去

Windows动态库

__declspec(dllexport)与__declspec(dllimport)

首先,给出一个相对标准的动态库写法,
示例可以在https://github.com/LeiChenIntel/wheels/tree/main/cmake_guide/sample3获取。
示例是一个非常简单的动态库,定义了一个变量calFlag,提供了加减乘除计算功能。
动态库头文件:

#ifndef CALCULATIONS_H
#define CALCULATIONS_H

#ifdef calculations_EXPORTS
#define CALCULATIONS_API __declspec(dllexport)
#else
#define CALCULATIONS_API __declspec(dllimport)
#endif

extern CALCULATIONS_API int calFlag;

CALCULATIONS_API float add(float value1, float value2);
CALCULATIONS_API float subtract(float value1, float value2);
CALCULATIONS_API float multiple(float value1, float value2);
CALCULATIONS_API float divide(float value1, float value2);

#endif // !CALCULATIONS_H

一个额外的说明:

#ifndef CALCULATIONS_H
#define CALCULATIONS_H
...
#endif // !CALCULATIONS_H

这样的写法确保在复杂的include环境(比如include层层互相嵌套)中,该头文件只被一个源文件include了一次,防止出现重复声明的问题。

动态库源文件:

#include "Calculations.h"
#include <iostream>

int calFlag = 1;

float add(float value1, float value2) {
	return value1 + value2;
}

float subtract(float value1, float value2) {
	return value1 - value2;
}

float multiple(float value1, float value2) {
	return value1 * value2;
}

float divide(float value1, float value2) {
	if (value2 == 0.0) {
		std::cout << "Illegal input. Divide a zero." << std::endl;
		return 0;
	}
	return value1 / value2;
}

在示例sample3.cpp中调用动态库,打印输出结果:

#include "Calculations.h"
#include <iostream>

int main() {
	std::cout << "CMake Sample 3:" << std::endl;
	std::cout << "Add value : " << add(1.2, 3.5) << std::endl;
	std::cout << "Subtract value: " << subtract(1.2, 3.4) << std::endl;
	std::cout << "Multiple value: " << multiple(1.2, 3.4) << std::endl;
	std::cout << "Divide value: " << divide(1.2, 3.4) << std::endl;
	std::cout << "calFlag value: " << calFlag << std::endl;
	return 0;
}

对于一开始看到代码的我来说,这段代码最让人困惑的地方莫过于CALCULATIONS_API的宏定义了,因为我看过有动态库头文件里只有__declspec(dllexport),没有__declspec(dllimport)。当然,现在看来这是一种非常糟糕的写法,不仅会给使用动态库的人带来不便,而有巨大的隐患。文件是类似这样的:

#ifndef CALCULATIONS_H
#define CALCULATIONS_H

extern __declspec(dllexport) int calFlag;

__declspec(dllexport) float add(float value1, float value2);
__declspec(dllexport) float subtract(float value1, float value2);
__declspec(dllexport) float multiple(float value1, float value2);
__declspec(dllexport) float divide(float value1, float value2);

#endif // !CALCULATIONS_H

通过查找资料,__declspec(dllexport)和__declspec(dllimport)是需要根据情况使用的。

  • 如果头文件声明是用于生成动态库本身,那这个接口要加__declspec(dllexport)前缀,告诉动态库这个接口是对外的。
  • 如果头文件声明是用于调用动态库,那这个接口要加__declspec(dllimport)前缀,说明这个源文件要导入动态库的内容。

所以上面的头文件写法对于生成动态库是没问题的,但对于使用动态库的源文件就不能使用这份头文件了,因为接口被写死为__declspec(dllexport)。一种解决方法是重新写一份一模一样但接口是__declspec(dllimport)的头文件,或者干脆直接在源文件声明:

extern __declspec(dllimport) int calFlag;
float add(float value1, float value2);
float subtract(float value1, float value2);
__declspec(dllimport) float multiple(float value1, float value2);
__declspec(dllimport) float divide(float value1, float value2);
#include <iostream>

int main() {
	std::cout << "CMake Sample 3:" << std::endl;
	std::cout << "Add value : " << add(1.2, 3.5) << std::endl;
	std::cout << "Subtract value: " << subtract(1.2, 3.4) << std::endl;
	std::cout << "Multiple value: " << multiple(1.2, 3.4) << std::endl;
	std::cout << "Divide value: " << divide(1.2, 3.4) << std::endl;
	std::cout << "calFlag value: " << calFlag << std::endl;
	return 0;
}

根据官方文档,对于函数来说__declspec(dllimport)前缀是可以省略的,比如add和subtract省略了也不会出问题(甚至可以使用__declspec(dllexport)也不会出错,这点比较奇怪)。但是,对于变量(公共数据符号)就一定要加前缀,比如calFlag不加前缀或者加了__declspec(dllexport)就会报unresolved external symbol "int calFlag"错误,如图。

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

在这里插入图片描述
现在问题变成了能不能把这些__declspec(dllexport),__declspec(dllimport)写法整合到一份头文件里,这样可以在生成动态库时调用,同时在使用动态库时也可以调用。回到最开始的头文件,这里的解决方法是使用宏定义calculations_EXPORTS(如果用CMake构建动态库,这个宏定义是自带的),在calculations项目中加上定义,使得CALCULATIONS_API被定义为__declspec(dllexport),在其他项目中则没有这个定义,此时CALCULATIONS_API为__declspec(dllimport)。

动态库calculations的属性,可以看到有calculations_EXPORTS定义,CALCULATIONS_API为__declspec(dllexport)。
在这里插入图片描述
调用动态库源文件sample3的属性,没有calculations_EXPORTS定义,CALCULATIONS_API为__declspec(dllimport)。
在这里插入图片描述

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值