cmake 同时生成共享库so及可执行文件的工程配置

1、使用cmake生成共享库及调用该库的可执行文件

共享库文件.so,及调用该so的可执行文件,使用场景就是,so是用来提供给客户的,可执行文件是自己用来测试看so的效果的。
上代码。
这里是一个简单的C++项目的示例,它使用 CMake 来生成一个共享库(.so 文件)以及一个调用该共享库的可执行文件。

项目结构

MyProject/
├── CMakeLists.txt
├── lib/
│   ├── CMakeLists.txt
│   └── mylib.cpp
├── include/
│   └── mylib.h
└── app/
    ├── CMakeLists.txt
    └── main.cpp

顶层 CMakeLists.txt

cmake_minimum_required(VERSION 3.10)

# 项目信息
project(MyProject VERSION 1.0)

# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# 添加子目录
add_subdirectory(lib)
add_subdirectory(app)

lib/CMakeLists.txt

# 生成共享库
add_library(mylib SHARED mylib.cpp)

# 设置库的头文件路径
target_include_directories(mylib PUBLIC ${CMAKE_SOURCE_DIR}/include)

lib/mylib.cpp

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

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

include/mylib.h

#ifndef MYLIB_H
#define MYLIB_H

void hello();

#endif // MYLIB_H

app/CMakeLists.txt

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

# 链接共享库
target_link_libraries(myapp PRIVATE mylib)

app/main.cpp

#include "mylib.h"

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

构建项目

  1. 创建并进入构建目录:

    mkdir build
    cd build
    
  2. 生成构建文件:

    cmake ..
    
  3. 构建项目:

    make
    

这样,你将生成一个共享库 libmylib.so 以及一个调用该共享库的可执行文件 myapp。执行 myapp 时,它将输出 “Hello from the library!”。

整个项目通过使用 CMake 实现了生成共享库和调用该库的可执行文件的功能。你可以根据需要进一步扩展和调整这个项目的结构和内容。
关于生成链接库,public,private,interface的区别如下:
在 CMake 中,target_link_libraries 命令用于为目标添加库依赖项。PRIVATEPUBLIC 是访问级别指示符,用于控制依赖关系传播的范围。它们的区别在于:

  1. PRIVATE

    • 表示库的链接仅对当前目标可见,不会传播到依赖该目标的其他目标。
    • 仅当前目标需要知道所链接的库。
  2. PUBLIC

    • 表示库的链接对当前目标和依赖当前目标的其他目标都可见。
    • 链接库不仅影响当前目标,还会传播到依赖该目标的其他目标。
  3. INTERFACE(虽然你没提到,但为了完整性也解释一下):

    • 表示库的链接仅对依赖当前目标的其他目标可见,而不影响当前目标本身。
    • 当前目标不需要该库,但依赖当前目标的目标需要。

下面是一个简单的示例,演示这三者的区别:

# 顶层 CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(LinkLibrariesExample)

add_subdirectory(libA)
add_subdirectory(libB)
add_subdirectory(app)

libA/CMakeLists.txt

add_library(libA STATIC libA.cpp)
target_include_directories(libA PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)

libA/libA.cpp

#include <iostream>
void funcA() {
    std::cout << "Function A" << std::endl;
}

libA/include/libA.h

#ifndef LIBA_H
#define LIBA_H

void funcA();

#endif // LIBA_H

libB/CMakeLists.txt

add_library(libB STATIC libB.cpp)
target_include_directories(libB PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(libB PUBLIC libA) # libB 依赖 libA

libB/libB.cpp

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

void funcB() {
    funcA();
    std::cout << "Function B" << std::endl;
}

libB/include/libB.h

#ifndef LIBB_H
#define LIBB_H

void funcB();

#endif // LIBB_H

app/CMakeLists.txt

add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE libB) # myapp 依赖 libB

app/main.cpp

#include "libB.h"

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

在这个示例中:

  • libB 通过 target_link_libraries(libB PUBLIC libA) 明确表示 libAlibB 是公共依赖。因此,依赖 libB 的任何目标(如 myapp)也会自动链接 libA,并且可以访问 libA 的头文件和符号。

  • 如果 libB 通过 target_link_libraries(libB PRIVATE libA) 表示 libAlibB 是私有依赖,那么 myapp 不会自动链接 libA,也不能访问 libA 的符号。

  • 如果 libB 通过 target_link_libraries(libB INTERFACE libA) 表示 libAlibB 是接口依赖,那么 libB 本身不会链接 libA,但是任何依赖 libB 的目标(如 myapp)会自动链接 libA

