pkg-config 这个小工具一般来说需要自己下载,执行 sudo apt install pkg-config
就可以了!
1. 命令简介 pkg-config 用于返回安装库的元信息
大家应该都知道一般用第三方库的时候,就少不了要使用到第三方的头文件和库文件。我们在编译、链接的时候,必须要指定这些头文件和库文件的位置。对于一个比较大的第三方库,其头文件和库文件的数量是比较多的,如果我们一个个手动地写,那将是相当的麻烦的。因此,pkg-config就应运而生了。pkg-config能够把这些头文件和库文件的位置指出来,给编译器使用。
pkg-config 主要提供了下面几个功能:
检查库的版本号。 如果所需要的库的版本不满足要求,它会打印出错误信息,避免链接错误版本的库文件
获得编译预处理参数,如宏定义、头文件的位置
获得链接参数,如库及依赖的其他库的位置,文件名及其他一些链接参数
自动加入所依赖的其他库的设置
在大多数的系统中呢,pkg-config 会去 /usr/lib/pkgconfig,/usr/share/pkgconfig,/usr/local/lib/pkgconfig,/usr/local/share/pkgconfig
中寻找 .pc
后缀的元数据文件(metadata file)。而且不是所有的库安装好后都带有 .pc
文件的。如果 .pc
不在上述的任意一个目录中,那么可以通过往 PKG_CONFIG_PATH 环境变量中添加目录,例如:$ export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/opt/pkgconfig
这样 pkg-config 就会在 /opt/pkgconfig
目录下寻找 .pc
文件了
另外还需要注意的是,上述环境变量的设置只对当前的终端窗口有效。为了让其永久生效,我们可以将上述命令写入到 /etc/bash.bashrc
等文件中,以方便后续使用
2. 注意点 我在之前碰到过一个很奇怪的现象,那就是,通过 pkg-config 工具把一个源文件编译并且链接好了(和 so 文件链接)得到了可执行文件。当我执行这个文件的时候却告诉我有申明了却未定义的函数(方法)!这让我很纳闷,明明编译和链接都成功通过了,运行的时候怎么就报未定义的错误了呢?要是有这类错误不是在链接的时候就应该报了吗?我百思不得其解啊!
后来看到别人的博客上写的文章才发现 pkg-config 工作在编译时和链接时,它是不管运行时的!也就是说在运行程序的时候,如果需要动态加载共享库(so 文件),就要到系统已知的 so 库目录下去寻找。而如果你的共享库没有在系统已知的 so 库目录下,就会报未定义的错误
可以通过往 LD_LIBRARY_PATH 环境变量添加 so 库目录来解决这个问题
这里我们列出pkg-config与LD_LIBRARY_PATH的主要工作阶段:
pkg-config: 编译时、 链接时
LD_LIBRARY_PATH: 链接时、 运行时
总结:
库文件在链接(静态库和共享库)和运行(仅限于使用共享库的程序)时被使用,其搜索路径是在系统中进行设置的。一般 Linux 系统把 /lib
和 /usr/lib
这两个目录作为默认的库搜索路径,所以使用这两个目录中的库时不需要进行设置搜索路径即可直接使用。对于处于默认库搜索路径之外的库,需要将库的位置添加到库的搜索路径之中。设置库文件的搜索路径有下列两种方式,可任选其中一种使用:
在环境变量 LD_LIBRARY_PATH 中指明库的搜索路径
在 /etc/ld.so.conf 文件
中添加库的搜索路径
将自己可能存放库文件的路径都加入到 /etc/ld.so.conf 目录
中是明智的选择。添加方法也及其简单,将库文件的绝对路径直接写进去就OK了,一行一个。比如:
1 2 3 /usr/X11R6/lib /usr/local/lib /opt/lib
也可以建一个 /etc/ld.so.conf.d 目录
,再在这个目录下建 \*.config 文件
并添加路径,然后在 /etc/ld.so.conf 目录
中添加这一行: include /etc/ld.so.conf.d/\*.config
(这样条理会清楚一点)
需要注意的是:第二种搜索路径的设置方式对于程序链接时的库(包括共享库和静态库)的定位已经足够了。但是对于使用了共享库的程序的执行还是不够的,这是因为为了加快程序执行时对共享库的定位速度,避免使用搜索路径查找共享库的低效率,所以是直接读取库列表文件 /etc/ld.so.cache
的方式从中进行搜索。/etc/ld.so.cache
是一个非文本的数据文件,不能直接编辑,它是根据 /etc/ld.so.conf
中设置的搜索路径由 /sbin/ldconfig
命令将这些搜索路径下的共享库文件集中在一起而生成的(ldconfig 命令要以 root 权限执行)。因此为了保证程序执行时对库的定位,在 /etc/ld.so.conf
中进行了库搜索路径的设置之后,还必须要运行 /sbin/ldconfig
命令更新 /etc/ld.so.cache
文件之后才可以。
ldconfig ,简单的说,它的作用就是将 /etc/ld.so.conf
列出的路径下的库文件缓存到 /etc/ld.so.cache
以供使用。因此当安装完一些库文件(例如刚安装好 glib),或者修改 ld.so.conf
增加新的库路径之后,需要运行一下 /sbin/ldconfig
使所有的库文件都被缓存到 ld.so.cache
中。如果没有这样做,即使库文件明明就在 /usr/lib
下的,也是不会被使用的,结果在编译过程中报错。
3. pc 文件书写规范 这里我们首先来看一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 # Package Information for pkg-config prefix=/usr/local exec_prefix=${prefix} libdir=${exec_prefix}/lib includedir=${prefix}/include/opencv4 Name: OpenCV Description: Open Source Computer Vision Library Version: 4.5.2 Libs: -L${exec_prefix}/lib -lopencv_stitching -lopencv_aruco -lopencv_bgsegm -lopencv_bioinspired -lopencv_ccalib -lopencv_dnn_objdetect -lopencv_dnn_superres -lopencv_dpm -lopencv_face -lopencv_fuzzy -lopencv_hdf -lopencv_hfs -lopencv_img_hash -lopencv_intensity_transform -lopencv_line_descriptor -lopencv_mcc -lopencv_quality -lopencv_rapid -lopencv_reg -lopencv_rgbd -lopencv_saliency -lopencv_stereo -lopencv_structured_light -lopencv_phase_unwrapping -lopencv_superres -lopencv_optflow -lopencv_surface_matching -lopencv_tracking -lopencv_highgui -lopencv_datasets -lopencv_text -lopencv_plot -lopencv_videostab -lopencv_videoio -lopencv_wechat_qrcode -lopencv_xfeatures2d -lopencv_shape -lopencv_ml -lopencv_ximgproc -lopencv_video -lopencv_dnn -lopencv_xobjdetect -lopencv_objdetect -lopencv_calib3d -lopencv_imgcodecs -lopencv_features2d -lopencv_flann -lopencv_xphoto -lopencv_photo -lopencv_imgproc -lopencv_core Libs.private: -ldl -lm -lpthread -lrt Cflags: -I${includedir}
这是 opencv4.5 库的一个真实的例子。下面我们简单描述一下.pc文件中的用到的一些关键词:
Name: 一个针对library或package的便于人阅读的名称。这个名称可以是任意的,它并不会影响到pkg-config的使用,pkg-config是采用pc文件名的方式来工作的。
Description: 对package的简短描述
URL: 人们可以通过该URL地址来获取package的更多信息或者package的下载地址
Version: 指定package版本号的字符串
Requires: 本库所依赖的其他库文件。所依赖的库文件的版本号可以通过使用如下比较操作符指定:=,<,>,<=,>=
Requires.private: 本库所依赖的一些私有库文件,但是这些私有库文件并不需要暴露给应用程序。这些私有库文件的版本指定方式与Requires中描述的类似。
Conflicts: 是一个可选字段,其主要用于描述与本package所冲突的其他package。版本号的描述也与Requires中的描述类似。本字段也可以取值为同一个package的多个不同版本实例。例如: Conflicts: bar < 1.2.3, bar >= 1.3.0
Cflags: 编译器编译本package时所指定的编译选项,和其他并不支持pkg-config的library的一些编译选项值。假如所需要的library支持pkg-config,则它们应该被添加到Requires或者Requires.private中
Libs: 链接本库时所需要的一些链接选项,和其他一些并不支持pkg-config的library的链接选项值。与Cflags类似
Libs.private: 本库所需要的一些私有库的链接选项。
4. 示例 我们给出一个用 pkg-config 工具协助编译的程序例子(rgb24_jpg.c):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #include <stdio.h> #include <stddef.h> #include <fcntl.h> #include "jpeglib.h" #include <stdlib.h> #define JPEG_QUALITY 100 int save_rgb_to_jpg (char *soureceData, int imgWidth, int imgHeight, char * fileName) { int depth = 3 ; struct jpeg_compress_struct cinfo ; struct jpeg_error_mgr jerr ; FILE *outfile; JSAMPROW row_pointer[1 ]; int row_stride = imgWidth; cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); if ((outfile = fopen(fileName, "wb" )) == NULL ) { fprintf (stderr , "can't open %s\n" , fileName); return -1 ; } jpeg_stdio_dest(&cinfo, outfile); cinfo.image_width = imgWidth; cinfo.image_height = imgHeight; cinfo.input_components = depth; cinfo.in_color_space = JCS_RGB; jpeg_set_defaults(&cinfo); jpeg_set_quality(&cinfo, JPEG_QUALITY, TRUE ); jpeg_start_compress(&cinfo, TRUE); row_stride = imgWidth; while (cinfo.next_scanline < cinfo.image_height) { row_pointer[0 ] = (unsigned char *)& soureceData[cinfo.next_scanline * row_stride*depth]; (void ) jpeg_write_scanlines(&cinfo, row_pointer, 1 ); } jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); fclose(outfile); return 0 ; } int main (int argc, char **argv) { if (argc < 5 ){ printf ("Usage: %s rgbpic imgWidth imgHeight jpgpic\n" , argv[0 ]); exit (0 ); } int imgWidth = atoi(argv[2 ]); int imgHeight = atoi(argv[3 ]); int depth = 3 ; char buf[imgHeight * imgWidth * depth]; int fd = open(argv[1 ], O_RDONLY); read(fd, buf, sizeof buf); save_rgb_to_jpg(buf, atoi(argv[2 ]), atoi(argv[3 ]), argv[4 ]); return 0 ; }
执行如下命令编译程序:
1 $ gcc rgb24_jpg.c -o rgb24_jpg `pkg-config --cflags --libs opencv4`
就可以看到程序编译好了
5. Linux下链接库的路径顺序 5.1 运行时链接库的搜索顺序 Linux程序在运行时对动态链接库的搜索顺序如下:
1) 在编译目标代码时所传递的动态库搜索路径(注意,这里指的是通过 -Wl,rpath=<path1>:<path2>
或 -R
选项传递的运行时动态库搜索路径,而不是通过 -L
选项传递的)
例如:
1 2 3 4 $ gcc -Wl,-rpath,/home/arc/test ,-rpath,/lib/,-rpath,/usr/lib/,-rpath,/usr/local /lib test.c 或者 $ gcc -Wl,-rpath=/home/arc/test :/lib/:/usr/lib/:/usr/local /lib test.c
2) 环境变量 LD_LIBRARY_PATH 指定的动态库搜索路径
3) 配置文件 /etc/ld.so.conf 中所指定的动态库搜索路径(更改 /etc/ld.so.conf 之后,一定要执行命令 ldconfig,该命令会将 /etc/ld.so.conf 文件中所有路径下的库载入内存)
4) 默认的动态库搜索路径 /lib
5) 默认的动态库搜索路径 /usr/lib
5.2 编译时与运行时动态库查找的比较 下面是对编译时库的查找与运行时库的查找做一个简单的比较:
1) 编译时查找的是静态库或动态库, 而运行时,查找的是动态库
2) 编译时可以用 -L
指定查找路径,或者用环境变量 LIBRARY_PATH , 而运行时可以用 -Wl,rpath
或者 -R
选项,或者修改 /etc/ld.so.conf ,或者设置环境变量 LD_LIBRARY_PATH
3) 编译时用的链接器是 ld,而运行时用的链接器是 /lib/ld-linux.so.2
4) 编译时与运行时都会查找默认路径 /lib 、*/usr/lib*
5) 编译时还有一个默认路径 /usr/local/lib ,而运行时不会默认查找该路径
5.3 补充:gcc使用-Wl,-rpath 1) -Wl,-rpath
加上 -Wl,-rpath
选项的作用就是指定程序运行时的库搜索目录,是一个链接选项,生效于设置的环境变量之前(LD_LIBRARY_PATH)。下面我们通过一个例子来说明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int add (int i, int j) ; #include "add.h" int add (int i, int j) { return i + j; } #include <stdio.h> #include <stdlib.h> #include "add.h" int main (int argc, char *argv[]) { printf ("1 + 2 = %d\n" , add(1 , 2 )); return 0 ; }
add.h 和 add.c 用于生成一个 so 库,实现了一个简单的加法,main.c 中引用共享库计算 1 + 2:
1 2 3 4 $ gcc add.c -fPIC -shared -o libadd.so $ gcc main.o -L. -ladd -o app
编译好后运行依赖库:
1 2 3 4 5 6 7 8 $ ldd app linux-vdso.so.1 (0x00007ffeb23ab000) libadd.so => not found libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007febb7dd0000) /lib64/ld-linux-x86-64.so.2 (0x00007febb83d0000 $ ./app ./app: error while loading shared libraries: libadd.so: cannot open shared object file: No such file or directory
可以看到, libadd.so 这个库没有找到,程序也无法运行,要运行它必须要把当前目录添加到环境变量或者搜索路径中去。但是如果在链接时加上 -Wl,rpath
选项之后:
1 2 3 4 5 6 7 8 9 $ gcc -c -o main.o main.c $ gcc -Wl,-rpath=`pwd ` main.o -L. -ladd -o app $ ldd app linux-vdso.so.1 (0x00007fff8f4e3000) libadd.so => /data/code/c/1-sys/solib/libadd.so (0x00007faef8428000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007faef8030000) /lib64/ld-linux-x86-64.so.2 (0x00007faef8838000) $ ./app 1 + 2 = 3
依赖库的查找路径就找到了,程序能正常运行。
下面我们再来看一下生成的可执行文件app,执行如下命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 $ readelf -d app Dynamic section at offset 0xe08 contains 26 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [libadd.so] 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] 0x000000000000000f (RPATH) Library rpath: [/root/test ] 0x000000000000000c (INIT) 0x400578 0x000000000000000d (FINI) 0x400784 0x0000000000000019 (INIT_ARRAY) 0x600df0 0x000000000000001b (INIT_ARRAYSZ) 8 (bytes) 0x000000000000001a (FINI_ARRAY) 0x600df8 0x000000000000001c (FINI_ARRAYSZ) 8 (bytes) 0x000000006ffffef5 (GNU_HASH) 0x400298 0x0000000000000005 (STRTAB) 0x400408 0x0000000000000006 (SYMTAB) 0x4002d0 0x000000000000000a (STRSZ) 189 (bytes) 0x000000000000000b (SYMENT) 24 (bytes) 0x0000000000000015 (DEBUG) 0x0 0x0000000000000003 (PLTGOT) 0x601000 0x0000000000000002 (PLTRELSZ) 96 (bytes) 0x0000000000000014 (PLTREL) RELA 0x0000000000000017 (JMPREL) 0x400518 0x0000000000000007 (RELA) 0x400500 0x0000000000000008 (RELASZ) 24 (bytes) 0x0000000000000009 (RELAENT) 24 (bytes) 0x000000006ffffffe (VERNEED) 0x4004e0 0x000000006fffffff (VERNEEDNUM) 1 0x000000006ffffff0 (VERSYM) 0x4004c6 0x0000000000000000 (NULL) 0x0
可以看到是在编译后的程序中包含了库的搜索路径。
6. 参考
pkg-config官网
ldconfig命令
gcc使用-Wl,-rpath解决so库版本冲突
Linux中pkg-config的使用