基于VS2019的C++动态链接库DLL生成与调用

发布时间:2024年01月03日

一、理论知识及实践经验

实验注意事项及部分程序编写规范(部分源自ChatGPT-3.5)

?__declspec(dllexport)和__declspec(dllimport)是用于在C++中定义动态链接库(DLL)的关键字。在编写动态链接库时,__declspec(dllexport)用于指示编译器将函数或变量导出到DLL中,以便其他程序可以使用它们(如此编译生成.dll时,导出关键字宏标记的函数或变量就会被标记为可供其它程序调用的符号)。而__declspec(dllimport)则用于告诉编译器在使用DLL中的函数或变量时,需要从DLL中导入它们(温馨提示:①“__”是两个下划线;②一些平台特定细节,导出的函数需要使用extern ”C“指定编译和链接规约);

?#pragma once是C++中的预处理器指令,用于确保头文件只被编译一次。当编译器遇到#pragma once指令时,它会检查该头文件是否已经被包含,如果是,则跳过该文件的处理,否则将其包含在编译过程中。这种方法与传统的使用#ifndef和#define宏来防止头文件被多次包含的方法相比,更加简洁和直观。使用#pragma once可以减少编译时间并避免由于多次包含同一头文件而引起的重定义错误

?#pragma comment(lib, "×××.lib") 是一个特殊的预处理器指令,用于告诉编译器在链接时将特定的库文件包含到可执行文件中。这个指令告诉编译器在链接时自动包含名为"×××.lib"的库文件,以简化编译过程,特别是对于需要链接静态库的情况。但是,该种方法是特定于编译器的,不是标准的C++语法,在不同的编译器中可能会有不同的行为。

整个程序中的预处理变量包括头文件保护符必须唯一,通常的做法是基于头文件中类的名字来构建保护符的名字,以确保其唯一性。为了避免与程序中的其它实体发生名字冲突,一般把预处理变量的名字全部大写[1];

在C++中,typedef关键字用于为现有的数据类型创建一个新的名称。这样做有助于提高代码的可读性和可维护性。例如,typedef int *ptr意味着指向int数据类型的指针别名为ptr;typedef int (*ptr)(int,int)意味着指向参数为两个int类型数据和返回值为int类型的函数的函数指针别名为ptr;

.dll生成配置环境需要与其调用环境相一致(如Debug/Release版本、×64/×86);

×××_EXPORTS(×××一般为大写项目名称)通常会在创建动态链接库DLL的项目中定义。在Visual Studio中,当你创建一个新的DLL项目时,通常会自动生成一个预定义的宏,名称类似于项目名称加上“_EXPORTS”。这样就可以根据这个宏的定义来选择是导出还是导入函数和变量;

命名域的使用可以有效避免部分不同文件同名函数冲突,格式为namespace{函数}。

编程拓展

? ? ? ? 确保头文件多次包含仍能安全工作的常用技术是预处理器(preprocessor),它由C++语言从C语言继承而来。预处理器是在编译之前执行的一段程序,可以部分地改变我们所写的程序[1]。

? ? ? ?预处理器指令可以在编译前对源代码进行处理,用于控制编译过程和生成特定的代码结构,预处理器指令通常以#开头。 C++中的预处理器指令包括#include(用于包含其他文件的内容)、#define(用于定义宏/预处理变量)、#ifdef / #ifndef / #else / #endif(头文件保护符,用于条件编译)、#pragma(用于向编译器发出特定的命令)、#error(用于在预处理阶段生成一个错误消息)、#warning(用于在预处理阶段生成一个警告消息)、#undef(用于取消已定义的宏)以及#line(用于修改行号和文件名信息)。

? ? ? ? 预处理变量有两种状态:已定义和未定义。#define指令把一个名字设定为预处理变量,#ifdef(当且仅当变量已定义时为真) / #ifndef(当且仅当变量未定义时为真)两个指令分别检查某个指定的预处理变量是否已经定义。一旦检查结果为真,则执行后续操作直至遇到#endif指令为止[1]。常见组合代码行如下(注意,如果是多对#ifndef / #endif为就近配对):

????????????????????????????????????????#ifndef ×××_H? ? ? ? ? ?(×××通常为编写类类名)

????????????????????????????????????????#define ×××_H

????????????????????????????????????????....? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(此处可插入包含库文件、类的声明)

????????????????????????????????????????#endif


? ? ? ? 宏(Macro),在计算机编程中是指定如何将某个输入映射到替换输出的规则或模式。将宏应用于输入称为宏扩展。输入和输出可以是词汇标记或字符序列或语法树。软件应用程序支持字符宏,可以轻松调用常用命令序列。某些编程语言支持令牌和树宏,以实现代码重用或扩展语言,有时适用于特定领域的语言。宏用于使程序员可以将一系列计算指令作为单个程序语句使用,从而使编程任务不那么繁琐且不易出错。宏通常允许位置或关键字参数来指示条件汇编程序的内容和根据操作系统、平台或其他因素等变量生成并用于创建整个程序或程序套件[3]。

? ? ? ? 在Windows模块开发中,宏经常和预处理指令#ifdef配合来控制模块的导出和导入符号;在跨平台开发时,通过宏和预处理指令配合,可以达到一定的代码开关控制,对不同的操作系统启用不同的代码;宏定义可以防止重复包含头文件;宏可以减少重复代码;宏可以简化部分信息输出[2]。??

