[C++] VS 2022演练 - 创建和使用用动态连接库(DLL) (详细图文)

什么是动态连接库?

动态链接库(Dynamic Link Library,简称DLL)是一种特殊的文件格式,它可以被多个程序同时共享和调用。当一个程序需要使用某个功能时,它会请求操作系统加载相应的DLL文件,并将函数地址注入到程序的内存空间中。这样,程序就可以像使用本地函数一样调用DLL中的函数,而不需要知道DLL的具体实现细节。

动态链接库的主要优点是它可以减少程序的大小,因为多个程序可以共享相同的DLL文件。此外,通过动态链接库,程序可以在运行时加载和卸载所需的功能,这使得程序更加灵活和可扩展。

前置条件

安装vs 2022社区版和“使用C++的桌面开发”相关组件,具体可以阅读:

[Visual Studio] 基础教程 - Window10下如何安装VS 2022社区版

演练介绍

本文将会创建一个MathLibrary 的C++ 动态连接库和一个MathClient的 C++控制台程序去调用MathLibrary.dll,2个项目都放置在同一个解决方案的“DLLDemo”目录中,最后效果如下图所示:

创建空白解决方案

1)在菜单栏上,选择“文件”>“新建”>“项目”,打开“创建新项目”对话框 ,选择“空白解决方案”。

创建 DLL 项目

 ​​​​​将创建一个 DLL 项目,添加代码,并生成它。 首先,启动 Visual Studio IDE,并在需要时登录。

在 Visual Studio 2022 中创建 DLL 项目

1)在菜单栏上,选择“文件”>“新建”>“项目”,打开“创建新项目”对话框 。

2)在对话框顶部,将“语言”设置为“C++”,将“平台”设置为“Windows”,并将“项目类型”设置为“库”。

3)从经过筛选的项目类型列表中,选择“动态链接库(DLL)”,然后选择“下一步”。

4)在“配置新项目”页面,在“项目名称”框中输入“MathLibrary”,以指定项目的名称。 把默认“位置”修改为你希望保存的目录。

5)选择“创建”按钮创建项目。

创建解决方案后,就可以在 Visual Studio 中的“解决方案资源管理器”窗口中看到生成的项目和源文件了。

备注:这里我使用了“解决方案文件夹”来给相关Demo做归类,这也是为什么上面步骤 4)中修改了项目保存的位置目录。

目前,此 DLL 并没有起到很大的作用。 接下来,创建一个头文件来声明 DLL 导出的函数,然后将函数定义添加到 DLL,使其具备更强大的功能。

将头文件添加到 DLL

1)若要为函数创建头文件,请在菜单栏上选择“项目”>“添加新项”。

2)在“添加新项”对话框的左窗格中,选择“Visual C++”。 在中间窗格中,选择 “头文件(.h)”。 指定 MathLibrary.h 作为头文件的名称。

3)选择“添加”按钮以生成一个空白头文件,该文件显示在新的编辑器窗口中。

4)将头文件的内容替换为以下代码:

// 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();

此头文件声明一些函数以生成通用 Fibonacci 序列,给定了两个初始值。 调用 fibonacci_init(1, 1) 会生成熟悉的 Fibonacci 数字序列。

请注意文件顶部的预处理器语句。 DLL 项目的新项目模板会将 <PROJECTNAME>_EXPORTS 添加到定义预处理器宏。 在此示例中,Visual Studio 在生成 MathLibrary DLL 项目时定义 MATHLIBRARY_EXPORTS

定义 MATHLIBRARY_EXPORTS 宏时,MATHLIBRARY_API 宏会对函数声明设置 __declspec(dllexport) 修饰符。 此修饰符指示编译器和链接器从 DLL 导出函数或变量,以便其他应用程序可以使用它。 如果未定义 MATHLIBRARY_EXPORTS(例如,当客户端应用程序包含头文件时),MATHLIBRARY_API 会将 __declspec(dllimport) 修饰符应用于声明。 此修饰符可优化应用程序中函数或变量的导入。 有关详细信息,请参阅 dllexport、dllimport

向 DLL 添加实现

1)在“解决方案资源管理器”中,右键单击“源文件”节点并选择“添加”>“新建项目”。 使用上一步中添加新头文件的相同方式,创建名为 MathLibrary.cpp 的新 .cpp 文件。

