C程序编译、链接与项目构建

C程序编译、链接与项目构建

    • 摘要
    • C编译环境
    • 静、动态库
      • 介绍
      • gcc与g++和程序编译、链接
      • Visual Studio创建和链接库
      • 动态库的显示调用
      • Windows下显示动态库的加载/查找方式
    • Make
      • 介绍
      • 安装
      • 使用
    • CMake
      • 介绍
      • 安装
      • 使用
        • 构建方式
        • 内部构建
        • 外部构建
        • 构建使用静/动态库
        • 常用[系统]变量
        • 常用指令
        • CMake模块
    • Make与CMake的联系与区别

摘要

本篇博客对C/C++程序的编译环境、库创建链接、Make和CMake工具的使用进行介绍,以便加深理解和记忆

C编译环境

  • C编译环境

    • GNU(GNU’s Not Unix):GNU是一个自由软件运动的项目,旨在开发一个类Unix操作系统。GNU项目创建了一系列工具和库,为开发者提供了自由的软件开发环境。其中包括GCC编译器、MinGW和其他开发工具。GNU以开源和自由软件的理念而闻名,为用户提供了更大的自由度和可定制性。

    • GCC(GNU Compiler Collection):GCC是GNU项目的核心组件之一,也是一个开源的编译器集合。它支持多种编程语言,包括C、C++、Objective-C、Fortran等。GCC是一个跨平台的编译器,提供了许多优化选项和功能,以生成高质量的可执行文件。(最早名为GNU Compiler C,针对C)

    • MinGW(Minimalist GNU for Windows):MinGW是一个开源的软件开发工具集,旨在为Windows提供GNU开发环境。它包含了一组用于Windows的头文件和库文件,以及GCC(GNU Compiler Collection)编译器。MinGW使得开发者能够在Windows上开发和编译使用GNU工具链的应用程序,它提供了一种轻量级的方式在Windows环境下进行开发。

    • MSVC(Microsoft Visual C++):MSVC是微软公司开发的一款C++编译器和集成开发环境(IDE)。它是Windows平台上最常用的C++开发工具之一。MSVC提供了丰富的开发工具、调试功能和图形化界面,使得Windows开发变得更加便捷。

  • 区别与联系:MinGW和GCC都属于GNU项目的一部分,它们提供了在Windows环境下进行GNU开发的工具和编译器。GCC是跨平台的编译器集合,MinGW专注于在Windows上提供GNU工具链的支持

静、动态库

介绍

  • 什么是库

