使用 CMake 编译生成 DLL(动态链接库)详解

使用 CMake 编译生成 DLL(动态链接库)详解

在现代软件开发中,动态链接库(DLL)是实现模块化和代码复用的重要手段。尤其在 Windows 平台上,DLL 的使用尤为普遍。本文将详细介绍如何使用 CMake 编译生成 DLL,包括项目配置、代码编写、构建步骤以及跨平台考虑等内容。

基本概念

在 CMake 中,创建动态链接库(DLL)主要通过 add_library 命令实现,并指定库类型为 SHARED。为了确保在 DLL 中正确导出符号,需要适当设置导出宏。以下是实现这一过程的详细步骤。

项目结构

假设你的项目名称为 MyProject,其目录结构如下:

MyProject/
├── CMakeLists.txt
├── include/
│   └── mylib.h
└── src/
    └── mylib.cpp

编写源代码

头文件 mylib.h

首先,创建一个头文件 mylib.h,用于声明 DLL 中导出的函数。

#ifndef MYLIB_H
#define MYLIB_H

#ifdef MYLIB_EXPORTS
#define MYLIB_API __declspec(dllexport)
#else
#define MYLIB_API __declspec(dllimport)
#endif

extern "C" {
    MYLIB_API void hello();
}

#endif // MYLIB_H

说明:

  • MYLIB_EXPORTS 宏用于在编译 DLL 时导出符号,而在使用 DLL 时导入符号。
  • __declspec(dllexport)__declspec(dllimport) 是 Windows 平台下用于导出和导入符号的关键字。
  • extern "C" 用于防止 C++ 的名称修饰,确保函数名在 DLL 中以 C 风格导出,便于其他语言或工具使用。

源文件 mylib.cpp

接下来,编写实现上述函数的源文件 mylib.cpp

#include "mylib.h"
#include <iostream>

void hello() {
    std::cout << "Hello from DLL!" << std::endl;
}

配置 CMakeLists.txt

在项目根目录下创建 CMakeLists.txt 文件,配置项目的构建过程。

cmake_minimum_required(VERSION 3.10)
project(MyLibrary VERSION 1.0.0 LANGUAGES CXX)

# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 定义库
add_library(MyLibrary SHARED src/mylib.cpp)

# 指定头文件的位置
target_include_directories(MyLibrary
    PUBLIC
        ${PROJECT_SOURCE_DIR}/include
)

# 定义 MYLIB_EXPORTS 宏,用于导出符号
target_compile_definitions(MyLibrary
    PRIVATE MYLIB_EXPORTS
)

# 设置库的输出目录(可选)
set_target_properties(MyLibrary PROPERTIES
    RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
    LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
    ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
)

说明:

  • add_library(MyLibrary SHARED src/mylib.cpp) 创建一个名为 MyLibrary 的动态链接库。
  • target_include_directories 指定了头文件的位置,使用 PUBLIC 表示这些头文件对使用该库的项目可见。
  • target_compile_definitions 定义了 MYLIB_EXPORTS 宏,仅在编译该库时有效,用于导出符号。
  • set_target_properties 设置了输出目录,可以根据需要调整。

构建 DLL

步骤 1:创建构建目录

打开终端(例如,Visual Studio 开发者命令提示符),然后执行以下命令创建并进入构建目录:

mkdir build
cd build

步骤 2:运行 CMake 配置项目

根据你的 Visual Studio 版本和目标架构,运行以下命令:

cmake .. -G "Visual Studio 16 2019" -A x64

说明:

  • -G "Visual Studio 16 2019" 指定使用的生成器,根据你的 Visual Studio 版本进行调整。
  • -A x64 指定生成 64 位库。你也可以选择 Win32 等其他架构。

步骤 3:编译项目

执行以下命令编译项目:

cmake --build . --config Release

说明:

  • --config Release 指定生成配置为 Release,你也可以选择 Debug。

完成后,生成的 DLL 将位于 build/bin/Release/ 目录下。

使用生成的 DLL

假设你有另一个项目需要使用 MyLibrary.dll,以下是如何配置和使用该 DLL 的示例。

创建使用 DLL 的项目

假设新项目名称为 MyApp,其目录结构如下:

MyApp/
├── CMakeLists.txt
├── main.cpp

源文件 main.cpp

编写一个简单的应用程序,调用 DLL 中的函数。

#include "mylib.h"

int main() {
    hello();
    return 0;
}

配置 CMakeLists.txt

MyApp 项目根目录下创建 CMakeLists.txt 文件:

cmake_minimum_required(VERSION 3.10)
project(MyApp LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 添加可执行文件
add_executable(MyApp main.cpp)

# 指定 MyLibrary 的路径
add_subdirectory(path/to/MyProject)

# 链接 MyLibrary
target_link_libraries(MyApp PRIVATE MyLibrary)

# 设置可执行文件的运行目录,以便找到 DLL
set_target_properties(MyApp PROPERTIES
    RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)

说明:

  • add_subdirectoryMyProject 项目添加到 MyApp 项目中,需要根据实际路径调整。
  • target_link_librariesMyLibrary 链接到 MyApp
  • 确保 MyLibrary.dll 位于可执行文件所在的目录,或者在系统的 PATH 中,以便在运行时找到 DLL。

构建和运行

MyApp 项目目录下执行以下命令:

mkdir build
cd build
cmake .. -G "Visual Studio 16 2019" -A x64
cmake --build . --config Release

# 运行可执行文件
./bin/Release/MyApp.exe

运行后,应该会在控制台看到输出:

Hello from DLL!

进一步优化

自动复制 DLL

为了方便运行,可以在 MyLibraryCMakeLists.txt 中添加自定义命令,自动将 DLL 复制到 MyApp 的可执行文件目录中。

# 在 MyLibrary 的 CMakeLists.txt 中添加

add_custom_command(TARGET MyLibrary POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:MyLibrary> 
    $<TARGET_FILE_DIR:MyApp/MyApp>
)

说明:

  • 这条命令将在 MyLibrary 构建后执行,将生成的 DLL 复制到 MyApp 的可执行文件目录中。

使用 GenerateExportHeader

为了简化导出符号的管理,可以使用 GenerateExportHeader 模块。

修改 CMakeLists.txt

# 在项目中启用 GenerateExportHeader
include(GenerateExportHeader)

add_library(MyLibrary SHARED src/mylib.cpp)

# 生成导出头文件
generate_export_header(MyLibrary
    EXPORT_FILE_NAME "${CMAKE_CURRENT_BINARY_DIR}/mylib_export.h"
)

# 包含生成的导出头文件
target_include_directories(MyLibrary
    PUBLIC
        ${PROJECT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_BINARY_DIR}
)

修改头文件 mylib.h

#ifndef MYLIB_H
#define MYLIB_H

#include "mylib_export.h"

extern "C" {
    MYLIB_EXPORT void hello();
}

#endif // MYLIB_H

说明:

  • GenerateExportHeader 自动生成导出头文件,简化跨平台导出符号的过程。
  • 这样可以更方便地管理不同平台下的符号导出,无需手动定义 __declspec(dllexport)__declspec(dllimport)

跨平台考虑

虽然 DLL 是 Windows 平台的专用格式,但在其他平台上,CMake 也支持生成共享库(如 .so 文件在 Linux 上,.dylib 文件在 macOS 上)。在跨平台项目中,可以使用条件判断来设置导出宏:

示例:

// mylib.h

#ifndef MYLIB_H
#define MYLIB_H

#if defined(_WIN32) || defined(_WIN64)
    #ifdef MYLIB_EXPORTS
        #define MYLIB_API __declspec(dllexport)
    #else
        #define MYLIB_API __declspec(dllimport)
    #endif
#else
    #define MYLIB_API
#endif

extern "C" {
    MYLIB_API void hello();
}

#endif // MYLIB_H

说明:

  • 在非 Windows 平台上,MYLIB_API 不进行任何特殊处理,适用于 Unix-like 系统。

总结

使用 CMake 编译生成 DLL 涉及多个步骤,包括项目配置、代码编写、构建过程以及符号导出管理。通过本文的详细介绍,你应该能够:

  1. 定义库类型为 SHARED:使用 add_library 命令并指定为 SHARED
  2. 管理符号导出:使用导出宏(如 __declspec(dllexport)__declspec(dllimport))确保正确导出和导入符号。
  3. 配置 CMakeLists.txt:设置包含目录、编译选项和输出目录。
  4. 构建项目:使用 CMake 配置和生成构建文件,然后编译生成 DLL。
  5. 使用 DLL:在其他项目中链接生成的 DLL,并确保在运行时能够找到 DLL 文件。
  6. 优化和跨平台支持:通过自动复制 DLL 和使用 GenerateExportHeader 模块,进一步优化构建过程,并支持跨平台开发。
posted @ 2025-01-08 16:11  悲三乐二  阅读(3252)  评论(2)    收藏  举报