2)在编辑器窗口中,选择 MathLibrary.cpp 的选项卡(如果已打开)。 如果未打开,请在“解决方案资源管理器”中,双击 MathLibrary 项目的“源文件”文件夹中的 MathLibrary.cpp,将其打开。

3)在编辑器中,将 MathLibrary.cpp 文件的内容替换为以下代码:

// MathLibrary.cpp : Defines the exported functions for the DLL.
#include "pch.h" // use stdafx.h in Visual Studio 2017 and earlier
#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_;
}

若要验证到目前为止是否一切正常,请编译动态链接库。 若要编译,请在菜单栏上选择“生成”>“生成解决方案”。 DLL 和相关编译器输出放在解决方案文件夹正下方的“Debug”文件夹中。 如果创建发布版本,该输出会放置在“Release”文件夹中。 输出应类似于:

已启动重新生成...
1>------ 已启动全部重新生成: 项目: MathLibrary, 配置: Debug x64 ------
1>pch.cpp
1>dllmain.cpp
1>MathLibrary.cpp
1>正在生成代码...
1>  正在创建库 D:\my_project\VCXXTutorials\x64\Debug\MathLibrary.lib 和对象 D:\my_project\VCXXTutorials\x64\Debug\MathLibrary.exp
1>MathLibrary.vcxproj -> D:\my_project\VCXXTutorials\x64\Debug\MathLibrary.dll
========== “全部重新生成”: 1 成功,0 失败,0已跳过 ==========
========= 重新生成 开始于 8:43 AM,并花费了 11.824 秒 ==========

从日志上看,对应的lib和dll文件保存在解决方案目录了,我们需要修改到项目目录:

修改前:

 修改后:

重新构建:

创建可使用 DLL 的客户端应用

创建 DLL 时,请考虑客户端应用如何使用它。 若要调用函数或访问由 DLL 导出的数据,客户端源代码必须在编译时具有可用的声明。 在链接时间,链接器需要信息来解析函数调用或数据访问。 而 DLL 在“导入库”中提供此信息,导入库是包含有关如何查找函数和数据的信息的文件,而不是实际代码。 而在运行时,DLL 必须可供客户端使用,位于操作系统可以找到的位置。

无论是你自己的还是来自第三方的信息,客户端应用项目都需要几条信息才能使用 DLL。 它需要查找声明 DLL 导出的标头、链接器的导入库和 DLL 本身。 一种解决方案是将所有这些文件复制到客户端项目中。 对于在客户端处于开发阶段时不太可能更改的第三方 DLL,此方法可能是使用它们的最佳方法。 但是,如果还要同时生成 DLL,最好避免重复。 如果创建了正在开发的 DLL 文件的本地副本,可能会意外更改一个副本而不是另一个中的头文件,或使用过期的库。

为避免不同步的代码,建议在客户端项目中设置包含路径,使其直接包括 DLL 项目中的 DLL 头文件。 此外,在客户端项目中设置库路径以包括 DLL 项目中的 DLL 导入库。 最后,将生成的 DLL 从 DLL 项目复制到客户端生成输出目录中。 此步骤允许客户端应用使用生成的同一 DLL 代码。

在 Visual Studio 2022 中创建客户端应用

1)在菜单栏上,选择“文件”>“新建”>“项目”,打开“创建新项目”对话框。

2)在对话框顶部,将“语言”设置为“C++”,将“平台”设置为“Windows”,并将“项目类型”设置为“控制台”。

3)从筛选的项目类型列表中,选择“控制台应用”,然后选择“下一步” 。

4)在“配置新项目”页面,在“项目名称”框中输入“MathClient”,以指定项目的名称。把默认“位置”修改为你希望保存的目录。

5)选择“创建”按钮创建客户端项目。

将为你创建一个最小的控制台应用程序项目。 主源文件的名称与你之前输入的项目名称相同。 在本例中,命名为 MathClient.cpp。 可以生成它,但它还不会使用你的 DLL。

接下来,要在源代码中调用 MathLibrary 函数,你的项目必须包括 MathLibrary.h 文件。 可以将此头文件复制到客户端应用项目中,然后将其作为现有项添加到项目中。 对于第三方库,此方法可能是一个不错的选择。 但是,如果同时处理 DLL 的代码和客户端的代码,则头文件可能会变为不同步。要避免此问题,请设置项目中的“附加包含目录”路径,使其包含指向原始标头的路径。

将 DLL 标头添加到包含路径

1)右键单击“解决方案资源管理器”中的“MathClient”节点以打开“属性页”对话框。

