使用C++创建并调用动态链接库(dll)

使用C++创建并调用动态链接库(dll)

cpp c++
cpp

与静态链接库一样,DLL 是按名称导出变量、函数和资源 。 客户端应用导入名称以使用这些变量、函数和资源 。 与静态链接库不同的是,Windows 在加载时或在运行时将应用中的导入连接到 DLL 中的导出,而不是在链接时连接它们。 Windows 需要不属于标准 C++ 编译模型的额外信息才能建立这些连接。 MSVC 编译器实现了一些 Microsoft 专用 C++ 扩展,以提供此额外信息

本文创建两个 Visual Studio 解决方案;一个生成 包含计算fibonicci序列的DLL,另一个生成客户端应用。 DLL 使用 C 调用约定。 只要平台、调用约定和链接约定匹配,便可从采用其他编程语言编写的应用中进行调用。 客户端应用使用隐式链接 ,其中 Windows 在加载时将应用链接到 DLL。 此链接允许应用调用 DLL 提供的函数,就像调用静态链接库中的函数一样。

创建 DLL 项目

在本系列的任务中,将创建一个 DLL 项目,添加代码,并生成它。 首先,启动 Visual Studio IDE,并在需要时登录。 根据使用的 Visual Studio 版本,操作说明会略有不同。 请确保在本页左上角的控件中选择了正确的版本。

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

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

在“配置新项目”页面,在“项目名称”框中输入“FabonacciDll”,以指定项目的名称 。 保留默认“位置”和“解决方案名称”值 。 将“解决方案”设置为“创建新解决方案” 。 如果“将解决方案和项目放在同一目录中”已选中,则取消选中 。

将头文件添加到 DLL在菜单栏上选择“项目”>“添加新项” 。

在菜单栏上选择“项目”>“添加新项” 。在“添加新项”对话框的左窗格中,选择“Visual C++” 。 在中间窗格中,选择 “头文件(.h)” 。 指定 FabonacciDll.h 作为头文件的名称 。将头文件的内容替换为以下代码:

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

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

向 DLL 添加内容

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

在编辑器窗口中,选择 FabonacciDll.cpp 的选项卡(如果已打开)。在编辑器中,将 FabonacciDll.cpp文件的内容替换为以下代码:

// FabonacciDll.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 "FabonacciDll.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”文件夹中 。对应文件夹下可以看到发布的dll文件(和lib文件等)。

注意:x64和x86(64位和32位)动态链接库不兼容,在调用时也一样,64位程序和32位程序只能调用对应的dll文件,本文以x86为例。需要在编译时选择x86.

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

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

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

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

在 Visual Studio 中创建客户端应用

选择“文件”>“新建”>“项目”,打开“创建新项目”对话框。在对话框顶部,将“语言”设置为“C++”,将“平台”设置为“Windows”,并将“项目类型”设置为“控制台”。从筛选的项目类型列表中,选择“控制台应用”,然后选择“下一步” 。在“配置新项目”页面,在“项目名称”框中输入“FabonacciDllTest”,以指定项目的名称 。

将FabonacciDllTest.cpp文件内的内容替换到以下内容:

// FabonacciDllTest.cpp : Client app for MathLibrary DLL.
// #include "pch.h" Uncomment for Visual Studio 2017 and earlier
#include <iostream>
#include "FabonacciDll.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;
}

导入DLL库并链接相关资源

要调用dll,需要导入dll的相关资源,包括引用头文件,导入lib文件并指定dll文件位置,如果需要编译后正常运行,还可以通过脚本将dll文件自动复制到目标文件夹下。

将 DLL 标头添加到包含路径

右键单击“解决方案资源管理器” 中的项目节点。在“配置”下拉框中,选择“所有配置”。在左窗格中,选择“配置属性”>“C/C++”>“常规” 。在属性窗格中,选择“附加包含目录” 编辑框旁的下拉控件,然后选择“编辑” 。

在“附加包含目录” 对话框的顶部窗格中双击以启用编辑控件。 或者,选择文件夹图标以创建新条目。在编辑控件中,指定指向 FabonacciDll.h 头文件的位置的路径。 可选择省略号 (…) 控件浏览到正确的文件夹 。

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

..\..\FabonacciDll\FabonacciDll

将 DLL 导入库添加到项目中

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

在“附加依赖项”对话框中,将 FabonacciDll.lib 添加到顶部编辑控件的列表中 。在左窗格中,选择“配置属性”>“链接器”>“常规” 。 在属性窗格中,选择“附加库目录” 编辑框旁的下拉控件,然后选择“编辑” 。

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

在生成后事件中复制 DLL

经过以上步骤后,项目已经可以正常编译,但是如果要直接运行,仍会报错,这是因为没有将需要的动态链接库dll文件复制到生成的客户端目录下,当然,手动复制必要的dll文件也可以保证代码正常运行,但更方便的方法是使用脚本命令。在左窗格中,选择“配置属性”>“生成时间”>“后期生成事件” 。在属性窗格中,在“命令行”字段中选择编辑控件 。 如果已按照指示将客户端项目置于 DLL 项目的单独解决方案中,则输入以下命令(源文件路径需要根据实际路径修改,可输入相对或者绝对路径):

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

这样就可以编译并运行客户端程序了。由于VC程序运行还需要依赖其他源文件,例如vcruntime140_1d.dll的运行时链接库,也可以将相应文件复制到生成目录下,这样项目可以复制到任何windows机器下运行。

本文对应代码源文件如下:

参考的微软官方文档 演练:创建和使用自己的动态链接库 (C++)

作者
魏智勇(John)
加入讨论

此站点使用 Akismet 来减少垃圾评论。了解我们如何处理您的评论数据

魏智勇(John)

站长,80后,创业者,擅长工业自动化与信息化技术,熟悉各种PLC,组态软件,熟悉计算机技术,熟悉LabVIEW、C,C#,JavaScript程序设计技术。