1.引言
本文介绍如何使用Visual Studio 2022将C语言函数封装成x64和x86平台上使用的动态链接库(dll文件)并生成对应的静态链接库(lib文件),以及如何在C++程序中调用生成的dll。
程序下载:
2.示例C语言程序
假设需要开发一个动态链接库,实现复数的四则运算,定义如下头文件ComplexCal.h和源码文件ComplexCal.c。
(1)ComplexCal.h
#ifndef COMPLEXCAL_H
#define COMPLEXCAL_H
//定义复数结构体
typedef struct {
double real;
double imag;
} Complex;
//复数加法
Complex add(Complex a, Complex b);
//复数减法
Complex subtract(Complex a, Complex b);
//复数乘法
Complex multiply(Complex a, Complex b);
//复数除法
Complex divide(Complex a, Complex b);
#endif //COMPLEXCAL_H
(2) ComplexCal.c
#include "CompplexCal.h"
/**
* 复数加法
* @param a Complex结构体
* @param b Complex结构体
* @return Complex结构体
*/
Complex add(Complex a, Complex b) {
Complex result;
result.real = a.real + b.real;
result.imag = a.imag + b.imag;
return result;
}
/**
* 复数减法
* @param a Complex结构体
* @param b Complex结构体
* @return Complex结构体
*/
Complex subtract(Complex a, Complex b) {
Complex result;
result.real = a.real - b.real;
result.imag = a.imag - b.imag;
return result;
}
/**
* 复数乘法
* @param a Complex结构体
* @param b Complex结构体
* @return Complex结构体
*/
Complex multiply(Complex a, Complex b) {
Complex result;
result.real = (a.real * b.real) - (a.imag * b.imag);
result.imag = (a.real * b.imag) + (a.imag * b.real);
return result;
}
/**
* 复数除法
* @param a Complex结构体
* @param b Complex结构体
* @return Complex结构体
*/
Complex divide(Complex a, Complex b) {
Complex result;
double denominator = b.real * b.real + b.imag * b.imag;
result.real = (a.real * b.real + a.imag * b.imag) / denominator;
result.imag = (a.imag * b.real - a.real * b.imag) / denominator;
return result;
}
3.创建Visual Studio 动态链接库工程并定义宏指令
3.1 创建dll工程
接下来说明如何创建Visual Studio动态链接库工程并编写相应的dll封装头文件。新建complex_cal文件夹,然后打开Visual Studio,在该目录下创建dll工程,如动图所示,然后将将示例头文件和源码文件复制到complex_cal/ComplexCal文件夹(Visual Studio解决方案文件ComplexCal.sln文件所在文件夹)内。
Visual Studio会预先创建 framework.h、pch.h、pch.c和dllmain.cpp等4个模板文件,其中pch.h为预编译标头文件,这里用不上这些模板文件,可以将其移除或者删除。
3.2 添加示例头文件和源码文件到解决方案内
首先需要添加ComplexCal.h(添加到头文件下)和ComplexCal.c(添加到源文件下)文件到Visual Studio解决方案资源管理器内,才能让Visual Studio加载。
3.3 取消使用预编译标头
另外还需取消使用预编译标头,否则将出现C1010错误:
取消方法在项目属性/C/C++/预编译头选项下选择不使用预编译头,如下所示:
注意在头部配置和平台上选中所有配置和所有平台。
3.4 在头文件中添加dll宏指令
ComplexCal.h中添加以下宏,导出函数给外部调用:
#ifndef COMPLEXCAL_H
#define COMPLEXCAL_H
//定义复数结构体
typedef struct {
double real;
double imag;
} Complex;
//dll入口宏指令
#ifndef COMPLEXCAL_H_API_EXPORTS
#define COMPLEXCAL_H_API_EXPORTS __declspec(dllexport)
#endif // !COMPLEXCAL_H_API_EXPORTS
//根据不同平台定义函数调用堆栈修饰宏
#ifdef _WIN64 || _M_X64
//x64平台
#define dll_std_call __stdcall
#elif _WIN32 || _M_IX86
// x86平台
#define dll_std_call __cdecl
#endif
// 一般用于将C++代码以标准C形式输出(即以C的形式被调用)
// 告诉编译器下面大括号括起来的函数是c语言函数(因为c++和c语言对函数的编译转换不一样,主要是c++中存在重载)
#ifdef __cplusplus
extern"C" {
#endif
//复数加法
__declspec(dllexport) Complex dll_std_call add(Complex a, Complex b);
//复数减法
__declspec(dllexport) Complex dll_std_call subtract(Complex a, Complex b);
//复数乘法
__declspec(dllexport) Complex dll_std_call multiply(Complex a, Complex b);
//复数除法
__declspec(dllexport) Complex dll_std_call divide(Complex a, Complex b);
#ifdef __cplusplus
}
#endif
#endif //COMPLEXCAL_H
3.5 选择目标平台并生成解决方案来生成dll
在Visual Studio工具栏上选择dll的构建配置和目标平台,例如Debug和x64。在Visual Studio中,Debug和Release是两种不同的构建配置。它们之间的主要区别在于编译器如何优化代码并生成可执行文件。
在Debug模式下,编译器会生成包含调试符号的二进制文件。这些符号可以用于在代码中设置断点、跟踪变量值等操作,以便进行调试。此外,编译器通常会关闭优化,以便使得调试更容易。
在Release模式下,编译器通常会启用各种优化,以生成更快、更紧凑的代码。这些优化可能会包括删除未使用的代码、内联函数、循环展开和其他技术。此外,由于没有调试符号,生成的文件大小也比Debug模式下的要小得多。
然后运行工具栏生成/生成解决方案来生成dll。
对于x64平台,生成的dll文件位于解决方案(.sln文件)同目录的x64文件夹下,对于Debug构建配置,位于x64/Debug,Release构建配置位于x64/Release。对于x86平台的Debug构建配置,则位于解决方案文件同目录的Debug文件夹,x86平台的Release构建配置则位于解决方案文件同目录的Release文件夹。
打开上述目录,其中的ComplexCal.dll和ComplexCal.lib即我们需要的dll文件,其它文件为编译和链接所需的中间文件。
4.在C++程序中引用动态库
4.1 演示工程配置
创建C++控制台应用,测试生成的复数运算dll。首先使用Visual Studio创建一个控制台应用工程,命名为ComplexCalApplication:
将上述生成的dll和lib文件以及ComplexCal.h头文件复制到解决方案文件ComplexCalApplication.sln所在目录的include文件夹下,这里以X64_Release为例。
然后添加ComplexCal.h头文件到解决方案资源管理器的头文件下,让Visual Studio能发现该文件。还需在项目属性->C/C++->常规选项下添加include文件夹为附加包含夹,以让链接器能发现并链接dll和lib文件:
另外定义生成后事件,让Visual Studio自动复制dll文件到可执行文件所在目录,避免出现dll丢失错误。生成后事件指令如下:
xcopy /y "$(ProjectDir)\include\*.dll" "$(TargetDir)"
上述命令表示在生成结束后自动将 include文件夹下的dll文件复制到可执行文件所在目录内。在项目属性中定义生成后事件的方法如下:
4.2 dll调用
在C++程序中调用dll以下几个步骤:
- 将 DLL 的头文件包含到应用程序中。
- 定义生成后事件将dll文件复制到可执行文件所在文件夹下。
如代码所示:
#include <iostream>
//引入dll头文件
#include "include/ComplexCal.h"
int main()
{
Complex a, b, sum, difference;
std::cout << "Enter the real and imaginary parts of the first complex number: ";
std::cin >> a.real >> a.imag;
std::cout << "Enter the real and imaginary parts of the second complex number: ";
std::cin >> b.real >> b.imag;
sum = add(a, b);
difference = subtract(a, b);
std::cout << "Sum: " << sum.real << " + " << sum.imag << "i" << std::endl;
std::cout << "Difference: " << difference.real << " + " << difference.imag << "i" << std::endl;
return 0;
}