首页 > 其他分享 >使用CMake构建C动态库

使用CMake构建C动态库

时间:2024-10-08 20:21:59浏览次数:9  
标签:SET CMake mymath double LIBRARY include 构建 result 动态

文章目录

概要

这篇文章的目的是提供一个示例,介绍如何在 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

相关文章

  • CMake 属性之目标属性
    【写在前面】CMake可以通过属性来存储信息。它就像是一个变量,但它被附加到一些其他的实体上,像是一个目录或者是一个目标。例如一个全局的属性可以是一个有用的非缓存的全局变量。在CMake的众多属性中,目标属性( TargetProperties )扮演着尤为重要的角色,它们直接关联到最......
  • 用AI构建小程序可行吗?
    随着移动互联网的快速发展,多端应用的需求日益增长。为了提高开发效率、降低成本并保证用户体验的一致性,前端跨端技术在如今的开发界使用已经非常普遍了,技术界较为常用的跨端技术有小程序技术、HTML5技术两大类。 2023年以来,伴随着AI技术在全球各行各业创造了生产力革命,而AI......
  • Python与虚拟现实:使用Python构建简单的VR场景
    解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界前言虚拟现实(VirtualReality,VR)作为一种沉浸式技术,近年来发展迅速。它不仅应用于游戏,还广泛用于医学模拟、建筑设计、教育培训等领域。通过VR,用户可以进入一个全新的虚拟世界,进行互动与体验。虽然构建复杂的VR......
  • 通过Python构建自动化股票分析工具:从数据抓取到技术分析与买卖信号生成
    解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界前言股票市场是一个高度复杂和波动的领域,投资者常常需要依赖技术分析和数据驱动的策略来做出买卖决策。借助Python,我们可以轻松自动化这些任务,帮助我们分析股票趋势、判断买卖时机,并生成交易信号。本文将详细介......
  • leetcode 刷题day35动态规划Part04 背包问题(1049. 最后一块石头的重量 II 、494. 目标
    1049.最后一块石头的重量II思路:解题的难度在于如何转化为背包问题。本题的核心思路是让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题,和416.分割等和子集非常像了。首先,背包的容量target为sum/2向下取整,得到dp[target]就是分出来较小的一堆,用su......
  • leetcode 刷题day37动态规划Part06背包问题( 322. 零钱兑换、279.完全平方数、139.单词
    322.零钱兑换思路:每种硬币的数量是无限的,是典型的完全背包问题。但是题目要求等于目标值的最小硬币个数。所以这里需要对动规五部曲进行分析。动规五部曲:1、确定dp数组以及下标的含义dp[j]:凑足总额为j所需钱币的最少个数为dp[j]2、确定递推公式凑足总额为j-coins[i......
  • 动态修改iconfont图标配色
    引言在iconfont图标字体库详细介绍一文中介绍了iconfont图标字体库的三种使用方法,分别是1.unicode引用2.font-class引用3.symbol引用。其中只有symbol引用的方式才能保留图标的色彩。但是如果我们想改变图标的颜色,那么该如何做呢?解决方法以React为例,在项目中,封装......
  • 揭秘动态化跨端框架在鸿蒙系统下的高性能解决方案
    作者:京东科技胡大海前言动态化跨端框架(后文统称“动态化”)是一个由京东金融大前端团队全自主研发的,一份代码,可以在HarmonyOS、iOS、Android、Web四端运行的跨平台解决方案。在研发团队使用后可大幅降低研发人力成本;为业务提供实时触达、A/B触达等能力以提升业务投放效率;同时......
  • 文盘rust--使用 Rust 构建RAG
    作者:京东科技贾世闻RAG(Retrieval-AugmentedGeneration)技术在AI生态系统中扮演着至关重要的角色,特别是在提升大型语言模型(LLMs)的准确性和应用范围方面。RAG通过结合检索技术与LLM提示,从各种数据源检索相关信息,并将其与用户的问题结合,生成准确且丰富的回答。这一机制特别适用于需......
  • 构建高效水果购物平台:SpringBoot飘香网站案例
    1系统概述1.1研究背景如今互联网高速发展,网络遍布全球,通过互联网发布的消息能快而方便的传播到世界每个角落,并且互联网上能传播的信息也很广,比如文字、图片、声音、视频等。从而,这种种好处使得互联网成了信息传播的主要途径,社会上各种各样的信息都想尽办法通过互联网进行......