2)在“配置”下拉框中,选择“所有配置”(如果尚未选择)。

3)在左窗格中,选择“配置属性”>“C/C++”>“常规”。

4)在属性窗格中,选择“附加包含目录”编辑框旁的下拉控件,然后选择“编辑”。

 5)在“附加包含目录”对话框的顶部窗格中双击以启用编辑控件。 或者,选择文件夹图标以创建新条目。

6)在编辑控件中,指定指向 MathLibrary.h 头文件的位置的路径。 可选择省略号 (...) 控件浏览到正确的文件夹。

还可将客户端源文件中的相对路径输入到包含 DLL 头文件的文件夹。 如果已按照指示将客户端项目置于 DLL 的相同的解决方案中,则相对路径可能如下所示:

..\MathLibrary

如果 DLL 和客户端项目位于其他文件夹中,请调整相对路径以进行匹配。 或者,使用省略号控件浏览文件夹。

7)在“附加包含项目”对话框中输入标头文件的路径后,选择“确定”按钮。 在“属性页”对话框中,选择“确定”按钮以保存更改。

现在可以包括 MathLibrary.h 文件,并使用它在客户端应用程序中声明的函数。 使用以下代码替换 MathClient.cpp 的内容:

// MathClient.cpp : Client app for MathLibrary DLL.
// #include "pch.h" Uncomment for Visual Studio 2017 and earlier
#include <iostream>
#include "MathLibrary.h"

int main()
{
    // 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;
}

此代码可进行编译,但不能链接。 如果现在生成客户端应用,则错误列表会显示几个 LNK2019 错误。 这是因为项目缺少一些信息:尚未指定项目依赖于 MathLibrary.lib 库。 而且,你尚未告诉链接器如何查找 MathLibrary.lib 文件。

要解决此问题,可以直接将库文件复制到客户端应用项目中。 链接器将自动查找并使用它。 但是,如果库和客户端应用都处于开发过程中,则可能会导致一个副本中的更改未在另一个副本中显示。 要避免此问题,可以设置“附加依赖项”属性,告诉生成系统项目依赖于 MathLibrary.lib。 此外,还可设置项目中的“附加库目录”路径,使其在链接时包含指向原始库的路径。

将 DLL 导入库添加到项目中

1)右键单击“解决方案资源管理器”中的“MathClient”节点,然后选择“属性”以打开“属性页”对话框。

2)在“配置”下拉框中,选择“所有配置”(如果尚未选择)。 它可确保任何属性更改同时应用于调试和发布版本。

3)在左窗格中,选择“配置属性”>“链接器”>“输入”。 在属性窗格中,选择“附加依赖项”编辑框旁的下拉控件,然后选择“编辑”。

 4)在“附加依赖项”对话框中,将 MathLibrary.lib 添加到顶部编辑控件的列表中。

5)选择“确定”返回到“属性页”对话框。

6)在左窗格中,选择“配置属性”>“链接器”>“常规”。 在属性窗格中,选择“附加库目录”编辑框旁的下拉控件,然后选择“编辑”。

7)在“附加库目录”对话框的顶部窗格中双击以启用编辑控件。 在编辑控件中,指定指向 MathLibrary.lib 文件位置的路径。 默认情况下,它位于 DLL 解决方案文件夹下的“Debug”文件夹中。 如果创建发布版本,该文件会放置在“Release”文件夹中。 可以使用 $(IntDir) 宏,这样无论创建的是哪种版本,链接器都可找到 DLL。 如果已按照指示将客户端项目置于 DLL 项目相同的解决方案中,则相对路径应如下所示:

..\MathLibrary\$(IntDir)

如果 DLL 和客户端项目位于其他位置,请调整相对路径以进行匹配。

 8)在“附加库目录”对话框中输入指向库文件的路径后,选择“确定”按钮返回到“属性页”对话框。 选择“确定”以保存属性更改。

客户端应用现在可以成功编译和链接,但它仍未具备运行所需的全部条件。 当操作系统加载应用时,它会查找 MathLibrary DLL。 如果在某些系统目录、环境路径或本地应用目录中找不到 DLL,则加载会失败。 根据操作系统,你将看到如下所示的错误消息:

避免此问题的一种方法是将 DLL 复制到包含客户端可执行文件的目录中,作为生成过程的一部分。 可将“后期生成事件”添加到项目中,以此添加一条命令,将 DLL 复制到生成输出目录。 此处指定的命令仅在 DLL 丢失或发生更改时才复制它。 此命令使用宏根据生成配置在调试或发布位置之间进行复制。

