使用C++开发和使用dll

摘要

在 Visual Studio 中创建 DLL 项目。 在同一个解决方案下创建调用 DLL 的客户端项目,使用静态调用和动态调用方法来调用创建的 DLL。

动态链接库介绍

在 Windows 开发中,DLL(Dynamic Link Library,动态链接库)和 LIB(Library,库文件)是两种常见的代码复用方式。

DLL是一个独立的可执行二进制文件(.dll 扩展名),包含可被多个程序共享的函数、类或资源。它不会被编译到最终的可执行文件(.exe)中,而是在程序运行时被加载到内存中供调用,因此能减少程序体积并实现资源共享。

LIB(库文件)是一个包含静态链接程序所需的所有已编译代码和资源的文件,它在程序编译时被直接编译到可执行文件(.exe)中。成为程序的一部分,使用LIB可以方便其他程序调用它提供的函数和资源。

创建一个DLL项目

为了方便,我会将DLL项目和客户端项目放在一个解决方案(Solution)下,这样可以同时进行开发和测试,同时也可以在客户端项目添加对DLL项目中函数的断点进行调试。首先打开Visual Studio,这里以2022社区版为例。

在菜单栏上,选择“文件”>“新建”>“项目”,打开“创建新项目”对话框 。名字可以随便起

空白解决方案

创建后是一个什么也没有的解决方案,下面右击解决方案,选择“添加”>“新建项目...”,打开添加新项目的对话框。搜索dll,选择具有“动态链接库(dll)”,名字可以保持默认。

动态链接库(dll)

自带的模板会有framework.h、pch.h等文件。这些文件是在编译的时候进行预编译的,可以加快编译速度,对于一般小规模开发来说,是不需要这些文件的,这里全删除,只留下dllmain.cpp,直接右击移除删除即可,删除后dllmain会报错,需要将头文件的#include "pch.h"修改为#include<windows.h>。然后需要告知VS不需要预编译,右击项目,选择属性,弹出属性对话框,找到C/C++的预编译头,设置为不使用预编译头。

设置不使用预编译头

再点击生成,发现可以正常生成。在dll项目目录新建两个文件夹,include和src,在头文件过滤器中添加.h文件,放在include文件夹,在源文件过滤器中添加同名的.cpp文件,放在src文件夹。这里就以MathLibrary为例。

为了方便文件管理,这里创建了include文件夹,在文件夹下面也可以再进行文件夹分类,在MathLibrary.h中写下面代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

// MathLibrary.h - Contains declarations of math functions
#pragma once

#ifdef MATHLIBRARY_EXPORTS
#define MATHLIBRARY_API __declspec(dllexport)
#else
#define MATHLIBRARY_API __declspec(dllimport)
#endif

// The Fibonacci recurrence relation describes a sequence F
// where F(n) is { n = 0, a
// { n = 1, b
// { n > 1, F(n-2) + F(n-1)
// for some initial integral values a and b.
// If the sequence is initialized F(0) = 1, F(1) = 1,
// then this relation produces the well-known Fibonacci
// sequence: 1, 1, 2, 3, 5, 8, 13, 21, 34, ...

// Initialize a Fibonacci relation sequence
// such that F(0) = a, F(1) = b.
// This function must be called before any other function.
extern "C" MATHLIBRARY_API void fibonacci_init(
const unsigned long long a, const unsigned long long b);

// Produce the next value in the sequence.
// Returns true on success and updates current value and index;
// false on overflow, leaves current value and index unchanged.
extern "C" MATHLIBRARY_API bool fibonacci_next();

// Get the current value in the sequence.
extern "C" MATHLIBRARY_API unsigned long long fibonacci_current();

// Get the position of the current value in the sequence.
extern "C" MATHLIBRARY_API unsigned fibonacci_index();

这段代码来自参考文档1,是微软官方提供的例子。此头文件声明一些函数以生成通用 Fibonacci 序列,给定了两个初始值。 调用 fibonacci_init(1, 1) 会生成熟悉的 Fibonacci 数字序列。

然后在cpp文件中进行实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// MathLibrary.cpp : Defines the exported functions for the DLL.
#include <utility>
#include <limits.h>
#include "MathLibrary.h"

// DLL internal state variables:
static unsigned long long previous_; // Previous value, if any
static unsigned long long current_; // Current sequence value
static unsigned index_; // Current seq. position

// Initialize a Fibonacci relation sequence
// such that F(0) = a, F(1) = b.
// This function must be called before any other function.
void fibonacci_init(
const unsigned long long a,
const unsigned long long b)
{
index_ = 0;
current_ = a;
previous_ = b; // see special case when initialized
}

// Produce the next value in the sequence.
// Returns true on success, false on overflow.
bool fibonacci_next()
{
// check to see if we'd overflow result or position
if ((ULLONG_MAX - previous_ < current_) ||
(UINT_MAX == index_))
{
return false;
}

// Special case when index == 0, just return b value
if (index_ > 0)
{
// otherwise, calculate next sequence value
previous_ += current_;
}
std::swap(current_, previous_);
++index_;
return true;
}