这种控制方式可以帮助更精确地管理依赖关系,避免不必要的库链接,确保编译和链接的效率。

2、包含保护

在 C++ 编程中,#ifndef#define#endif 常用于头文件的包含保护(include guard)。它们的作用是防止头文件被多次包含导致的重复定义问题。

具体解释

  1. #ifndef (if not defined)

    • 该指令检查指定的宏是否未被定义。如果未定义,则处理后续的代码段。
  2. #define

    • 该指令定义一个宏,通常在 #ifndef 之后使用,以确保后续包含同一个头文件时宏已经被定义,从而避免重复包含。
  3. #endif

    • 该指令结束一个 #ifndef 或其他预处理条件。

包含保护示例

下面是一个包含保护的典型示例:

myheader.h
#ifndef MYHEADER_H
#define MYHEADER_H

// 头文件内容
void myFunction();

#endif // MYHEADER_H

工作原理

  1. 第一次包含 myheader.h

    • #ifndef MYHEADER_H 检查 MYHEADER_H 是否未被定义。由于它尚未定义,条件为真。
    • #define MYHEADER_H 定义 MYHEADER_H,防止后续重复包含。
    • 包含头文件内容,定义 myFunction
  2. 第二次及后续包含 myheader.h

    • #ifndef MYHEADER_H 检查 MYHEADER_H 是否未被定义。由于它已被定义,条件为假。
    • 直接跳过头文件内容,避免重复定义。

实际效果

在代码中使用包含保护可以防止头文件重复包含引起的编译错误,例如重复定义函数、变量或类。以下是一个示例,演示头文件包含保护的实际效果:

main.cpp
#include "myheader.h"
#include "myheader.h" // 重复包含,但不会导致问题

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

在这个示例中,由于 myheader.h 使用了包含保护,尽管它被包含了两次,但编译器只会处理一次,不会出现重复定义的错误。

现代替代方案

C++20 引入了模块(modules),它提供了一种更现代化的头文件管理方式,可以从根本上避免这些问题。不过,包含保护仍然是目前广泛使用的解决方案,特别是在不支持模块的编译器和代码库中。

#pragma once 是一种用于防止头文件被多次包含的编译指示,它的作用与包含保护(include guard,即 #ifndef#define#endif 组合)类似,但更加简洁。

pragma once 的作用

当在一个头文件中使用 #pragma once 时,编译器会确保该头文件在一个编译单元中只会被处理一次,即使它被多次包含。这样可以防止重复定义的问题。

使用示例

myheader.h
#pragma once

// 头文件内容
void myFunction();

优缺点

优点

  1. 简洁#pragma once 使用起来比传统的包含保护更加简洁,没有定义宏的麻烦。
  2. 减少错误:避免了手动管理宏名称的错误,例如拼写错误或名称冲突。

缺点

  1. 移植性:虽然大多数现代编译器都支持 #pragma once,但它不是 C++ 标准的一部分。在一些非常老旧或特定的编译器上可能不被支持。

兼容性

目前,几乎所有主流编译器(如 GCC、Clang 和 MSVC)都支持 #pragma once,因此在大多数情况下,它是一个安全且有效的选择。

对比示例

使用包含保护(include guard)
#ifndef MYHEADER_H
#define MYHEADER_H

// 头文件内容
void myFunction();

#endif // MYHEADER_H
使用 #pragma once
#pragma once

// 头文件内容
void myFunction();

总结

#pragma once 提供了一种简单有效的方式来防止头文件的重复包含。如果你确定你的编译器支持它,那么它是一个很好的选择。不过,如果你需要兼容非常老旧的编译器,包含保护(include guard)可能是更安全的选择。

3、指定共享库和可执行文件的保存路径

可以通过在 CMake 文件中设置适当的输出目录来指定生成的共享库(.so 文件)和可执行文件的保存位置。你可以使用 CMAKE_LIBRARY_OUTPUT_DIRECTORYCMAKE_RUNTIME_OUTPUT_DIRECTORY 来分别设置共享库和可执行文件的输出目录。