在生成后事件中复制 DLL

1)右键单击“解决方案资源管理器”中的“MathClient”节点,然后选择“属性”以打开“属性页”对话框。

2)在“配置”下拉框中,选择“所有配置”(如果尚未选择)。

3)在左窗格中,选择“配置属性”>“生成事件”>“生成后事件”。

4)在属性窗格中,在“命令行”字段中选择编辑控件。 如果已按照指示将客户端项目置于 DLL 项目相同的解决方案中,则输入以下命令:

xcopy /y /d "..\MathLibrary\$(IntDir)MathLibrary.dll" "$(OutDir)"

如果 DLL 和客户端项目在其他目录中,请更改 DLL 的相对路径以进行匹配。

5)选择“确定”按钮以保存对项目属性所做的更改。

现在,客户端应用具备生成和运行所需的全部条件。 通过在菜单栏上选择“生成”>“生成解决方案”来生成应用程序。 Visual Studio 中的“输出”窗口的示例应如下所示,具体取决于你的 Visual Studio 版本。

这里同样发现,MathClient.exe保存到了解决方案目录,我们也把它修改到项目目录:

修改前:

修改后:

 重新生成:

祝贺你,你已创建一个可调用 DLL 中的函数的应用程序。 现在运行应用程序以查看它执行的操作。 在菜单栏上,选择“调试”>“开始执行(不调试)”。 此时,Visual Studio 会打开一个命令窗口,供程序在其中运行。 输出的开始一部分应如下所示:

按任意键关闭命令窗口。

现在,你已创建一个 DLL 和一个客户端应用程序,可以进行试验。 尝试在客户端应用的代码中设置断点,并在调试器中运行该应用。 看看单步执行库调用时会发生什么情况。 将其他函数添加到库中,或编写另一个使用 DLL 的客户端应用。

部署应用时,还必须部署它使用的 DLL。 若要使你生成的或从第三方加入的 DLL 可用于应用,最简单的方法就是将其放在应用所在的同一目录中。 这称为“应用本地部署”。 有关部署的更多信息,请参阅 Deployment in Visual C++

参考资料

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

  • 19
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,下面是详细步骤: 1. 创建一个 C++ 的 DLL 项目,实现一个简单的加法函数,例如: ```c++ extern "C" __declspec(dllexport) int add(int a, int b) { return a + b; } ``` 2. 在 C++ 项目的属性中,选择“生成”选项卡,将“配置类型”设置为“动态(.dll)”。 3. 创建一个 C++/CLI 的 Class Library 项目,用于封装 C++ DLL 中的函数。在此项目中,创建一个 public ref class,包含一个 public 的方法,用于调用 C++ DLL 中的加法函数。例如: ```c++ #pragma once using namespace System; namespace MyLibrary { public ref class MyMath { public: static int Add(int a, int b) { return add(a, b); } }; } ``` 4. 在 C++/CLI 项目的属性中,选择“常规”选项卡,将“目标框架”设置为“.NET Framework 4.x”或更高版本。 5. 在 C++/CLI 项目中,添加对 C++ DLL 的引用。在“解决方案资源管理器”中,右键单击“引用”,选择“添加引用”,然后选择“浏览”选项卡,找到 C++ DLL 的位置并添加它。 6. 在 C++/CLI 项目中,添加对 System.Runtime.InteropServices 的引用。在“解决方案资源管理器”中,右键单击“引用”,选择“添加引用”,然后选择“程序集”选项卡,找到 System.Runtime.InteropServices 并添加它。 7. 在 C++/CLI 项目中,在 MyMath 类的头文件中,添加以下代码: ```c++ [DllImport("MyCppDll.dll", CallingConvention = CallingConvention.Cdecl)] extern int add(int a, int b); ``` 这将告诉 C++/CLI 项目需要使用 C++ DLL 中的 add 函数。 8. 在 C++/CLI 项目中,编译并运行代码,调用 MyMath 类的 Add 方法,如下所示: ```c++ int result = MyLibrary::MyMath::Add(1, 2); Console::WriteLine(result); // 输出:3 ``` 这样就完成了通过 C++/CLI 调用 C++ DLL 的过程,并且实现了一个简单的加法操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老狼IT工作室

你的鼓励将是我创作的最大动力。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值