库是写好的,成熟的,可复用的代码。本质上来说,库是一种可执行代码的二进制形式,可以被OS载入内存执行。库有两种:静态库(.a、.lib)和动态库(.so、.dll)

  • 静态库

    • 概念:链接阶段,会将汇编生成的目标文件.o/.obj与依赖的静态库(.lib)一起打包到可执行文件中。一个静态库可以简单地看成一组目标文件(.o/.obj)的集合(即很多目标文件经过压缩打包形成的一个文件)
    • 特点
      • 程序对静态库的链接是在编译时完成的
      • 可执行程序在运行时与静态库再无瓜葛,移植方便(因为静态库在编译阶段就已被目标程序链接到一起生成可执行文件)
      • 通过静态链接的可执行程序体积通常较大,因为链接到一起时会占用较多空间
    • 静态库包含的内容
      • .a/.lib二进制文件:它是静态库实际的内容
      • .h头文件,它是静态库中函数、变量、宏定义的声明,以供引用并使用
  • 动态库

    • 为何需要动态库:动态库的出现是为了解决静态库的一些不足:

      • 空间浪费
      • 静态库更新带来的程序全量更新问题:如果静态库更新了,依赖于它的应用程序都需要重新编译、发布给用户(对于用户来说,可能是一个很小的改动,却导致整个程序重新下载,全量更新
        动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新
    • 特点

      • 动态库把库函数的链接载入推迟到程序运行的时期
      • 可以实现进程之间的资源共享(因此动态库也称为共享库)
      • 将程序更新变得简单
      • 可以真正做到链接载入完全由程序员在程序代码中控制(显示调用
    • 动态库包含的内容

      • .dll/.so文件:动态库的实际内容
      • .lib:动态库的入口文件
      • .h:动态库的声明头文件,以供程序引入和使用

gcc与g++和程序编译、链接

  • gcc与g++的联系与区别

    • gcc与g++都是GNU(组织)的一个编译器

    • gcc与g++都可以编译c代码与c++代码。但是:后缀为.c的,gcc把它当做C程序,而g++当做是C++程序;后缀为.cpp的,两者都会认为是C++程序

    • 编译可以用gcc/g++,而链接可以用g++或者gcc -lstdc++。因为gcc命令不能自动和C++程序使用的库联接(当然可以选择手动链接,使用命令如下),所以通常使用g++来完成联接。但在编译阶段,g++会自动调用gcc,二者等价

  • gcc编译过程

    • 预处理:处理#开头的语句,进行文本替换
    gcc -E hello.c -o hello.i
    
    • 编译:检查代码规范性和语法错误,将程序编译为汇编代码(语法检查、代码优化)
    gcc -S hello.i -o hello.s
    
    • 汇编:将汇编代码转换为二进制目标文件(机器可识别)
    gcc -c hello.s -o hello.o
    
    • 链接:将目标文件链接成最终可执行程序(处理目标文件间的依赖关系)
    gcc hello.o -o hello
    
  • 静态链接库

    • 创建静态链接库
# 将所有指定的源文件,都编译成相应的目标文件
g++ -c greeting.cpp  name.cpp
# greeting.cpp name.cpp greeting.o name.o greeting.h name.h
# 将生成的目标文件打包成静态链接库(可以将多个目标文件打成一个链接库)
# 静态链接库的不能随意起名,需遵循如下的命名规则:libxxx.a/libxxx.lib
ar rcs libmyfunction.a name.o greeting.o 
  • 链接静态链接库
# 将主文件编译为目标文件
g++ -c main.cpp
# 链接
g++ -static main.o libmyfunction.a
# -L注定路径,-l指定库名称,中间一般不加空格
# g++ -static main.o -L /home/wohu/cpp/src -lmyfunction
  • 动态链接库

    • 创建动态链接库
    # 1.直接使用源文件创建动态链接库
    # -shared 选项用于生成动态链接库
    # -fpic(还可写成 -fPIC)选项的功能是,令 GCC 编译器生成动态链接库(多个目标文件的压缩包)时,表示各目标文件中函数、类等功能模块的地址使用相对地址,而非绝对地址。这样,无论将来链接库被加载到内存的什么位置,都可以正常使用
    gcc -fpic -shared 源文件名... -o 动态链接库名# 2.先使用 gcc -c 指令将指定源文件编译为目标文件,再由目标文件生成动态链接库
    g++ -c -fPIC name.cpp greeting.cpp
    # 生成动态链接库
    g++ -shared greeting.o name.o -o libmyfunction.so
    
    • 链接动态链接库
    g++ main.cpp  libmyfunction.so -o main
    

Visual Studio创建和链接库

  • 静态库

    • 创建静态库

    使用VS新建项目 → 选择Win32控制台程序 → 选择应用程序设置 → 勾选静态库 → 编写静态库 → Build项目

    • 链接静态库

      • 方法一:引用的静态库是同一解决方案下的子工程

        • 项目 → 属性 → 项目依赖项:在欲引用静态库的项目中点选静态库依赖项

        • 右键欲引用静态库的项目 → 属性 → 配置属性 → C/C++ → 常规 → 在附加包含目录中键入静态库{name}.h头文件所在文件夹的路径

      • 方法二:右键欲引用静态库的项目 → 属性 → 配置属性 → 链接器 → 命令行:在其他选项中键入完整的静态库.lib路径

      • 方法三

        • 右键欲引用静态库的项目 → 属性 → 配置属性 → 链接器 → 常规 → 附加库目录中键入静态库所在的目录
        • 右键欲引用静态库的项目 → 属性 → 配置属性 → 链接器 → 输入 → 附加依赖项中输入静态库的名称{name}.lib

  • 动态库

    • 创建动态库

      • 创建项目

      • 项目结构

      pch是预编译头文件,通常将一些不怎么变动的头文件预先编译,加快工程编译速度

      dllmain.cpp是dll程序的主文件,其中DllMain函数是dll的入口点,每次这个dll被加载都会执行DllMain,然后根据运行时状态执行不同的命令

      // dllmain.cpp : 定义 DLL 应用程序的入口点。
      #include "pch.h"BOOL APIENTRY DllMain( HMODULE hModule,DWORD  ul_reason_for_call,LPVOID lpReserved)
      {switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:   //被程序加载时执行case DLL_THREAD_ATTACH:    //被线程加载时执行case DLL_THREAD_DETACH:    //被线程卸载时执行case DLL_PROCESS_DETACH:   //被程序卸载时执行break;}return TRUE;
      }
      
      • 编写自定义的DLL函数
      ```c
      // dllmain.cpp : 定义 DLL 应用程序的入口点。
      #include "pch.h"BOOL APIENTRY DllMain( HMODULE hModule,DWORD  ul_reason_for_call,LPVOID lpReserved)
      {switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:case DLL_PROCESS_DETACH:break;}return TRUE;
      }//编写函数 pch.h里面记得要添加include<iostream>
      void test1()
      {std::cout << "test1 is worked\n";
      }void test2()
      {std::cout << "test2 is worked\n";
      }
      
      • 将函数或对象暴露给外界:编写完函数或对象,外界还是无法执行的,这就像js里的模块化编程,需要将想要给外界使用的功能暴露(export)出来

        • 方法一:在函数名(对象)前加入暴露给外界的关键字

          • C
          __declspec(dllexport) void test1()
          {std::cout << "test1 is worked\n";
          }
          
          • C++:C++支持函数名重载,方法是将原有函数名粉碎,向函数名中添加关于参数的信息,就是说原来的函数名就是test1,但C++会粉碎成?test1@@YAXXZ这样的名字,这也就导致了我们暴露出去的函数名其实根本不是test1。为了解决这个问题:只需要在编写dll的时候在函数前告诉编译器,用C风格来暴露test函数,就不会被粉碎函数名了
          extern "C" __declspec(dllexport) void test1()
          {std::cout << "test1 is worked\n";
          }
          

          实际在C++中,我们更倾向于通过对象的方式将方法暴露给外界:

          class __declspec(dllexport) test
          {test() {};
          };
          
        • 方法二 :使用模块定义文件(.def):右键项目 → 添加项 → 新建项 → 选择模块定义文件创建

        自定义名称,并在文件中写入(.def文件中以";"作为注释符):

        LIBRARY {dll_project_name}   ;LIBRAY后面跟dll的项目名称
        EXPORTS    ;EXPORTS代表后面的都是要export出去的函数
        test2  ;一行一个函数名
        

      生成dll:在vs的顶部工具栏,依次点击生成 → 生成dll测试 → dll文件在项目文件夹的debug目录

    • 链接动态库

      • 方法一:同一解决方案

        • 右键项目 → 属性 → 通用属性 → 引用 → 添加新引用
        • 右键项目 → 属性 → 配置属性 → C/C++ → 常规 → 附加包含目录 键入动态库.h头文件的路径
      • 方法二:不要求同一解决方案

        • 右键项目 → 属性 → 配置属性 → 链接器 → 常规 → 附加依赖库目录 键入动态库所在目录

        • 右键项目 → 属性 → 配置属性 → 连接器 → 输入 → 附加目录 键入动态库对应的.lib文件(dll的入口)

动态库的显示调用

  • Linux:#include <dlfcn.h>

    • void * dlopen( const char * pathname, int mode ):函数以指定模式打开指定的动态连接库文件,并返回一个句柄给调用进程

    • void* dlsym(void* handle,const char* symbol):dlsym根据动态链接库操作句柄(pHandle)与符号(symbol),返回符号对应的地址。使用这个函数不但可以获取函数地址,也可以获取变量地址

    • int dlclose (void *handle):dlclose用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载

    • const char *dlerror(void):当动态链接库操作函数执行失败时,dlerror可以返回出错信息,返回值为NULL时表示操作函数执行成功

    • Windows

//main.cpp : 测试动态链接库
#include <iostream>// 函数指针:定义了一个类型名为fun的,指向空的函数指针
// https://blog.csdn.net/qq_35621436/article/details/106085752
typedef void(*func)(); 
int main(void)
{/**引入要加载的动态链接库HMODULE点进去看的话其实是HINSTANCE的一个别名,就是一个句柄如果获取到了,会返回这个动态库的句柄,否则返回NULL**/HMODULE dlltest = LoadLibraryW(L"{name}.dll");if (dlltest){/**获取函数名所在的地址,即函数指针获取到的地址默认是void类型,因此要自己定义一个函数指针,进行强制类型转换**/func test = (func)GetProcAddress(dlltest, "test");if (test) {test();}else {MessageBoxW(NULL, L"找不到test方法", L"ERROR", NULL);}}else {MessageBoxW(NULL,L"找不到dll",L"ERROR",NULL);}
}

Windows下显示动态库的加载/查找方式

  • 应用程序控制DLL的加载路径

    • 使用全路径加载
    • 使用DLL重定向
    • 使用manifest文件
  • 直接加载而不查找

    • 对于已经加载到内存中的同名DLL,系统使用已经加载的DLL,并且忽略待加载DLL的路径

    • 如果该DLL存在于某个Windows版本的已知DLL列表(unkown DLL)中,系统使用已知DLL的拷贝(包括已知DLL的依赖项)。已知DLL列表可以从如下注册表项看到:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs(注意:这里有个比较坑的地方,对于有依赖项的DLL(即使使用全路径指定DLL位置),系统查找其所依赖DLL的方法是按照实际的模块名称来的,因此如果加载的DLL不在系统查找顺序目录下,那么动态加载该DLL(LoadLibrary)会返回一个"找不到模块"的错误)

  • 系统标准DLL查找顺序

    • 系统设置了"安全DLL查找模式"(默认启用)

      • 应用程序所在目录

      • 系统目录。GetSystemDirectory返回的目录,通常是系统盘\Windows\System32

      • Windows目录。GetWindowsDirectory返回的目录,通常是系统盘\Windows

      • 当前目录。GetCurrentDirectory返回的目录

      • 环境变量PATH中所有目录

    • "安全DLL查找模式"被禁用

      • 应用程序所在目录

      • 当前目录。GetCurrentDirectory返回的目录

      • 系统目录。GetSystemDirectory返回的目录,通常是系统盘\Windows\System32

      • Windows目录。GetWindowsDirectory返回的目录,通常是系统盘\Windows

      • 环境变量PATH中所有目录

    • 如何控制"安全DLL查找模式"

      • 注册表HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode

      • 调用SetDllDirectory函数可以禁用"安全DLL查找模式",并修改DLL查找顺序

  • 修改系统DLL查找顺序

    • 使用LOAD_WITH_ALTERED_SEARCH_PATH标志调用LoadLibraryEx函数:这种方式调用LoadLibraryEx函数,需要设置lpFileName参数(绝对路径)。将"查找应用程序所在目录"修改为lpFileName指定的目录

    • 调用SetDllDirectory函数,修改后会将lpFileName目录放在查找系统目录前面

    • win8、windows server 2012还有其他方法,如:SetDefaultDllDirectoriesAddDllDirectory、[**RemoveDllDirectory

Make

Make - GNU Project - Free Software Foundation 跟我一起写Makefile — 跟我一起写Makefile 1.0 文档 (seisman.github.io)

介绍

Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作,提高开发效率。

  • Make 使用 Makefile 文件描述项目的构建过程,其中包含了源文件、目标文件以及编译和链接的命令等信息。Makefile 按照一定的规则解析,将源码和构建过程相互关联起来,执行具体的构建操作,生成目标文件或可执行文件

  • Make 工具的优势在于它可以识别哪些文件被修改了,只编译修改过的部分,以提高构建速度。此外,Make 工具还支持基于条件的编译,也就是预处理器(preprocessor)功能,可以生成不同的输出文件用于不同的平台或不同的运行环境

  • Make 工具具有很好的跨平台性,可以在 Unix/Linux、Windows、Mac 等多种操作系统上使用,并且可以与多种编程语言搭配使用,如 C、C++、Java 等

安装

  • Linux

  • Windows

    • Make for Windows (sourceforge.net):实际基于MinGW,推荐后者

    • MinGW - Minimalist GNU for Windows download | SourceForge.net

      • 下载安装,将Bin目录加入到系统环境变量中

      • 运行MinGW Installer,勾选所需的包,点击左上角的Installation,点击Apply Change

    在这里插入图片描述

    • mingw32-make.exe更名为make.exe方便使用

使用

CMake

CMake官网 CMake官方中文文档 CMake-Practice-zh-CN: CMake 实践 (github.com) 《CMake Best Practices》的非专业个人翻译 (github.com)

介绍

CMake是一种管理源代码构建的工具,被广泛用于C/C++项目

使用的工具链:cmake + make。通过cmake语法编写CMakeLists.txt文件,描述项目的构建属性和配置,并进行自动化的项目构建(可执行二进制文件、静/动态库)

  • 开源、高效率

  • 跨平台:在Linux/Unix平台,生成makefile;在Mac平台生成xcode;在Windows平台可以生成MSVC工程文件

  • 可扩展:可为cmake编写特定功能的模块,扩展cmake功能

安装

各大Linux发行版都集成了cmake,无需手动安装。如需安装可从官网上下载安装

使用

构建方式
  • 内部构建

  • 外部构建:推荐的方式,在源CMakeLists.txt所在的文件夹下新建build目录(或其他任意位置),在该位置下键入cmake ..cmake 源工程路径,此时区分出了PROJECT_SOURCE_DIR和PROJCT_BINARY_DIR

内部构建

源码和构建中间、目标在同一目录下,将hello.c和CMakeLists.txt放入同一目录

# 注意:命令和括号之间没有空格
CMAKE_MINIMUM_REQUIRED(VERSION 3.29)#[[指定项目信息:该声明隐式地定义了两个变量:项目名_BINARY_DIR和项目名_BINARY_DIR,
它们指向了当前CMakeLists.txt文件的路径,更好的替代方式是PROJECT_SOURCE_DIR和
PROJCT_BINARY_DIR,它们可以避免由项目名称更改造成的问题 
PROJECT (项目名VERSION 版本号DESCRIPTION "项目介绍"LANGUAGE C)
]]
PROJECT(hello)#[[打印信息:MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display")SEND_ERROR,产生错误,生成过程被跳过SATUS,输出前缀为—的信息FATAL_ERROR,立即终止所有 cmake 过程
]]
# 注意除IF语句中,变量的使用方式为:${}
MESSAGE(STATUS "This is Projct BINARY dir" ${项目名_BINARY_DIR})
MESSAGE(STATUS "This is Projct SOURCE dir" ${项目名_SOURCE_DIR})# 显示定义变量:SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
# 变量值可以用双引号标识,以应对存在空格的情况:SET(SRC_LIST “fu nc.c”)
SET(SRC_LIST hello.c)# 添加要构建可执行文件的源文件和输出文件,此时表示可执行文件为hello,源文件列表为SRC_LIST变量
# 多个参数可以使用空格或分号分隔:ADD_EXECUTABLE(hello main.c;func.c)
ADD_EXECUTABLE(hello ${SRC_LIST})
cmake .
外部构建

注意:CMake需要在任何子目录中建立一个CMakeLists.txt,如下将源文件放入src文件夹中,src文件夹中也需要创建一个

- root# 源文件- src# 源码hello.cCMakeLists.txt# 文档- doc# 版权说明、自述文件COPYRIGHT.txtREADME.txt# bat脚本,用于调用二进制可执行文件run.bat# 构建后的目标文件夹- bin# 构建目录- buildCMakeLists.txt
# /src/CMakeLists.txt
ADD_EXECUTABLE(hello hello.c)# 指定最终的目标二进制的位置(指最终生成的 hello 或者最终的共享库,不包含编译生成的中间文件)
# 内部编译下,PROJECT_BINARY_DIR是当前目录,外部编译下,PROJECT_BINARY_DIR是构建目录
# 与下面的ADD_SUBDIRECTORY用途相同(在哪里 ADD_EXECUTABLE 或 ADD_LIBRARY,就在哪改变路径)
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
# /CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.12...3.29)Project(hello)# 向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置
# ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
# EXCLUDE_FROM_ALL 参数的含义是将这个目录从编译过程中排除,比如,工程的 example,可能就需要工程构建完成后,再进入 example 目录单独进行构建
# 将 src 子目录加入工程,并指定编译输出(包含编译中间结果)路径为bin 目录。如果不进行 bin 目录的指定,那么编译结果(包括中间结果)都将存放在build/src 目录(这个目录跟原有的 src 目录对应)
ADD_SUBDIRECTORY(src bin)# 指定安装目录的变量:默认值/usr/local,注意Windows下转义字符
SET(CMAKE_INSTALL_PREFIX C:\\Users\\liyifan31\\Desktop\\des)# 安装命令:注意FILES、PROGRAMS、DIRECTORY是标识符
#[[# 目标文件安装:TARGETS# TARGET参数即是ADD_EXECUTABLE、ADD_LIBRARY定义的目标文件INSTALL(TARGETS targets...# 静态库、动态库、可执行二进制文件    [[ARCHIVE|LIBRARY|RUNTIME]# 定义安装路径,若以/开头则是绝对路径,CMAKE_INSTALL_PREFIX失效    [DESTINATION <dir>]       [PERMISSIONS permissions...][CONFIGURATIONS[Debug|Release|...]][COMPONENT <component>][OPTIONAL]] [...])# 普通文件安装INSTALL(FILES files... DESTINATION <dir># 定义文件权限,默认为:OWNER_WRITE,OWNER_READ,GROUP_READ,WORLD_READ,即644权限[PERMISSIONS permissions...][CONFIGURATIONS [Debug|Release|...]][COMPONENT <component>][RENAME <name>] [OPTIONAL])# 非目标文件的可执行程序安装(比如脚本之类),与FILES相比,默认为755权限INSTALL(PROGRAMS files... DESTINATION <dir>[PERMISSIONS permissions...][CONFIGURATIONS [Debug|Release|...]][COMPONENT <component>][RENAME <name>] [OPTIONAL])# 文件夹INSTALL(DIRECTORY dirs... DESTINATION <dir>[FILE_PERMISSIONS permissions...]# 所在 Source 目录的相对路径。注意:abc 和 abc/有很大的区别# 如果目录名不以/结尾,那么这个目录将被安装为目标路径下的 abc,# 如果目录名以/结尾,代表将这个目录中的内容安装到目标路径,但不包括这个目录本身[DIRECTORY_PERMISSIONS permissions...][USE_SOURCE_PERMISSIONS][CONFIGURATIONS [Debug|Release|...]][COMPONENT <component>]# 使用正则表达式进行过滤[[PATTERN <pattern> | REGEX <regex>][EXCLUDE] [PERMISSIONS permissions...]] [...])# e.g.# 将 icons 目录安装到 <prefix>/share/myproj,将 scripts/中的内容安装到<prefix>/share/myprojINSTALL(DIRECTORY icons scripts/ DESTINATION share/myproj# 不包含目录名为 CVS 的目录PATTERN "CVS" EXCLUDE# 对于 scripts/*文件指定权限为 OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READPATTERN "scripts/*"PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READGROUP_EXECUTE GROUP_READ)
]]INSTALL(DIRECTORY doc DESTINATION doc)
INSTALL(DIRECTORY data DESTINATION data)
INSTALL(TARGETS ${PROJECT_BINARY_DIR}\\bin\\Debug\\ DESTINATION bin)
cd build
cmake ..
cmake -DCMAKE_INSTALL_PREFIX=/usr
make
make install
构建使用静/动态库

目录结构

CMakeLists.txt
- libCMakeLists.txthello.chello.h
- build
# /lib/CMakeLists.txtSET(LIBHELLO_SRC hello.c)#[[ADD_LIBRARY(libname # SHARED,动态库 STATIC,静态库 MODULE,在使用 dyld 的系统有效,# 如果不支持 dyld,则被当作 SHARED 对待[SHARED|STATIC|MODULE]# 该库不会被默认构建,除非有其他的组件依赖或者手工构建[EXCLUDE_FROM_ALL]source1 source2 ... sourceN
)
]]
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})#[[# 设置输出属性SET_TARGET_PROPERTIES(target1 target2 ...PROPERTIES prop1 value1prop2 value2 ...)
]]
# 解决静态库和动态库重名/名称不一致的问题
# OUTPUT_NAME 设置输出名称。解决名称不一致问题
# CLEAN_DIRECT_OUTPUT 解决输出时,同名文件清除问题
# VERSION 库版本号
# SOVERSION api版本号
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1 VERSION 1.2 SOVERSION 1)
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello" CLEAN_DIRECT_OUTPUT 1 VERSION 1.2 SOVERSION 1)# 获取输出属性
GET_TARGET_PROPERTY(OUTPUT_VALUE hello_static OUTPUT_NAME)
MESSAGE(STATUS “This is the hello_static OUTPUT_NAME:” ${OUTPUT_VALUE})
# /CMakeLists.txt
Project(hello_lib)ADD_SUBDIRECTORY(lib lib)INSTALL(TARGETS hello hello_staticLIBRARY DESTINATION libARCHIVE DESTINATION lib
)
INSTALL(FILES hello.h DESTINATION /include)
  • 使用静态库和动态库

新建项目,在根路径下创建src文件夹和CMakeLists.txt,在src文件夹下创建sort_test.c和CMakeLists.txt,sort.c中引用刚才的库头文件

# /src/CMakeLists.txt#[[ # 向工程添加多个特定的头文件搜索路径,路径之间用空格分割# 通过 AFTER 或者 BEFORE 参数,控制追加到当前的头文件搜索路径的后面还是前面INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
]]
INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/extra/sort/include") 
# 通过 SET 这个 cmake 变量为 on,可以将添加的头文件搜索路径放在已有路径的前面
# SET(CMAKE_INCLUDE_DIRECTORIES_BEFORE ON)# 为target添加共享库,路径之间用空格分割
LINK_DIRECTORIES("${CMAKE_SOURCE_DIR}/extra/sort/lib/WIN32")
#[[链接
TARGET_LINK_LIBRARIES(target library1<debug | optimized> library2 ...)
]]
# 动态
TARGET_LINK_LIBRARIES(sort_test sort.dll)
# 静态
TARGET_LINK_LIBRARIES(sort_test sort.lib)#[[特殊的系统环境变量:注意不是CMake变量CMAKE_INCLUDE_PATH、CMAKE_LIBRARY_PATH# 修改这两个环境变量到系统共存的第三方库环境变量
]]
# 在环境变量中找sort.h的路径添加到myHeader变量中
FIND_PATH(myHeader sort.h)
# 指定路径
# FIND_PATH(myHeader NAMES sort.h PATHS /usr/include /usr/include/sort)
# 判空
IF(myHeader)
# 添加到include目录
INCLUDE_DIRECTORIES(${myHeader})
ENDIF(myHeader)# 注意搜索路径应该添加到可执行文件前
ADD_EXECUTABLE(sort_test sort_test.c)
/CmakeLists.txt
Project(sort_lib)
ADD_SUBDIRECTORY(src bin)
常用[系统]变量
  • 常用变量
常用变量含义说明
CMAKE_BINARY_DIR
PROJECT_BINARY_DIR
< < <projectname>_BINARY_DIR
工程编译发生的目录
CMAKE_SOURCE_DIR
PROJECT_SOURCE_DIR
_SOURCE_DIR
工程顶层目录
CMAKE_CURRENT_SOURCE_DIR当前处理的 CMakeLists.txt 所在的路径
CMAKE_CURRRENT_BINARY_DIRtarget 编译目录ADD_SUBDIRECTORY(src bin)可以更改这个变量的值
CMAKE_CURRENT_LIST_FILE调用这个变量的 CMakeLists.txt 的完整路径
CMAKE_CURRENT_LIST_LINE输出这个变量所在的行
CMAKE_MODULE_PATH定义自己的 cmake 模块所在的路径如果工程比较复杂,有可能会自己编写一些 cmake 模块,这些 cmake 模块是随你的工程发布的,为了让 cmake 在处理 CMakeLists.txt 时找到这些模块,你需要通过 SET 指令,将自己的 cmake 模块路径设置一下
EXECUTABLE_OUTPUT_PATH
LIBRARY_OUTPUT_PATH
重新定义最终结果的存放目录
PROJECT_NAME返回通过 PROJECT 指令定义的项目名称
  • 系统变量
# 调用系统的环境变量
$ENV{NAME}
# 设置环境变量
SET(ENV{变量名} 值)
环境变量含义说明
CMAKE_INCLUDE_CURRENT_DIR自动添加 CMAKE_CURRENT_BINARY_DIR CMAKE_CURRENT_SOURCE_DIR 到当前处理的 CMakeLists.txt相当于在每个 CMakeLists.txt 加入:
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}``${CMAKE_CURRENT_SOURCE_DIR})
CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE将工程提供的头文件目录始终至于系统头文件目录的前面当定义的头文件确实跟系统发生冲突时可以提供一些帮助
  • 系统信息
系统信息含义
CMAKE_MAJOR_VERSIONCMAKE 主版本号,比如 2.4.6 中的 2
CMAKE_MINOR_VERSIONCMAKE 次版本号,比如 2.4.6 中的 4
CMAKE_PATCH_VERSIONCMAKE 补丁等级,比如 2.4.6 中的 6
CMAKE_SYSTEM系统名称,比如 Linux-2.6.22
CMAKE_SYSTEM_NAME不包含版本的系统名,比如 Linux
CMAKE_SYSTEM_VERSION系统版本,比如 2.6.22
CMAKE_SYSTEM_PROCESSOR处理器名称,比如 i686
UNIX在所有的类 UNIX 平台为 TRUE,包括 OS X 和 cygwin
WIN32在所有的 win32 平台为 TRUE,包括 cygwin
  • 开关选项
开关选项含义
MAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS用来控制 IF ELSE 语句的书写方式
BUILD_SHARED_LIBS控制默认的库编译方式,默认为静态库
CMAKE_C_FLAGS设置C编译选项,也可以通过指令ADD_DEFINITIONS()添加
CMAKE_CXX_FLAGS设置C++编译选项,也可以通过指令ADD_DEFINITIONS()添加
常用指令
# 1.向C,C++编译器添加-D定义
# 如果你的代码中定义了#ifdef ENABLE_DEBUG #endif,这个代码块就会生效。
# 如果要添加其他的编译器开关,可以通过 CMAKE_C_FLAGS 变量和 CMAKE_CXX_FLAGS 变量设置
ADD_DEFINITIONS(-DENABLE_DEBUG -DABC)# 2.定义 target 依赖的其他 target,确保在编译本 target 之前,其他的 target 已经被构建
ADD_DEPENDENCIES(target-name depend-target1 depend-target2 ...)# 3.ADD_TEST(testname Exename arg1 arg2 ...)
# testname 是自定义的 test 名称,Exename 可以是构建的目标文件也可以是外部脚本等等,后面连接传递给可执行文件的参数。
# 如果没有在同一个 CMakeLists.txt 中打开ENABLE_TESTING()指令,任何 ADD_TEST 都是无效的
ADD_TEST(mytest ${PROJECT_BINARY_DIR}/bin/main)
# 4.控制 Makefile 是否构建 test 目标,涉及工程所有目录,一般情况这个指令放在工程的主CMakeLists.txt 中
ENABLE_ESTING() # 5.发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来自动构建源文件列表
AUX_SOURCE_DIRECTORY(dir VARIABLE)# 6.在指定的目录运行某个程序,通过 ARGS 添加参数,如果要获取输出和返回值
# 可通过 OUTPUT_VARIABLE 和 RETURN_VALUE 分别定义两个变量
# 该指令可以在 CMakeLists.txt 处理过程中支持任何命令,如根据系统情况去修改代码文件
# 在 CMakeLists.txt 处理过程中执行命令,并不会在生成的 Makefile 中执行
EXEC_PROGRAM(Executable [directory in which to run][ARGS <arguments to executable>][OUTPUT_VARIABLE <var>][RETURN_VALUE <var>])
# 在 src 目录执行 ls 命令,并把结果和返回值存下来
EXEC_PROGRAM(ls ARGS "*.c" OUTPUT_VARIABLE LS_OUTPUT RETURN_VALUELS_RVALUE)
IF(not LS_RVALUE)
MESSAGE(STATUS "ls result: " ${LS_OUTPUT})
ENDIF(not LS_RVALUE)# 7.文件操作指令
FILE(WRITE filename "message to write"... )
FILE(APPEND filename "message to write"... )
FILE(READ filename variable)
FILE(GLOB  variable [RELATIVE path] [globbing expressions]...)
FILE(GLOB_RECURSE variable [RELATIVE path][globbing expressions]...)
FILE(REMOVE [directory]...)
FILE(REMOVE_RECURSE [directory]...)
FILE(MAKE_DIRECTORY [directory]...)
FILE(RELATIVE_PATH variable directory file)
FILE(TO_CMAKE_PATH path result)
FILE(TO_NATIVE_PATH path result)# 8.载入 CMakeLists.txt 文件,也用于载入预定义的 cmake 模块
# OPTIONAL 参数的作用是文件不存在也不会产生错误
# 可以指定载入一个文件,如果定义的是一个模块,那么将在CMAKE_MODULE_PATH中
# 搜索这个模块并入。载入的内容将在处理到 INCLUDE 语句是直接执行。
INCLUDE(file1 [OPTIONAL])
INCLUDE(module [OPTIONAL])# 9.Find
# VAR 变量代表找到的文件全路径,包含文件名
FIND_FILE(<VAR> name1 path1 path2 ...) 
# VAR 变量表示找到的库全路径,包含库文件名
FIND_LIBRARY(<VAR> name1 path1 path2 ...)
# e.g.
FIND_LIBRARY(libX X11 /usr/lib)
IF(NOT libX)
MESSAGE(FATAL_ERROR “libX not found”)
ENDIF(NOT libX)
# VAR 变量代表包含这个文件的路径
FIND_PATH(<VAR> name1 path1 path2 ...) 
# VAR 变量代表包含这个程序的全路径
FIND_PROGRAM(<VAR> name1 path1 path2 ...) 
# 调用预定义在 CMAKE_MODULE_PATH 下的 Find<name>.cmake 模块,
# 也可以自己 定义Find<name>模块,通过SET(CMAKE_MODULE_PATH dir)将其放入工程的某个目录中供工程使用
FIND_PACKAGE(<name> [major.minor] [QUIET] [NO_MODULE][[REQUIRED|COMPONENTS] [componets...]])# 10.IF指令
IF(expression)# THEN section.COMMAND1(ARGS ...)COMMAND2(ARGS ...)...ELSE(expression)# ELSE section.COMMAND1(ARGS ...)COMMAND2(ARGS ...)...
ENDIF(expression)
# 凡是出现 IF 的地方一定要有对应的 ENDIF。出现 ELSEIF 的地方,ENDIF 是可选的
# IF(var),如果变量不是空,0,N, NO, OFF, FALSE, NOTFOUND 或<var>_NOTFOUND 时,表达式为真。
# IF(NOT var ),与上述条件相反
# IF(var1 AND var2),当两个变量都为真是为真。
# IF(var1 OR var2),当两个变量其中一个为真时为真。
# IF(COMMAND cmd),当给定的cmd确实是命令并可以调用是为真。
# IF(EXISTS dir)或者IF(EXISTS file),当目录名或者文件名存在时为真。
# IF(file1 IS_NEWER_THAN file2),当 file1 比 file2 新,或者 file1/file2 其 中有一个不存在时为真,文件名请使用完整路径。
# IF(IS_DIRECTORY dirname),当dirname是目录时,为真
# 正则匹配
IF(variable MATCHES regex)
IF(string MATCHES regex)
# IF("hello" MATCHES "ell")
MESSAGE("true")
ENDIF("hello" MATCHES "ell")# 数字比较表达式
IF(variable LESS number) 
IF(string LESS number) 
IF(variable GREATER number) 
IF(string GREATER number) 
IF(variable EQUAL number) 
IF(string EQUAL number))# 按照字母序的排列进行比较
IF(variable STRLESS string)
IF(string STRLESS string)
IF(variable STRGREATER string) 
IF(string STRGREATER string) 
IF(variable STREQUAL string)
IF(string STREQUAL string)# 判断变量定义:IF(DEFINED variable)
IF(WIN32)MESSAGE(STATUS “This is windows.”) # 作一些 Windows 相关的操作
ELSE(WIN32)MESSAGE(STATUS “This is not windows”) # 作一些非 Windows 相关的操作
ENDIF(WIN32)# 简化ELSE、ENDIF,使用开关语句
SET(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS ON)
# 简化为
IF(WIN32)
ELSE()
ENDIF()
# 配合ELSEIF使用
IF(WIN32)
#do something related to WIN32 
ELSEIF(UNIX)
#do something related to UNIX
ELSEIF(APPLE)
#do something related to APPLE
ENDIF(WIN32)# 11.WHILE
WHILE(condition)COMMAND1(ARGS ...)COMMAND2(ARGS ...)...
ENDWHILE(condition)# 12.FOREACH
# 列表
AUX_SOURCE_DIRECTORY(. SRC_LIST) 
FOREACH(F ${SRC_LIST})MESSAGE(${F})
ENDFOREACH(F)
# 范围
FOREACH(VAR RANGE 10) 
MESSAGE(${VAR}) 
ENDFOREACH(VAR)
# 范围和步进
FOREACH(A RANGE 5 15 3) MESSAGE(${A}) 
ENDFOREACH(A) 
CMake模块
  • 以往找库链接的过程:引入+链接
# /src/CMakeLists.txt
INCLUDE_DIRECTORIES(/usr/include)
TARGET_LINK_LIBRARIES(curltest curl)
  • 使用CMake模块

让工程找到 FindHELLO.cmake 模块(存放在工程中的 cmake 目录)

# /CMakeLists.txt
SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)

FIND_PACKAGE可以直接调用预定义的模块

# /src/CMakeLists.txt
# 对于系统预定义的 Find<name>.cmake 模块,都会定义以下几个变量:
# 判断模块是否被找到,如果没有找到,按照工程的需要关闭某些特性、给出提醒或者中止编译
# <name>_FOUND
# 如果 <name>_FOUND 为真,则将 <name>_INCLUDE_DIR 加入 INCLUDE_DIRECTORIES
# 将 <name>_LIBRARY 加入 TARGET_LINK_LIBRARIES 中 
# FIND_PACKAGE(<name> [major.minor] [QUIET] [NO_MODULE]
#     [[REQUIRED|COMPONENTS] [componets...]])
FIND_PACKAGE(CURL)
IF(CURL_FOUND)INCLUDE_DIRECTORIES(${CURL_INCLUDE_DIR})TARGET_LINK_LIBRARIES(curltest ${CURL_LIBRARY})
ELSE(CURL_FOUND)MESSAGE(FATAL_ERROR ”CURL library not found”)
ENDIF(CURL_FOUND)
  • 自定义CMake模块

编库时,项目结构

- CMakeLists.txt- srcCMakeLists.txtcurl.c- cmakeFindCURL.cmake- build
# /cmake/FindCURL.cmake
FIND_PATH(CURL_INCLUDE_DIR curl.h /usr/include/curl /usr/local/include/curl)
FIND_LIBRARY(CURL_LIBRARY NAMES curl PATH /usr/lib /usr/local/lib)
IF(CURL_INCLUDE_DIR AND CURL_LIBRARY)SET(CURL_FOUND TRUE)
ENDIF(CURL_INCLUDE_DIR AND CURL_LIBRARY)
IF(CURL_FOUND)# 根据FIND_PACKAGE中的QUIET参数判断IF(NOT CURL_FIND_QUIETLY)MESSAGE(STATUS "Found CURL: ${CURL_LIBRARY}")ENDIF(NOT CURL_FIND_QUIETLY)
ELSE(CURL_FOUND)# 根据FIND_PACKAGE中的REQUIRED参数判断IF(CURL_FIND_REQUIRED)MESSAGE(FATAL_ERROR "Could not find CURL library")ENDIF(CURL_FIND_REQUIRED)
ENDIF(CURL_FOUND)

Make与CMake的联系与区别

  • Make:用来处理编译顺序和依赖关系并构建最终项目的工具

  • CMake:本身不执行Make过程,而是根据不同平台的特性,生成对应平台的Makefie,这样我们每个工程只要写一个CMake文件即可了,其余的交给不同平台的处理器来产生不同的Makefile文件即可。而且CMake的语法也更加简洁

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/573532.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

题目:摆花(蓝桥OJ 0389)

问题描述&#xff1a; 题解&#xff1a; #include <bits/stdc.h> using namespace std; using ll long long; const int N 105; const ll p 1e6 7; ll a[N], dp[N][N];int main() {int n, m; cin >> n >> m;for(int i 1; i < n; i)cin >> a[i…

基于springboot+vue+Mysql的学生综合测评系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

【动手学深度学习】深入浅出深度学习之线性神经网络

目录 &#x1f31e;一、实验目的 &#x1f31e;二、实验准备 &#x1f31e;三、实验内容 &#x1f33c;1. 线性回归 &#x1f33b;1.1 矢量化加速 &#x1f33b;1.2 正态分布与平方损失 &#x1f33c;2. 线性回归的从零开始实现 &#x1f33b;2.1. 生成数据集 &#x…

深入理解Vue的生命周期机制

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

(4)(4.3) Kogger Sonar

文章目录 前言 1 推荐硬件 2 配置回声探测仪模块 3 连接ArduPilot硬件 4 参数说明 前言 KOGGER 声纳(KOGGER Sonar)是一款结构紧凑、成本低廉的水下回声测深仪模块&#xff0c;带有 UART 接口&#xff0c;电源电压为 5-14v。 1 推荐硬件 CP210x USB->UART 转换器和安装…

数字信号转模拟信号 DA变换 高精度PWM脉宽调制信号100Hz PWM/5KHz PWM /10KHz PWM转4-20mA/0-10V/1-5V/0-5V

主要特性: >>精度等级&#xff1a;0.1级。产品出厂前已检验校正&#xff0c;用户可以直接使用 >>辅助电源&#xff1a;8-32V 宽范围供电 >>PWM脉宽调制信号输入: 1Hz~10KHz >>输出标准信号&#xff1a;0-5V/0-10V/1-5V,0-10mA/0-20mA/4-20mA等&…

LLM应用:Prompt flow vs LangChain

背景 Prompt flow和LangChain都是LLM时代&#xff0c;为高效地构建LLM应用而生。 Prompt flow是Microsoft开源的&#xff0c;其诞生时&#xff0c;LangChain已经很有名气了。 所以作为后生的Prompt flow会为我们带来哪些新的东西呢&#xff1f; ​​​​​​​ Prompt flo…

Echart饼状图标注太密集导致文字被遮盖的解决办法

数据过于密集显示标注信息会出现遮盖情况 minAngle属性加avoidLabelOverlap属性搭配使用&#xff0c; 分析&#xff1a;minAngle属性为最小角度值&#xff0c;avoidLabelOverlap为是否启动标注压盖自动优化。 type: pie, minAngle: 5,    //最小的扇区角度&#…

aws使用记录

数据传输&#xff08;S3) 安装命令行 安装awscli: https://docs.aws.amazon.com/zh_cn/cli/latest/userguide/getting-started-install.html#getting-started-install-instructions 直到 aws configure list 可以运行 身份验证&#xff1a; 运行&#xff1a; aws config…

JavaScript高架(二)-V8引擎下

书归上回 ECS&#xff08;Execution Context Stack&#xff09; V8引擎为了执行代码, V8引擎内部会有一个执行上下文栈Execution Context Stack&#xff08;ESC函数调用栈)&#xff0c;当首次加载JS的时候就会创建一个Execution Context Stack(ECS)&#xff0c;它是用于执行代…

嵌入式和 Java 走哪条路?

最近看到一个物联网大三学生的疑问&#xff0c;原话如下&#xff1a; 本人普通本科物联网工程专业&#xff0c;开学大三&#xff0c;现在就很迷茫&#xff0c;不打算考研了&#xff0c;准备直接就业&#xff0c;平时一直在实验室参加飞思卡尔智能车比赛&#xff0c;本来是想走嵌…

Arcgis 导入经纬度坐标、导出经纬度坐标

目录 一、导入经纬度坐标 1、在excel中准备好经纬度坐标的数据表 2、将数据放入Acrgis的工作路径 3、在arcgis中添加数据 4、显示经纬度坐标点 5、导出为shp矢量文件 二、根据shp的经纬度坐标点导出成经纬度坐标 1、右键选择打开属性表 2、在属性表的菜单下拉栏里找到…