IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> Windows 上开发使用跨平台 C++ 动态链接库 (DLL) -> 正文阅读

[C++知识库]Windows 上开发使用跨平台 C++ 动态链接库 (DLL)

起因

学习过程中, 准备从网上使用一个使用 CMake 管理的 C++ 库项目, 这个项目将构建为一个动态链接库 (DLL).

在 Windows 上, 动态链接库本身可以完成编译链接过程且不报错, 而调用该库的程序能够完成编译, 但是在链接过程提示 “未定义符号 (undefined symbols)” (经过确认为动态库里的符号).

经过检查, 链接使用的命令没有问题, 那么就意味着链接器没有找到这些符号, 但是为什么会出现这样的情况, 以及该如何解决呢?

这说明, 某个阶段不报错并不意味着这个阶段没问题啊.

一个简单的例子

接下来用一个简单的例子来说明下这个问题. 项目源代码参考 cpp-multi-file-demo 仓库.

使用到的 Date 库

例子中有一个简单的 Date 库, 代码如下:

Date/Date.hpp

#ifndef DATE_HPP
#define DATE_HPP

class Date {
    int y;
    int m;
    int d;
    bool is_leap(int year) {
        return (year % 4 ==0 && year % 100 != 0) || year % 400 == 0;
    }

public:
    Date(int y_, int m_, int d_) : y(y_), m(m_), d(d_) {}
    int get_days();
};

#endif // DATE_HPP

Date/Date.cpp

#include "Date.hpp"

int Date::get_days()
{
    int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    if (is_leap(this->y)) {
        days[2] = 29;
    }
    auto cnt = 0;
    for (int i = 1; i < this->m; ++i) {
        cnt += days[i];
    }
    cnt += this->d;
    return cnt;
}

Date/CMakeLists.txt

稍微修改该 CMake, 使得 add_library 声明一个生成动态链接库 (即 SHARED 共享库) 的目标

默认为 STATIC, 即静态链接库; 除此之外还有 OBJECTS 类型的库目标. 使用库目标可以更好的处理代码之间的依赖和复用, 不过本篇内容中不详细展开.

project(My-Date)

cmake_minimum_required(VERSION 3.13)

set(CMAKE_CXX_STANDARD 14)

add_library(Date SHARED "Date.hpp" "Date.cpp")
target_include_directories(Date PUBLIC .)

调用 Date 库

之后调用这个库.

这里是将 Date 库作为子目录引入 (使用 add_subdirectory 命令), 这样 CMake 便能识别到其中定义的库目标 (library target), 即 Date, 并且能够直接使用相应的库目标名.

在项目构建时, Date 库作为依赖, 将先被从源码构建 (而不是使用现成的库), 之后再被链接到调用该库的目标.

CMakeLists.txt

project(CPP-Multi-File-Demo)
cmake_minimum_required(VERSION 3.13)

# 子目录 Date 中的 CMakeLists.txt 中包含生成 Date 这个目标, 引入后便可以使用
add_subdirectory(Date)

# 创建一个名为 demo 的生成可执行文件的目标, 使用 demo.cpp 源文件
add_executable(demo "demo.cpp")

# 将 demo 和 "Date" 库进行链接
target_link_libraries(demo Date)

demo.cpp

#include <iostream>
#include "Date.hpp"

int main() {
    int y, m, d;
    std::cin >> y >> m >> d;
    Date d1(y, m, d);
    std::cout << d1.get_days();
}

问题复现

为了更好地说明问题, 体现跨平台的特性, 这里分别在 Linux 和 Windows 上进行测试.

在 Ubuntu 上构建

首先尝试在 Ubuntu 上构建.

将项目下载到本地, 按照上述修改 Date/CMakeLists.txt 中的 add_library 命令为生成 SHARED 共享库目标.

在 Ubuntu 终端里的构建过程

使用的过程就是一个基本的 CMake 项目的构建流程:

cd project-folder
mkdir build
cd build
cmake ..
cmake --build .

通过 cmake --build . 的输出可以得知, 首先编译 Date.cpp 得到 Date.cpp.o, 并链接得到了 C++ 共享库 libDate.so; 之后编译 demo.cpp 得到 demo.cpp.o, 并经过链接得到可执行程序 demo; 整个过程没有问题, 最后得到的可执行文件能够正常执行.

在 Windows 上构建

首先打开我们的 Visual Studio 老大哥.

默认 Visual Studio 使用的是自家的 MSVC 编译器. 可以看到结果是生成失败, “fatal erroe LNK1104: 无法打开文件 Date/Date.lib”.

经过检查发现文件夹里确实没有 .lib 文件 (但是有 .dll 文件), 说明 MSVC 根本没生成 LIB 文件.

根据经验, 我们知道 Windows 上开发时使用的 (动态链接) 库文件是 lib 和 dll 成对配套的, lib 文件起到一个索引的作用, dll 中存放真正的代码. 既然这里没有生成, 那问题也就好解决了, 基本上搜索一下就能出来.

在 Visual Studio 中尝试构建, 发现报错

之后换用 Clang for MSVC, 虽然输出了 Date.lib, 但是还是提示找不到符号.

