C程序编译、链接与项目构建
- 摘要
- C编译环境
- 静、动态库
- 介绍
- gcc与g++和程序编译、链接
- Visual Studio创建和链接库
- 动态库的显示调用
- 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);
}
}
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.c
CMakeLists.txt
# 文档
- doc
# 版权说明、自述文件
COPYRIGHT.txt
README.txt
# bat脚本,用于调用二进制可执行文件
run.bat
# 构建后的目标文件夹
- bin
# 构建目录
- build
CMakeLists.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/myproj
INSTALL(DIRECTORY icons scripts/ DESTINATION share/myproj
# 不包含目录名为 CVS 的目录
PATTERN "CVS" EXCLUDE
# 对于 scripts/*文件指定权限为 OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ
PATTERN "scripts/*"
PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ
GROUP_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
- lib
CMakeLists.txt
hello.c
hello.h
- build
# /lib/CMakeLists.txt
SET(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 value1
prop2 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_static
LIBRARY DESTINATION lib
ARCHIVE 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_DIR | target 编译目录 | 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_VERSION | CMAKE 主版本号,比如 2.4.6 中的 2 |
CMAKE_MINOR_VERSION | CMAKE 次版本号,比如 2.4.6 中的 4 |
CMAKE_PATCH_VERSION | CMAKE 补丁等级,比如 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
- src
CMakeLists.txt
curl.c
- cmake
FindCURL.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的语法也更加简洁