当我们使用C或者C++来造轮子的时候,我们需要使用各种各样的构建工具,如果我们选用当今的事实标准构建库:CMake来构建项目的时候,我们可能会感觉以前的手动添加头文件和链接库文件是一件很麻烦的事情。这个时候,我们可以利用现在CMake的特性来为我们自动生成这些文件。
引言
有过一定C++开发经验的人都会知道,现代C++的事实构建标准是CMake,同时,使用CMake作为C++的项目管理工具是一件很方便的事情,我们可以轻松通过 find_package和target_link指令来快速引入一个外部库。大部分现代C++库实际上都提供了一个findxxx.cmake的包,实际上,这些包都是可以用CMAKE自动生成的。
我们以一个例子来说明一下如何使用CMAKE使得我们的项目可以轻松实现和大部分现代C++库一样的CMAKE引入特性。
实战
我们以一个C库为例子,这里的实际情况会比纯C++更加复杂,因为很多C++库的核心层都是用C来实现的,我们需要这样的例子来更加透彻的理解CMake的作用。
开发一个C的模块库
现在,让我们来开始手搓一个C的模块管理库,使得我们可以动态加载模块。即使是C++库,也有很多库的外面套了一层C的模块管理。
因为用C需要extern C的缘故,我们要写很多的判断,我们先来封装一个简单的宏。
#ifndef EXTERN_C_H
#define EXTERN_C_H
#ifdef __cplusplus
#define BEGIN_EXTERN_C extern "C" {
#define END_EXTERN_C }
#else
#define BEGIN_EXTERN_C
#define END_EXTERN_C
#endif
#endif // EXTERN_C_H
然后,我们来写核心模块
#ifndef CMOD_H
#define CMOD_H
#include <stddef.h>
#include"extern_c.h"
BEGIN_EXTERN_C
// 模块接口结构体
struct cmod {
const char *name; // 模块名称
void *(*init)(void); // 初始化函数
void (*destroy)(void *); // 销毁函数
void *userdata; // 用户数据
};
// 模块管理器接口
struct cmod_manager {
struct cmod **modules; // 模块列表
size_t module_count; // 模块数量
size_t capacity; // 模块列表容量
};
// 初始化模块管理器
struct cmod_manager *cmod_manager_create_unique(void);
// 销毁模块管理器
void cmod_manager_destroy_unique(struct cmod_manager *manager);
// 注册模块
int cmod_register_unique(struct cmod_manager *manager, const char *name, void *(*init)(void), void (*destroy)(void *));
// 卸载模块
int cmod_unregister_unique(struct cmod_manager *manager, const char *name);
// 导入模块
void *cmod_import_unique(struct cmod_manager *manager, const char *name);
END_EXTERN_C
#endif // CMOD_H
实现部分
#include "cmod.h"
#include <stdlib.h>
#include <string.h>
// 初始化模块管理器
struct cmod_manager *cmod_manager_create_unique(void) {
struct cmod_manager *manager = malloc(sizeof(struct cmod_manager));
if (!manager) return NULL;
manager->module_count = 0;
manager->capacity = 10;// 初始容量
manager->modules = malloc(sizeof(struct cmod *) * manager->capacity);
if (!manager->modules) {
free(manager);
return NULL;
}
return manager;
}
// 销毁模块管理器
void cmod_manager_destroy_unique(struct cmod_manager *manager) {
if (!manager) return;
for (size_t i = 0; i < manager->module_count; i++) {
if (manager->modules[i]->destroy) {
manager->modules[i]->destroy(manager->modules[i]->userdata);
}
free(manager->modules[i]);
}
free(manager->modules);
free(manager);
}
// 注册模块
int cmod_register_unique(struct cmod_manager *manager, const char *name, void *(*init)(void), void (*destroy)(void *)) {
if (!manager || !name || !init) return -1;
// 检查模块是否已存在
for (size_t i = 0; i < manager->module_count; i++) {
if (strcmp(manager->modules[i]->name, name) == 0) {
return -1;// 模块已存在
}
}
// 扩容
if (manager->module_count >= manager->capacity) {
manager->capacity *= 2;
struct cmod **new_modules = realloc(manager->modules, sizeof(struct cmod *) * manager->capacity);
if (!new_modules) return -1;
manager->modules = new_modules;
}
// 创建模块
struct cmod *module = malloc(sizeof(struct cmod));
if (!module) return -1;
module->name = name;
module->init = init;
module->destroy = destroy;
module->userdata = NULL;
// 初始化模块
module->userdata = module->init();
if (!module->userdata) {
free(module);
return -1;
}
// 添加到管理器
manager->modules[manager->module_count++] = module;
return 0;
}
// 卸载模块
int cmod_unregister_unique(struct cmod_manager *manager, const char *name) {
if (!manager || !name) return -1;
for (size_t i = 0; i < manager->module_count; i++) {
if (strcmp(manager->modules[i]->name, name) == 0) {
if (manager->modules[i]->destroy) {
manager->modules[i]->destroy(manager->modules[i]->userdata);
}
free(manager->modules[i]);
// 移动最后一个模块到当前位置
manager->modules[i] = manager->modules[--manager->module_count];
return 0;
}
}
return -1; // 模块未找到
}
// 导入模块
void *cmod_import_unique(struct cmod_manager *manager, const char *name) {
if (!manager || !name) return NULL;
for (size_t i = 0; i < manager->module_count; i++) {
if (strcmp(manager->modules[i]->name, name) == 0) {
return manager->modules[i]->userdata;
}
}
return NULL; // 模块未找到
}
日志部分
#ifndef LOG_H
#define LOG_H
#include"extern_c.h"
BEGIN_EXTERN_C
// 定义日志级别
typedef enum {
LOG_INFO,
LOG_WARNING,
LOG_ERROR,
LOG_CRITICAL,
LOG_FATAL
} LogLevel;
// 公共接口
void log_message(LogLevel level, const char *format, ...);
void log_info(const char *format, ...);
void log_warning(const char *format, ...);
void log_error(const char *format, ...);
void log_critical(const char *format, ...);
void log_fatal(const char *format, ...);
END_EXTERN_C
#endif // LOG_H
日志实现
#ifndef LOG_H
#define LOG_H
#include"extern_c.h"
BEGIN_EXTERN_C
// 定义日志级别
typedef enum {
LOG_INFO,
LOG_WARNING,
LOG_ERROR,
LOG_CRITICAL,
LOG_FATAL
} LogLevel;
// 公共接口
void log_message(LogLevel level, const char *format, ...);
void log_info(const char *format, ...);
void log_warning(const char *format, ...);
void log_error(const char *format, ...);
void log_critical(const char *format, ...);
void log_fatal(const char *format, ...);
END_EXTERN_C
#endif // LOG_H
模块注册宏
#ifndef REGISTER_IMPORT_H
#define REGISTER_IMPORT_H
#define REGISTER_MODULE(manager, name, init, destroy) \
if (cmod_register_unique(manager, name, init, destroy) != 0) { \
log_error("Failed to register %s module.", name); \
cmod_manager_destroy_unique(manager); \
return 1; \
}
#define IMPORT_MODULE(manager, name, type) \
type *name = (type *)cmod_import_unique(manager, #name); \
if (!name) { \
log_error("Failed to import %s module.", #name); \
cmod_manager_destroy_unique(manager); \
return 1; \
}
#endif // REGISTER_IMPORT_H
C++的封装层,提供C++绑定,可以更方便给C++用
#ifndef CMOD_CXX_API_HPP
#define CMOD_CXX_API_HPP
#include "cmod.h"
#include <stdexcept>
class CModManager {
public:
CModManager() {
manager_ = cmod_manager_create_unique();
if (!manager_) {
throw std::runtime_error("Failed to create module manager.");
}
}
~CModManager() {
cmod_manager_destroy_unique(manager_);
}
void registerModule(const char *name, void *(*init)(void), void (*destroy)(void *)) {
if (cmod_register_unique(manager_, name, init, destroy) != 0) {
throw std::runtime_error("Failed to register module.");
}
}
void unregisterModule(const char *name) {
if (cmod_unregister_unique(manager_, name) != 0) {
throw std::runtime_error("Failed to unregister module.");
}
}
void *importModule(const char *name) {
void *module = cmod_import_unique(manager_, name);
if (!module) {
throw std::runtime_error("Failed to import module.");
}
return module;
}
private:
struct cmod_manager *manager_;
};
#endif// CMOD_CXX_API_HPP
随便实现一个模块函数
#include "extern_c.h"
#ifndef FOO_H
#define FOO_H
BEGIN_EXTERN_C
struct foo {
int (*func)(void);
};
void *foo_init(void);
void foo_destroy(void *data);
END_EXTERN_C
#endif // FOO_H
#include "foo.h"
#include <stdio.h>
#include <stdlib.h>
// 模块函数实现
static int foo_func(void) {
return 42;
}
// 模块初始化函数
void *foo_init(void) {
struct foo *foo = malloc(sizeof(struct foo));
if (!foo) return NULL;
foo->func = foo_func;
return foo;
}
// 模块销毁函数
void foo_destroy(void *data) {
if (data) {
free(data);
data = NULL;// 将指针置为 NULL
}
}
C的example
#include "cmod.h"
#include "foo.h"
#include "log.h"
#include "register_import.h"
int main() {
// 创建模块管理器
struct cmod_manager *manager = cmod_manager_create_unique();
if (!manager) {
log_fatal("Failed to create module manager.");
return 1;
}
// 注册 foo 模块
REGISTER_MODULE(manager, "foo", foo_init, foo_destroy)
// 导入 foo 模块
IMPORT_MODULE(manager, foo, struct foo)
// 调用 foo 模块的函数
int result = foo->func();
log_info("Function returned: %d", result);
// 卸载 foo 模块
if (cmod_unregister_unique(manager, "foo") != 0) {
log_error("Failed to unregister foo module.");
}
// 最好确保在模块卸载后不再使用 foo 指针
//foo = NULL;
// 注册 bar 模块
REGISTER_MODULE(manager, "bar", foo_init, foo_destroy)// 假设 bar 模块使用相同的初始化和销毁函数
// 导入 bar 模块
IMPORT_MODULE(manager, bar, struct foo)
// 调用 bar 模块的函数
result = bar->func();
log_info("Function returned: %d", result);
// 卸载 bar 模块
if (cmod_unregister_unique(manager, "bar") != 0) {
log_error("Failed to unregister bar module.");
}
// 最好确保在模块卸载后不再使用 bar 指针
//bar = NULL;
// 注册 baz 模块
REGISTER_MODULE(manager, "baz", foo_init, foo_destroy)// 假设 baz 模块使用相同的初始化和销毁函数
// 导入 baz 模块
IMPORT_MODULE(manager, baz, struct foo)
// 调用 baz 模块的函数
result = baz->func();
log_info("Function returned: %d", result);
// 卸载 baz 模块
if (cmod_unregister_unique(manager, "baz") != 0) {
log_error("Failed to unregister baz module.");
}
// 最好确保在模块卸载后不再使用 baz 指针
//baz = NULL;
// 销毁模块管理器
cmod_manager_destroy_unique(manager);
return 0;
}
C++的example
#include "cmod_cxx_api.hpp"
#include "foo.h"
#include "log.h"
int main() {
try {
// 创建模块管理器
CModManager manager;
// 注册 foo 模块
manager.registerModule("foo", foo_init, foo_destroy);
// 导入 foo 模块
struct foo* fooModule = static_cast<struct foo*>(manager.importModule("foo"));
// 调用 foo 模块的函数
int result = fooModule->func();
log_info("Function returned: %d", result);
// 卸载 foo 模块
manager.unregisterModule("foo");
// 注册 bar 模块
manager.registerModule("bar", foo_init, foo_destroy); // 假设 bar 模块使用相同的初始化和销毁函数
// 导入 bar 模块
struct foo* barModule = static_cast<struct foo*>(manager.importModule("bar"));
// 调用 bar 模块的函数
result = barModule->func();
log_info("Function returned: %d", result);
// 卸载 bar 模块
manager.unregisterModule("bar");
// 注册 baz 模块
manager.registerModule("baz", foo_init, foo_destroy); // 假设 baz 模块使用相同的初始化和销毁函数
// 导入 baz 模块
struct foo* bazModule = static_cast<struct foo*>(manager.importModule("baz"));
// 调用 baz 模块的函数
result = bazModule->func();
log_info("Function returned: %d", result);
// 卸载 baz 模块
manager.unregisterModule("baz");
} catch (const std::exception& e) {
log_fatal("Exception: %s", e.what());
return 1;
}
return 0;
}
好了,关键点来了,这是我们项目的CMake
cmake_minimum_required(VERSION 3.20) # 设置最低 CMake 版本
project(cmodel LANGUAGES C CXX)
# 设置输出目录
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
# 定义源文件列表
set(LIB_LIST
cmodel/cmod.h cmodel/cmod.c
cmodel/log.h cmodel/log.c
cmodel/extern_c.h
)
# 创建共享库和静态库
add_library(cmodel_shared SHARED ${LIB_LIST})
add_library(cmodel_static STATIC ${LIB_LIST})
# 添加头文件路径
target_include_directories(cmodel_shared PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/cmodel>
$<INSTALL_INTERFACE:include/cmodel>)
target_include_directories(cmodel_static PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/cmodel>
$<INSTALL_INTERFACE:include/cmodel>)
# 添加示例子目录
add_subdirectory(example)
# 安装规则
install(TARGETS cmodel_shared cmodel_static
EXPORT cmodelTargets # 导出目标
ARCHIVE DESTINATION lib/cmodel
LIBRARY DESTINATION lib/cmodel
RUNTIME DESTINATION bin
)
# 安装头文件
install(FILES
cmodel/cmod.h
cmodel/log.h
cmodel/cmod_cxx_api.hpp
cmodel/extern_c.h
cmodel/register_import.h
DESTINATION include/cmodel
)
# 导出目标文件
install(EXPORT cmodelTargets
FILE cmodelTargets.cmake
NAMESPACE cmodel::
DESTINATION lib/cmake/cmodel
)
# 创建并安装 cmodel-config.cmake 文件
configure_file(
${CMAKE_SOURCE_DIR}/cmake/cmodel-config.cmake.in
${CMAKE_BINARY_DIR}/cmodel-config.cmake
@ONLY
)
install(FILES ${CMAKE_BINARY_DIR}/cmodel-config.cmake DESTINATION lib/cmake/cmodel)
# 创建并安装 Findcmodel.cmake 文件
configure_file(
${CMAKE_SOURCE_DIR}/cmake/Findcmodel.cmake
${CMAKE_BINARY_DIR}/Findcmodel.cmake
COPYONLY
)
install(FILES ${CMAKE_BINARY_DIR}/Findcmodel.cmake DESTINATION lib/cmake/cmodel)
我们需要通过install来指定我们要安装的头文件,库文件,以及导出的目标文件。我们这里默认的安装路径是/usr/local/include和/usr/local/lib,并且我们把所有的文件封装到了cmodel里面去。我们就可以通过#include<cmodel/cmod.h>这样来调用。
然后,我们进行安装。因为是系统目录所以得加sudo。
sudo cmake --build . --config Debug --target install -j12 --
你可以通过-DCMAKE_INSTALL_PREFIX来控制它的安装路径。
当然,这里有一个更好的脚本。
#!/bin/bash
# 定义变量
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) # 获取脚本所在目录的绝对路径
BUILD_DIR="${SCRIPT_DIR}/build" # 构建目录
INSTALL_PREFIX="${SCRIPT_DIR}/install" # 自定义安装路径
# 检查 build 目录是否存在
if [ ! -d "${BUILD_DIR}" ]; then
echo "Error: 'build' directory not found. Please run CMake to configure the project first."
exit 1
fi
# 进入 build 目录
cd "${BUILD_DIR}" || { echo "Failed to enter build directory"; exit 1; }
# 检查是否需要管理员权限
echo "Checking installation permissions..."
if [ -w "/usr/local/lib" ]; then
echo "Installing to system directory (/usr/local)..."
sudo cmake --build . --config Debug --target install -j12 --
else
echo "No write permission for /usr/local. Installing to custom directory (${INSTALL_PREFIX})..."
cmake -DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" ..
cmake --build . --config Debug --target install -j12 --
echo "Installation completed. Files are located in: ${INSTALL_PREFIX}"
fi
可以看到我们的/usr/local/lib/cmake就产生了这几个.cmake文件我们这个时候来做一个example,把上面给的代码复制下来,会发现找到了包,并且成功输出了信息:cmodel found: /usr/local/lib/cmake/cmodel
# 查找 cmodel 库
find_package(cmodel REQUIRED)
message(STATUS "cmodel found: ${cmodel_DIR}")
add_executable(testc example.c foo.h foo.c)
target_link_libraries(testc cmodel::cmodel_shared)
add_executable(test_cpp example.cpp foo.h foo.c)
target_link_libraries(test_cpp cmodel::cmodel_shared)