// Get the current value in the sequence.
unsigned long long fibonacci_current()
{
return current_;
}

// Get the current index position in the sequence.
unsigned fibonacci_index()
{
return index_;
}

会发现系统提示无法打开"MathLibrary.h",这是因为我们创建了一个include文件,但是还没有加入包含。在解决方案管理器中右击dll项目> 属性 > C++ > 常规 > 附加包含目录 编辑选择include所在路径,最好是使用相对路径,直接输入./include即可

点击生成,正常生成后会在解决方案文件同级的目录的平台文件下的Debug或Release文件夹中生成dll,这个就是动态链接库文件。

使用一个DLL

动态加载

在同一个解决方案下面创建一个新的控制台项目,用来测试刚刚创建的动态链接库。右击解决方案,选择添加>新建>项目 选择 控制台项目

控制台项目

右击控制台项目,设置为启动项。

在项目的cpp文件中编写如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <windows.h>

typedef int (*lpinitFun)(unsigned long long, unsigned long long); // 定义函数指针类型
typedef bool (*lpFun)();
typedef unsigned long long (*lpnowFun)();

int main()
{
std::cout << "Hello World!\n";

// 直接调用dll
HINSTANCE hDll; // DLL 句柄
lpinitFun initfun;
lpFun next;
lpnowFun current;
hDll = LoadLibrary(L"DLL1.dll"); // 动态获取dll文件的路径
if (hDll != nullptr)
{
initfun = (lpinitFun)GetProcAddress(hDll, "fibonacci_init");
next = (lpFun)GetProcAddress(hDll, "fibonacci_next");
current = (lpnowFun)GetProcAddress(hDll, "fibonacci_current");
// Initialize a Fibonacci relation sequence.
initfun(1, 1);
// Write out the sequence values until overflow.
do {
std::cout << current() << std::endl;
} while (next());
}
}

运行程序,输出如下

dll调用

这里是直接从dll中获取数据调用,但是大家同为C++代码,可以更加静态一点进行调用。

静态调用

右击控制台项目,选择“配置属性”>“C/C++”>“常规”,将dll项目的include文件夹路径添加到附加包含目录中,这里使用相对路径../Dll1/include其中Dll1是我的dll项目名称,因为他们在同一个解决方案下面,所以相对路径处理起来还是比较方便的。

在debug路径下会生成lib文件,这个文件是相当于是dll的目录,只要将这个添加到路径中,就可以实现不使用dll句柄HINSTANCE直接对函数进行操作

选择“配置属性”>“链接器”>“输入”。 在属性窗格中,选择“附加依赖项”编辑框旁的下拉控件,然后选择“编辑”。 填入"Dll1.lib",选择“确定”返回到“属性页”对话框。选择“配置属性”>“链接器”>“常规”。 在属性窗格中,选择“附加库目录”编辑框旁的下拉控件,然后选择“编辑”。填入 "../$(IntDir)" 这里使用了VS的宏路径,无论是Debug还是Release都可以生成。都是同一个路径。下面将测试端的main函数修改为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
#include <windows.h>
#include <MathLibrary.h>

typedef int (*lpinitFun)(unsigned long long, unsigned long long); // 定义函数指针类型
typedef bool (*lpFun)();
typedef unsigned long long (*lpnowFun)();

int main()
{
std::cout << "Hello World!\n";

// 直接调用dll
HINSTANCE hDll; // DLL 句柄
lpinitFun initfun;
lpFun next;
lpnowFun current;
hDll = LoadLibrary(L"DLL1.dll"); // 动态获取dll文件的路径
if (hDll != nullptr)
{
initfun = (lpinitFun)GetProcAddress(hDll, "fibonacci_init");
next = (lpFun)GetProcAddress(hDll, "fibonacci_next");
current = (lpnowFun)GetProcAddress(hDll, "fibonacci_current");
// Initialize a Fibonacci relation sequence.
initfun(1, 1);
// Write out the sequence values until overflow.
do {
std::cout << current() << std::endl;
} while (next());
}

// Initialize a Fibonacci relation sequence.
fibonacci_init(1, 1);
// Write out the sequence values until overflow.
do {
std::cout << fibonacci_index() << ": "
<< fibonacci_current() << std::endl;
} while (fibonacci_next());
// Report count of values written before overflow.
std::cout << fibonacci_index() + 1 <<
" Fibonacci sequence values fit in an " <<
"unsigned 64-bit integer." << std::endl;

}

前半部分保持不动,后半部分使用函数调用,因为是在同一个解决方案下面,所以dll不需要手动迁移,可以直接调用。

如果想给dll添加第三方库,可以参考下一篇文章

参考文档

  1. 演练:创建和使用自己的动态链接库 (C++)