二、实验实践

实验操作系统:Windows 10

实验高级程序设计语言:C++

实验开发环境:Visual Studio 2019

(一)C++源程序生成.dll动态链接文件及其使用[4][5][6][7]

1、编写生成.dll动态链接库

方法一

? ? ? ? 方法一基于动态链接库(DLL)(项目模板)实现。需要注意的是,该模板自动提供预处理器定义CALCULATORAPIDLL_EXPORTS,而我们的项目名称为Calculator_API_DLL,实际上该处DLL导入导出宏需要匹配一致。

对应文件代码

// Calculator.h文件代码

// 预防重复包含头文件
#ifndef CALCULATOR_H
#define CALCULATOR_H


// Visual Studio创建DLL文件会自动添加Calculator_API_EXPORTS
// 通过宏与预处理命令控制DLL文件导入导出符号
#ifdef CALCULATORAPIDLL_EXPORTS
#define CalDLL_API __declspec(dllexport)
#else
#define CalDLL_API __declspec(dllimport)
#endif

// 包含标准库
#include<iostream>

// 添加结构体定义或函数定义
struct CalDLL_API CalNum
{
	int CalAdd(int, int);
	int CalSub(int, int);
	int CalPro(int, int);
	int CalDiv(int, int);
};

namespace CalculatorSpace
{
	void CalDLL_API printHello();
}


#endif //CALCULATOR_H




// Calculator.cpp文件代码

// 添加预编译头
#include "pch.h"

#include"Calculator.h"

using namespace std;

inline int CalNum::CalAdd(int a, int b)
{
	return a + b;
}

inline int CalNum::CalSub(int a, int b)
{
	return a - b;
}

inline int CalNum::CalPro(int a, int b)
{
	return a * b;
}

inline int CalNum::CalDiv(int a, int b)
{
	// 判断分母是否为除数
	if (b == 0)
	{
		cout << "分母不能为零!计算失败!" << endl;
		return 0;
	}
	else
		return a / b;
}

void CalculatorSpace::printHello()
{
	cout << "hello World!" << endl;
}

方法二

? ? ? ? 方法二基于空项目(项目模板)实现。需要注意的是,该模板由于不提供预处理器定义,因此,我们可以i将DLL导入导出宏命名为”项目名称_EXPORTS“。

对应文件代码

// Calculator.h文件代码

// 预防重复包含头文件
#ifndef CALCULATOR_H
#define CALCULATOR_H


// 通过宏与预处理命令控制DLL文件导入导出符号
#ifdef Calculator_API_EXPORTS
#define CalDLL_API __declspec(dllexport)
#else
#define CalDLL_API __declspec(dllimport)
#endif

// 包含标准库
#include<iostream>

// 添加结构体定义或函数定义
struct CalDLL_API CalNum
{
	int CalAdd(int, int);
	int CalSub(int, int);
	int CalPro(int, int);
	int CalDiv(int, int);
};

namespace CalculatorSpace
{
	void CalDLL_API printHello();
}


#endif //CALCULATOR_H






// Calculator.cpp文件代码

#include"Calculator.h"

using namespace std;

inline int CalNum::CalAdd(int a, int b)
{
	return a + b;
}

inline int CalNum::CalSub(int a, int b)
{
	return a - b;
}

inline int CalNum::CalPro(int a, int b)
{
	return a * b;
}

inline int CalNum::CalDiv(int a, int b)
{
	// 判断分母是否为除数
	if (b == 0)
	{
		cout << "分母不能为零!计算失败!" << endl;
		return 0;
	}
	else
		return a / b;
}

void CalculatorSpace::printHello()
{
	cout << "hello World!" << endl;
}

2、主程序调用.dll动态链接库

实验代码

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

using namespace std;

int main()
{
	int a, b;
	cout << "请输入需要进行计算的数字a与b:" << endl;
	cin >> a >> b;
	// 测试类的调用
	// 调用结构体默认构造函数
	CalNum Caculator;
	int A1 = Caculator.CalAdd(a, b);
	int A2 = Caculator.CalAdd(a, b);
	int A3 = Caculator.CalAdd(a, b);
	int A4 = Caculator.CalAdd(a, b);
	cout << "a+b=" << A1 << endl;
	cout << "a-b=" << A2 << endl;
	cout << "a*b=" << A3 << endl;
	cout << "a/b=" << A4 << endl;
	// 测试函数调用
	CalculatorSpace::printHello();
	return 0;
};

实验结果

参考资料:

[1]?C++ Primer中文版:第5版 /(美)李普曼(Lippman,S.B.),(美)拉乔伊(Lajoie,J.),(美)默(Moo,B.E.)著;王刚,杨巨峰译. —北京:电子工业出版社,2013.9.

[2]?C/C++宏的基本使用方法附例子讲解_c++宏machine-CSDN博客

[3]?https://en.wikipedia.org/wiki/Macro_(computer_science)

[4]?VS2019编译生成动态链接库dll的两种方式_vs 编译dll-CSDN博客

[5]?手把手教你如何制作和使用lib和dll_user_plc. lib怎么编写-CSDN博客

[6]?VS2019-C++创建和调用DLL动态链接库(傻瓜式教程)_创建动态链接库-CSDN博客

[7]?https://www.cnblogs.com/gongxijun/p/4368404.html

文章来源:https://blog.csdn.net/Jcb1906824038/article/details/135328913
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。