在 VSCode 中使用 CMake Tool + Clang for MSVC 尝试进行构建

原因和解决办法

通过查询可知, 在 Linux 上动态库默认是导出所有符号的, 而 Windows 则不然, 还需要单独的 LIB 文件来配套动态链接库使用.

详情可以参考微软的文档 Exporting from a DLL - Microsoft Docs, 或者自行搜索相关关键词 (如 “Windows” “DLL” “symbols” 等).

这里介绍一个简单的解决方法, 即在 CMake 进行 Configure 的过程中, 传递这样一个选项, 即 -DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=TRUE.

CMake 传递选项的方法

下面介绍几个常用 IDE 中添加 CMake 选项的方式.

要添加的选项就是 -DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=TRUE.

Visual Studio Code + CMake Tools

图形化界面的添加方法如下:

在这里插入图片描述

这个选项也可以设置成目录的 (而非全局的), 也就是在工作目录下新建一个 .vscode 目录, 在其中新建一个 settings.json, 并确保其中有一个键名为 cmake.configureArgs 的列表, 其中包含相应的要传递给 CMake 的选项.

文件 .vscode/settings.json 的内容类似如下:

{
  "cmake.configureArgs": [
    "-DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=TRUE",
    "-DCMAKE_TOOLCHAIN_FILE=C:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake"
  ]
}

Visual Studio

确保在 Visual Studio 中打开的是一个 CMake 项目.

在 “项目” 菜单里选择 “<项目名> 的 CMake 设置”, 之后找到 “命令参数”, 并在其中添加要使用的选项.

在这里插入图片描述

这会在 CMake 项目下生成一个 CMakeSettings.json 文件. 在上述图形界面里的修改都会同步存储到这个文件里.

CLion

CLion 的配置也是类似,在 “文件 (Files)” → “设置 (Settings)” → “构建, 执行, 部署 (Build, Execution, Deplyment)” → “CMake” 中, 在某个配置中的 CMake options 中添加要传递给 CMake 的选项.

使用动态链接库的问题

在笔者测试时, 在执行 demo 时还会出现找不到 DLL 库 (动态链接库) 的问题, 猜测 Windows 上可执行文件链接到库时使用的是相对路径; 而在 Linux 上则似乎不用额外处理.

因为动态链接是在运行时装载, 所以尽管生成可执行程序时能够成功链接, 但是运行时还是有可能失败.

解决方式也就是让可执行文件能够找到库文件即可, 也就是保证 DLL 的搜索路径里存在需要的文件.

一般来说, 手动将生成的 DLL 库复制到 demo 所在的目录下, 或者在 CMake 中添加一个 install 命令, 将生成的库文件安装到可执行文件装载时能够找到的目录之后再执行, 就能够解决问题了.

install(TARGETS Date demo)

在 Visual Studio 中, 会默认设置 CMAKE_INSTALL_PREFIX 变量 (也就是 install 命令的默认路径前缀) 为项目目录下的 out/install/.

在其他 IDE 中, 若没有设置该变量, 则 CMake 默认会安装到系统的目录去, 在 Windows 上就是 %ProgramFiles%, 而由于一般情况下 IDE 程序没有管理员权限, 则安装到该目录时将会由于权限不足而失败.

按理说, 共享库确实应该安装到一个 “共享” 的地方. 但是由于自己调试期间写的程序可能不标准, 安装到系统目录去一般也不是啥好事, 因此一般要特别处理这些问题, 比如手动复制库文件或者修改 CMake 的安装前缀.

Windows 上的 Visual Studio 在添加了 install 目标的情况下, 会出现一个带有 “(安装)” 字样的运行选项, 点击即可进行安装并直接运行安装后的可执行程序.

Windows 上其他的 IDE 推荐在项目的 CMakeLists.txt 中设定 CMAKE_INSTALL_PREFIX 变量 (因为可以获取到 PROJECT_SOURCE_DIR 变量, 以便将内容安装在项目当前目录下). 这里以项目目录下的 install 为例.

set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR}/install)

之后运行相应的 “安装” 目标 (或者 install ALL 安装所有目标), 把文件都安装到同一个目录下, 总之确保可执行文件依赖的 DLL 文件都能够被找到.

Windows 下 CMake 会把 DLL 和可执行程序都放在 CMAKE_INSTALL_PREFIX 下的 bin 目录里, 而不是 LIB 文件所在的在 lib 目录下, 因此可执行程序可以找到 dll

这样之后可执行程序便应当能够正常运行. 不过需要注意执行的是安装目录里的可执行文件, 而不是构建目录里的.

为了方便, 可以在 IDE 中进行一些配置.

Visual Studio Code 的 CMake Tools 插件暂时不清楚该如何配置, 不过至少可以通过命令行手输命令执行.

CLion 中可以参考如下方式, 修改 “Run/Debug Configuration” 中的 “Working directory” 并添加 “Before launch” 的任务来实现:

在这里插入图片描述

至此, 问题就应该全部解决了.

总结

总结就是, 尽量少用动态链接库…

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-07-05 23:22:06  更:2022-07-05 23:23:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 15:41:50-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码