我使用 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
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_subdirectory 将 MyProject 项目添加到 MyApp 项目中,需要根据实际路径调整。
target_link_libraries 将 MyLibrary 链接到 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
为了方便运行,可以在 MyLibrary 的 CMakeLists.txt 中添加自定义命令,自动将 DLL 复制到 MyApp 的可执行文件目录中。
# 在 MyLibrary 的 CMakeLists.txt 中添加
add_custom_command(TARGET MyLibrary POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy $
$
)
说明:
这条命令将在 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 涉及多个步骤,包括项目配置、代码编写、构建过程以及符号导出管理。通过本文的详细介绍,你应该能够:
定义库类型为 SHARED:使用 add_library 命令并指定为 SHARED。
管理符号导出:使用导出宏(如 __declspec(dllexport) 和 __declspec(dllimport))确保正确导出和导入符号。
配置 CMakeLists.txt:设置包含目录、编译选项和输出目录。
构建项目:使用 CMake 配置和生成构建文件,然后编译生成 DLL。
使用 DLL:在其他项目中链接生成的 DLL,并确保在运行时能够找到 DLL 文件。
优化和跨平台支持:通过自动复制 DLL 和使用 GenerateExportHeader 模块,进一步优化构建过程,并支持跨平台开发。