演练:创建和使用自己的动态链接库 (C++)
1.演示如何使用Visual Studio IDE通过Microsoft C++ (MSVC)编写自己的动态链接库(DLL)。
2.演示如何从其他C++应用中使用DLL。
DLL(在基于UNIX的操作系统中也成为“共享库”)是最有用的Windows组件类型之一。可以将其用作共享代码和资源、缩小应用大小的一种方法。DLL甚至可使应用更易于维护和扩展。
创建一个DLL并实现一些数学函数;然后再创建一个控制台应用来使用DLL中的这些函数;了解Windows DLL中使用的一些编程技术和约定。
本演练覆盖以下任务:
在Visual Studio中创建DLL项目。
将导出的函数和变量添加到该DLL。
在Visual Studio中创建一个控制台应用项目。
在该控制台应用中使用从DLL导入的函数和变量。
运行已完成的应用。
与静态链接库一样,DLL也是按名称导出变量、函数和资源。客户端应用导入名称以使用这些变量、函数和资源。
与静态链接库不同的是,Windows在加载时或运行时将应用中的导入连接到DLL中的导出,而不是在链接时连接它们。
Windows需要不属于标准C++编译模型的额外信息才能建立这些连接。MSVC编译器实现了一些Microsoft专用C++扩展,以提供此额外信息。
本演练将创建两个Visual Studio解决方案;一个生成DLL,另一个生成客户端应用。DLL使用C调用约定。只要平台、调用约定和链接约定匹配,便可从采用其他编程语言编写的应用中进行调用。客户端应用使用隐式链接,其中Windows在加载时将应用链接到DLL。此链接允许应用调用DLL提供的函数,就像调用静态链接库中的函数一样。
本演练并不涵盖一些常见的情况。此代码不会演示其他编程语言对C++ DLL的使用。它不会演示如何创建纯资源DLL,也不会演示如何使用显式链接在运行时(而不是在加载时)加载DLL。可以使用MSVC和Visual Studio来执行所有这些操作。
尽管DLL的代码是用C++编写的,但我们还是为导出的函数使用了C样式接口。有两个主要原因:首先,许多其他语言支持导入C样式函数。不必用C++编写客户端应用。其次这样可避免一些与导出的类和成员函数相关的常见缺陷。导出类时很容易产生难以诊断的错误,因为类声明中引用的所有内容都必须具有同样被导出的实例化。此限制适用于DLL,但不适用静态库。如果类是纯旧式数据样式,则不应遇到此问题。
有关DLL的详细信息的链接,请参阅在Visual Studio中创建C/C++ DLL。有关隐式链接和显式链接的详细信息,请参阅确定要使用的链接方法。有关创建用于使用C语言链接约定的编程语言的C++ DLL的信息,请参阅导出C++函数以用于C语言可执行文件。有关如何创建用于.NET语言的DLL的信息,请参阅从Visual Basic应用程序调用DLL函数。
创建DLL项目
1.创建“动态链接库(DLL)”
2.项目名称“MathLibrary”
将头文件添加到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项目的新项目模板会将
定义MATHLIBRARY_EXPORTS宏时,MATHLIBRARY_API宏会对函数声明设置__declspec(dllexport) 修饰符。此修饰符指示编译器和链接器从DLL导出函数或变量,以便其他应用程序可以使用它。 如果未定义MATHLIBRARY_EXPORTS(例如,当客户端应用程序包含头文件时),MATHLIBRARY_API 会将__declspec(dllimport)修饰符应用于声明。此修饰符可优化应用程序中函数或变量的导入。有关详细信息,请参阅dllexport、dllimport。
向DLL添加实现
1.在“解决方案资源管理器”中,右键单击“源文件”节点并选择“添加”>“新建项目”。使用上一步中添加新头文件的相同方式,创建名为MathLibrary.cpp的新.cpp文件。
2.在编辑器窗口中,选择MathLibrary.cpp的选项卡(如果已打开)。如果未打开,请在“解决方案资源管理器”中,双击MathLibrary项目的“Source Files”文件夹中的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”文件夹中。输出应类似于:
生成开始于 16:59...
1>------ 已启动生成: 项目: MathLibrary, 配置: Debug x64 ------
1>pch.cpp
1>dllmain.cpp
1>MathLibrary.cpp
1>正在生成代码...
1> 正在创建库 D:\desktop\learn_\Visual_Studio_2022\MathLibrary\MathLibrary\x64\Debug\MathLibrary.lib 和对象 D:\desktop\learn_\Visual_Studio_2022\MathLibrary\MathLibrary\x64\Debug\MathLibrary.exp
1>MathLibrary.vcxproj -> D:\desktop\learn_\Visual_Studio_2022\MathLibrary\MathLibrary\x64\Debug\MathLibrary.dll
========== 生成: 1 成功,0 失败,0 最新,0 已跳过 ==========
========== 生成 于 16:59 完成,耗时 00.765 秒 ==========
祝贺自己,已使用Visual Studio创建了一个DLL! 接下来,将创建一个使用DLL导出的函数的客户端应用。
创建可使用DLL的客户端应用
创建DLL时,请考虑客户端应用如何使用它。若要调用函数或访问由DLL导出的数据,客户端源代码必须在编译时具有可用的声明。在链接时间,链接器需要信息来解析函数调用或数据访问。而DLL在“导入库”中提出此信息,导入库时包含由关如何查找函数和数据的信息的文件,而不是实际代码。而在运行时,DLL必须可提供客户端使用,位于操作系统可以找到的位置。
无论是你自己的还是来自第三方的信息,客户端应用项目都需要几条信息才能使用DLL。它需要查找声明DLL导出的标头、链接器的导入库和DLL本身。一种解决方案是将所有这些文件复制到客户端项目中。队医在客户端处于开发阶段时不太可能更改的第三方DLL,此方法可能是使用它们的最佳方法。但是,如果还要同时生成DLL,最好避免重复。如果创建了正在开发的DLL文件的本地副本,可能会意外更改一个副本而不是另一个中的头文件,或使用过期的库。
为避免不同步的代码,建议在客户端项目中设置包含路径,使其直接包括DLL项目中的DLL头文件。此外,在客户端项目中设置库路径以包括DLL项目中的DLL导入库。最后,将生成的DLL从DLL项目复制到客户端生成输出目录中。此步骤允许客户端应用使用生成的同一DLL代码。
在Visual Studio中创建客户端应用
1.创建“控制台应用”
2.项目名称“MathClient”
将为你创建一个最小的控制台应用程序项目。 主源文件的名称与你之前输入的项目名称相同。 在本例中,命名为 MathClient.cpp。 可以生成它,但它还不会使用你的 DLL。
接下来,要在源代码中调用MathLibrary函数,你的项目必须包括MathLibrary.h文件。可以将此头文件复制到客户端应用项目中,然后将其作为现有项添加到项目中。对于第三方库,此方法可能是一个不错的选择。但是,如果同时处理DLL的代码和客户端的代码,则头文件可能会变为不同步。要避免此问题,请设置项目中的“附加包含目录”路径,使其包含指向原始标头的路径。
将DLL标头添加到包含路径
1.右键单击“解决方案资源管理器”中的“MathClient”节点以打开“属性页”对话框。
2.在“配置”下拉框中,选择“所有配置”(如果尚未选择)。
3.在左窗格中,选择“配置属性”>“C/C++”>“常规”。
4.在属性窗格中,选择“附加包含目录”编辑框旁的下拉控件,然后选择“编辑”。
5.在“附加包含目录”对话框的顶部窗格中双击以启用编辑控件。或者,选择文件夹图标以创建新条目。
6.在编辑控件中,指定指向MathLibrary.h头文件的位置的路径。可选择省略号(...)控件浏览到正确的文件夹。
D:\desktop\learn_\Visual_Studio_2022\MathLibrary\MathLibrary\MathLibrary
现在可以包括 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项目的单独解决方案中,则相对路径应如下所示:
D:\desktop\learn_\Visual_Studio_2022\MathLibrary\MathLibrary\$(IntDir)
8.在“附加库目录”对话框中输入指向库文件的路径后,选择“确定”按钮返回到“属性页”对话框。选择“确定”以保存属性更改。
客户端应用现在可以成功编译和链接,但它仍未具备运行所需的全部条件。当操作系统加载应用时,它会查找MathLibrary DLL。如果在某些系统目录、环境路径或本地应用目录中找不到DLL,则加载会失败。根据操作系统,你将看到如下所示的错误消息:
The code execution cannot proceed because MathLibrary.dll was not found. Reinstalling the program may fix this problem.
避免此问题的一种方法是将DLL复制到包含客户端可执行文件的目录中,作为生成过程的一部分。 可将“后期生成事件”添加到项目中,以此添加一条命令,将DLL复制到生成输出目录。此处指定的命令仅在DLL丢失或发生更改时才复制它。此命令使用宏根据生成配置在调试或发布位置之间进行复制。
在生成后事件中复制DLL
1.右键单击“解决方案资源管理器”中的“MathClient”节点,然后选择“属性”以打开“属性页”对话框。
2.在“配置”下拉框中,选择“所有配置”(如果尚未选择)。
3.在左窗格中,选择“配置属性”>“生成时间”>“后期生成事件”。
4.在属性窗格中,在“命令行”字段中选择编辑控件。 如果已按照指示将客户端项目置于 DLL 项目的单独解决方案中,则输入以下命令:
xcopy /y /d "D:\desktop\learn_\Visual_Studio_2022\MathLibrary\MathLibrary\$(IntDir)MathLibrary.dll" "$(OutDir)"
现在,客户端应用具备生成和运行所需的全部条件。通过在菜单栏上选择“生成”>“生成解决方案”来生成应用程序。Visual Studio 中的“输出”窗口的示例应如下所示,具体取决于你的 Visual Studio 版本:
1>------ Build started: Project: MathClient, Configuration: Debug Win32 ------
1>MathClient.cpp
1>MathClient.vcxproj -> C:\Users\username\Source\Repos\MathClient\Debug\MathClient.exe
1>1 File(s) copied
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
祝贺自己,已创建一个可调用DLL中的函数的应用程序。现在运行应用程序以查看它执行的操作。 在菜单栏上,选择“调试”>“启动而不调试”。此时,Visual Studio会打开一个命令窗口,供程序在其中运行。输出的最后一部分应如下所示:
71: 498454011879264
72: 806515533049393
73: 1304969544928657
74: 2111485077978050
75: 3416454622906707
76: 5527939700884757
77: 8944394323791464
78: 14472334024676221
79: 23416728348467685
80: 37889062373143906
81: 61305790721611591
82: 99194853094755497
83: 160500643816367088
84: 259695496911122585
85: 420196140727489673
86: 679891637638612258
87: 1100087778366101931
88: 1779979416004714189
89: 2880067194370816120
90: 4660046610375530309
91: 7540113804746346429
92: 12200160415121876738
93 Fibonacci sequence values fit in an unsigned 64-bit integer.
D:\desktop\learn_\Visual_Studio_2022\MathClient\MathClient\x64\Debug\MathClient.exe (进程 22228)已退出,代码为 0 (0x0)。
按任意键关闭此窗口. . .
现在,已创建一个DLL和一个客户端应用程序,可以进行试验。尝试在客户端应用的代码中设置断点,并在调试器中运行该应用。看看单步执行库调用时会发生什么情况。将其他函数添加到库中,或编写另一个使用DLL的客户端应用。
部署应用时,还必须部署它使用的DLL。若要使你生成的或从第三方加入的DLL可用于应用,最简单的方法就是将其放在应用所在的同一目录中。这称为“应用本地部署”。有关部署的更多信息,请参阅Deployment in Visual C++。
原始资料地址:
演练:创建和使用自己的动态链接库 (C++)
如有侵权联系删除 仅供学习交流使用