以下是一个简单的 C++ 项目的 CMake 示例,它生成一个共享库(.so 文件)和一个可执行文件,并指定它们的保存位置。

项目结构

my_project/
├── CMakeLists.txt
├── src/
│   ├── main.cpp
│   └── mylib.cpp
├── include/
│   └── mylib.h
└── build/

CMakeLists.txt

cmake_minimum_required(VERSION 3.10)

# 项目信息
project(MyProject VERSION 0.1)

# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# 输出源路径和二进制路径
message(STATUS "src path: ${CMAKE_SOURCE_DIR}")
message(STATUS "binary path: ${CMAKE_BINARY_DIR}")

# 设置库文件和可执行文件的输出目录
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin)

message(STATUS "Library output path: ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
message(STATUS "Executable output path: ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")

# 包含头文件目录
include_directories(${CMAKE_SOURCE_DIR}/include)

# 设置源文件列表
set(SRC_LIST
    src/mylib.cpp
)

# 添加共享库
add_library(mylib SHARED ${SRC_LIST})

# 设置可执行文件源文件
set(MAIN_SRC
    src/main.cpp
)

# 添加可执行文件
add_executable(my_executable ${MAIN_SRC})

# 链接共享库到可执行文件
target_link_libraries(my_executable PRIVATE mylib)

src/mylib.cpp

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

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

include/mylib.h

#pragma once

void myFunction();

src/main.cpp

#include "mylib.h"

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

构建项目

  1. 在项目根目录创建 build 目录并进入:

    mkdir build
    cd build
    
  2. 运行 CMake 配置和生成:

    cmake ..
    cmake --build .
    

结果

构建完成后,你将在 lib 目录下找到生成的共享库文件 libmylib.so,在 bin 目录下找到生成的可执行文件 my_executable

4、多个源文件处理

如果项目的源文件分布在多个文件夹中,你可以使用 file(GLOB ...)aux_source_directory 来收集这些文件夹中的源文件。

示例项目结构

my_project/
├── CMakeLists.txt
├── src/
│   ├── main.cpp
│   └── lib/
│       ├── mylib.cpp
│       └── anotherlib.cpp
├── include/
│   ├── mylib.h
│   └── anotherlib.h
└── build/

CMakeLists.txt

cmake_minimum_required(VERSION 3.10)

# 项目信息
project(MyProject VERSION 0.1)

# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# 输出源路径和二进制路径
message(STATUS "src path: ${CMAKE_SOURCE_DIR}")
message(STATUS "binary path: ${CMAKE_BINARY_DIR}")

# 设置库文件和可执行文件的输出目录
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin)

message(STATUS "Library output path: ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
message(STATUS "Executable output path: ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")

# 包含头文件目录
include_directories(${CMAKE_SOURCE_DIR}/include)

# 使用 GLOB 收集源文件
file(GLOB_RECURSE LIB_SOURCES
    "${CMAKE_SOURCE_DIR}/src/lib/*.cpp"
)

# 设置源文件列表
set(SRC_LIST ${LIB_SOURCES})

# 添加共享库
add_library(mylib SHARED ${SRC_LIST})

# 设置可执行文件源文件
set(MAIN_SRC
    src/main.cpp
)

# 添加可执行文件
add_executable(my_executable ${MAIN_SRC})

# 链接共享库到可执行文件
target_link_libraries(my_executable PRIVATE mylib)

src/lib/mylib.cpp

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

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

include/mylib.h

#pragma once

void myFunction();

src/lib/anotherlib.cpp

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

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

include/anotherlib.h

#pragma once

void anotherFunction();

src/main.cpp

#include "mylib.h"
#include "anotherlib.h"

int main() {
    myFunction();
    anotherFunction();
    return 0;
}

构建项目

  1. 在项目根目录创建 build 目录并进入:

    mkdir build
    cd build
    
  2. 运行 CMake 配置和生成:

    cmake ..
    cmake --build .
    

结果

构建完成后,你将在 lib 目录下找到生成的共享库文件 libmylib.so,在 bin 目录下找到生成的可执行文件 my_executable

这个方法使用了 file(GLOB_RECURSE ...) 来递归收集指定目录下的所有 .cpp 文件,你也可以根据需要调整目录路径和文件扩展名。

  • 11
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值