文章目录
概要
这篇文章的目的是提供一个示例,介绍如何在 Linux 中使用 CMake 作为构建工具来创建 C 共享库。
为什么
我找不到一个清晰而简单的示例来说明如何执行如此基本且非常常见的事情,例如使用 CMake 作为构建工具创建 C 库。这不是关于 C 语言的指南,甚至不是关于 C 库是什么的指南,这更像是实现此目的所需的调查结果的集合。
目的
一般来说,按照模块化设计编写软件是一种很好的(必需的!)做法,以便于发展/维护/理解。库是一种隔离常见软件部分(比如说函数)的方法,使它们可供其他程序使用,提供应用程序将使用的集成点(通常称为 API),以便利用通过它们实现的算法。
库的设计目标也可能是将暴露给外部世界的内容与实际实现该逻辑的内部逻辑分开。这就是为什么我设计了具有公共和私有结构的示例的原因:
- public :包含可通过 .h 文件公开访问的 API 定义。
- private :包含将被隐藏(不可链接)的逻辑的实际实现,只能通过公共 API 访问。
设想
该示例的目的是创建一个实现内部基本数学运算(求和、减法、乘法、除法)的库,仅公开单个功能(称为计算),该功能将被外部化以便让实际程序使用它们。
工作空间
我们将在 Linux 中构建一个共享库,该库将由程序集成。我们需要的工具有:
- 一个 Linux 环境(我在本例中使用了 Ubuntu 20.04,但应该可以在任何支持以下工具的 Linux 发行版中工作)。
- CMake 版本 3.4+
- Gcc 9+
- 文本编辑器
由于示例很简单,旧版本的工具也应该可以工作。
代码
该示例的运行代码可以在这里找到。
所以,我们到了。我们想要实现一个能够实现基本数学运算的库:
- Sum
- Subtraction
- Multiplication
- Division
然而,我们示例的目的不仅是实现这些操作背后的逻辑,而且是将具体操作实现与更通用和集中的单点联系(外部/公共 API)解耦。
这就是为什么我们将向程序公开另一个函数(公共 API):
代码结构
在深入代码之前,让我们准备一个文件夹来存储我们的示例,例如 /tmp/example/ ,我们项目的结构将如下所示:
/tmp/example
├── calc
│ ├── build
│ ├── CMakeLists.txt
│ └── code
│ └── main.c
└── library
├── build
├── CMakeLists.txt
└── code
├── private
│ ├── include
│ │ └── operations.h
│ └── src
│ └── operations.c
└── public
├── include
│ └── mymath.h
└── src
└── mymath.c
两个主文件夹 library 和 calc 将直观地包含库和将使用它的程序。
每个文件夹包含一个 CMakeList.txt 文件、一个 build 和 code 文件夹。这是因为:
- CMakeList.txt :包含构建过程的定义。
- code :包含(库和程序的)源代码。
- build :将用于源外构建方法。如果您在存储库中找不到它,只需自己创建它。
应该多说一句“源外概念”:CMake 是一个出色的工具,它隐藏了构建过程(编译、链接、安装等)的大量复杂性。为了实现这一点,CMake需要大量的中间文件来编排任务。这些文件严格来说并不是您要管理的项目的一部分,而是用于构建该项目的工具链的一部分。为了保持干净的环境,最佳实践是在不同的文件夹 (build) 中初始化构建过程,以避免文件混淆并易于清理内容(例如,很容易保留通过将其放在 .gitignore 上,可以在 git 存储库中清除所有内容)。
库
code 文件夹划分是我为了清楚地了解外部化概念而采取的极端做法。在 private 文件夹中,我放置了处理数学运算的实际实现的代码,而 public 文件夹包含将导出的 API,以便让其他程序使用它。
在 public/include 中,我们可以找到头文件 mymath.h,其中包含 API 的签名,该签名将用于触发文件 位于 private/include 中。
Private implementation
这是operations.h头文件的内容:
#ifndef OPERATIONS_H
#define OPERATIONS_H
double sum(double a, double b);
double sub(double a, double b);
double mult(double a, double b);
double div(double a, double b);
#endif //OPERATIONS_H
这里没什么困难,只是一个简单的函数定义,接受两个双精度数作为输入,返回一个双精度数作为输出。我们看看private/src文件夹下的operations.c文件中的相关实现:
#include "operations.h"
double sum(double a, double b)
{
return a + b;
}
double sub(double a, double b)
{
return a - b;
}
double mult(double a, double b)
{
return a * b;
}
double div(double a, double b)
{
return a / b;
}
Public implementation
这是 mymath.h 的内容:
#ifndef MATH_HEADER
#define MATH_HEADER
#define EXPORT __attribute__((__visibility__("default")))
typedef enum
{
SUM,
SUB,
MULT,
DIV
} operation;
typedef struct
{
int error;
double value;
} result_t;
EXPORT result_t compute(double a, double b, operation o);
#endif //!MATH_HEADER
这里有一些有趣的事情。在第 20 行,您可以看到一个名为 compute 的更“复杂”的函数,它接受两个双精度数作为输入(操作的操作数)和一个定义要执行的操作的 enum他们。结果是一个包含错误标志和值的结构,该值是执行操作的结果。
该函数用 EXPORT 属性标记,该属性在编译时通过 attribute((visibility(“default”))) 指令解析,指示链接器必须导出该符号。这是我们选择使该函数在最终库的二进制文件中可见的点,以便让其他程序链接到它。
那么,让我们看看 mymath.c 内部的实现:
#include "public/include/mymath.h"
#include "private/include/operations.h"
result_t compute(double a, double b, operation o)
{
result_t result = {.error = 0 };
switch (o)
{
case SUM:
result.value = sum(a, b);
break;
case SUB:
result.value = sub(a, b);
break;
case MULT:
result.value = mult(a, b);
break;
case DIV:
result.value = div(a, b);
break;
default:
result.error = 1;
}
return result;
}
非常清楚如何通过此实现连接私有和公共部分:公共函数 compute 使用定义在 operations.h 头文件中的私有操作 sum, sub, mult, div,该操作实现进入operations.c 之一。
编译一切
CMake 将负责组织上面显示的所有文件,通过 gcc 创建构建和链接任务。这是 CMakeList.txt :
################################################################################
### Selecting CMake minimum version
CMAKE_MINIMUM_REQUIRED (VERSION 3.4.0)
################################################################################
### Setting Global Parameters
SET(PROJECT_NAME "My Math Lib")
SET(BINARY_NAME "mymath")
PROJECT("${PROJECT_NAME}")
SET(LIBRARY_VERSION_MAJOR 0)
SET(LIBRARY_VERSION_STRING 0.1)
INCLUDE(GNUInstallDirs)
SET(LIBRARY_BASE_PATH "${PROJECT_SOURCE_DIR}/code")
################################################################################
### Project definition
INCLUDE_DIRECTORIES (
"${LIBRARY_BASE_PATH}"
)
SET(PUBLIC_SOURCES_FILES
"${LIBRARY_BASE_PATH}/public/src/mymath.c"
)
SET(PRIVATE_SOURCES_FILES
"${LIBRARY_BASE_PATH}/private/src/operations.c"
)
SET(PUBLIC_HEADERS_FILES
"${LIBRARY_BASE_PATH}/public/include/mymath.h"
)
SET(PRIVATE_HEADERS_FILES
"${LIBRARY_BASE_PATH}/private/include/operations.h"
)
SET(CMAKE_C_VISIBILITY_PRESET hidden)
ADD_LIBRARY (
${BINARY_NAME} SHARED ${PUBLIC_SOURCES_FILES} ${PRIVATE_SOURCES_FILES}
)
SET_TARGET_PROPERTIES (
${BINARY_NAME} PROPERTIES
VERSION ${LIBRARY_VERSION_STRING}
SOVERSION ${LIBRARY_VERSION_MAJOR}
PUBLIC_HEADER ${PUBLIC_HEADERS_FILES}
)
################################################################################
### Adding compilator definitions, like debug symbols
ADD_DEFINITIONS("-g")
################################################################################
### Library installation directives
INSTALL (
TARGETS ${BINARY_NAME}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
################################################################################
这个 CMakeList.txt 文件非常简单,值得强调的是第 46-48 行 (ADD_LIBRARY ),我们在其中设置共享库定义。在第 50–55 行 (SET_TARGET_PROPERTIES ) 中,我们定义 PUBLIC_HEADER 参数,该参数声明哪些头文件 (.h) 将被视为外部文件(在示例中为 ).这些文件将使用 INSTALL 指令在系统内传播(参见第 65 行),以便其他程序在编译时将它们包含在其源代码中,并链接到库 文件在链接时。
要构建所有内容,只需将自己放入 build 目录并运行:
/tmp/example/library/build $ cmake ..
这将生成 make 使用的编译自动化文件。之后,只需运行:
/tmp/example/library/build $ make
这实际上将构建库。剩下的就是将其安装到系统中:
/tmp/example/library/build $ sudo make install
就是这样,您应该在 /usr/local/include 下找到 mymath.h ,在 /usr/local/lib 下找到 libmymath.so 。
使用库
现在该库已经构建并安装,使用它非常容易。只需将头文件 mymath.h 包含到您的代码中并使用它公开的功能即可。这是您可以在 code/src 路径下找到的 main.c 代码的代码:
#include <stdio.h>
#include <mymath.h>
int main()
{
double a = 1;
double b = 2;
operation op = DIV;
result_t s = compute(1, 2, op);
if (!s.error)
{
printf("Operation result: %0.3f\n", s.value);
}
else
{
printf("Error on operation\n");
}
}
另外,相关的 CMakeLists.txt :
################################################################################
### Selecting CMake minimum version
CMAKE_MINIMUM_REQUIRED (VERSION 3.4.0)
################################################################################
### Setting Global Parameters
SET(PROJECT_NAME "Calc using My Math Library")
SET(BINARY_NAME "calc")
PROJECT("${PROJECT_NAME}")
SET(APP_VERSION_STRING 1.0)
INCLUDE(GNUInstallDirs)
SET(APP_BASE_PATH "${PROJECT_SOURCE_DIR}/code")
SET(THREADS_PREFER_PTHREAD_FLAG ON)
FIND_PACKAGE(Threads REQUIRED)
################################################################################
### Check Dependencies
INCLUDE(CheckIncludeFile)
################################################################################
### Project definition
INCLUDE_DIRECTORIES (
"${APP_BASE_PATH}/"
)
FILE(
GLOB SOURCES
"${APP_BASE_PATH}/*.c"
)
LINK_DIRECTORIES(
"/usr/local/lib/"
)
ADD_DEFINITIONS("-g")
ADD_EXECUTABLE (
${BINARY_NAME} ${SOURCES}
)
TARGET_LINK_LIBRARIES(${BINARY_NAME} PUBLIC "mymath")
################################################################################
用于构建示例的过程与库的过程类似。将自己放入 build 目录并运行
在构建过程结束时,您将找到一个 calc 可执行文件。只需运行它:
/tmp/example/calc/build $ ./calc
Operation result: 0.500
标签:SET,CMake,mymath,double,LIBRARY,include,构建,result,动态
From: https://blog.csdn.net/weixin_42990464/article/details/142768240