cmake--动态库之配置(第一篇)

记录最近使用cmake遇到和解决的问题

一、 关于cmake

cmake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。只是 CMake 的组态档取名为 CMakeLists.txt。Cmake 并不直接建构出最终的软件,而是产生标准的建构档(如 Unix 的 Makefile 或 Windows Visual C++ 的 projects/workspaces),然后再依一般的建构方式使用。

  上面是我随便抄的,因为我写不出这么言简意赅的定义。但是你让做mcu的人看了上述定义就大概率等于白说。大部分mcu的开发者都是基于商业的IDE(破解是违法的哦)来编译、链接甚至调试,虽然现在很多mcu也开始使用makefile无界面的形式来执行编译,但是依然很多人不知道Makefile。但是用ide,你依然需要配置头文件路径啊,编译等级啊,输出的路径以及名字啊,生成库(基本都是静态库)还是可执行文件啊,等等。整完这些配置好,找个🔨(build),一点,就开始编译,不成功,改改配置改改代码,直到编译通过,很少有人会关系IDE做了那些事。
  那IDE到底做了那些事呢,在交叉编译(暂且这么理解吧,毕竟都是在windows平台编译成mcu可执行的文件了)之前,IDE很重要的一件事就是根据你上面的一堆配置,生成Makefile文件或者Makefile文件,然后才是编译、链接、输出可执行文件。同样的cmake一个很重要的功能就是用那个来生成Makefile文件或者project文件,还有没有其他作用,当然还有了,比如让它生成shell脚本之类,当然这些都是高级用法,后面用到,还会继续展开的。

cmake怎么学

  一句话:及其容易入门,但是深入使用有难度。我fork了一个github仓库--cmake-examples,对着上面的示例,基本上把那天就从无知到敢写量产代码里了,看下下面的示例,后面你就能cmake,make,编译成功,是不是简直不要太简单。如果是已经工作了的小哥/小妹或者想快速上手的学生,看完01/02就已经入门了,03以后讲的的啥,我也没看过,但这并不影响我写代码、写CMakeLists.txt。因为据说,前面两章只讲了20%的知识,但是却可以覆盖80%的应用场景。我学完前两章后,就开始干活了,遇到了不会的,基本上就是百度了。所以建议先学前两章,剩下的可以在工作项目实操中慢慢锻炼,慢慢学习;如果是时间充足的学生,我觉得整个一起学完也是一个很不错的事情。

# Set the minimum version of CMake that can be used
# To find the cmake version run
# $ cmake --version
cmake_minimum_required(VERSION 3.5)

# Set the project name
project (hello_cmake)

# Add an executable
add_executable(hello_cmake main.cpp)

  一开始,我一个在linux开发的同事,推荐我用cmake来着,但是我还是喜欢在visual studio,如果是linux,我就vs的ssh远程调试。等后面自己整vsomeip时,不得不面对cmake,可真香,有需求的同事尽快用起来吧。另外,由于cmake版本更新很快,一些冷门用法也不要死记硬背了,能不用也尽量不用了,不然给别人用时,容易出现兼容性问题。

二、 这篇的主题

  这篇主题主要是在用cmake构建动态库时,遇到的一些问题和解决思路,基本都是导出google/baidu找的,写出来一方面是为了记录,一方面是做个问题集。

1. 关于-fPIC

```gnu手册
-fpic
Generate position-independent code (PIC) suitable for use in a shared library, if supported for the target machine. Such code accesses all constant addresses through a global offset table (GOT). The dynamic loader resolves the GOT entries when the program starts (the dynamic loader is not part of GCC; it is part of the operating system). If the GOT size for the linked executable exceeds a machine-specific maximum size, you get an error message from the linker indicating that -fpic does not work; in that case, recompile with -fPIC instead. (These maximums are 8k on the SPARC, 28k on AArch64 and 32k on the m68k and RS/6000. The x86 has no such limit.)

Position-independent code requires special support, and therefore works only on certain machines. For the x86, GCC supports PIC for System V but not for the Sun 386i. Code generated for the IBM RS/6000 is always position-independent.

When this flag is set, the macros pic and PIC are defined to 1.

-fPIC
If supported for the target machine, emit position-independent code, suitable for dynamic linking and avoiding any limit on the size of the global offset table. This option makes a difference on AArch64, m68k, PowerPC and SPARC.

Position-independent code requires special support, and therefore works only on certain machines.

When this flag is set, the macros pic and PIC are defined to 2.

-fpie
-fPIE
These options are similar to -fpic and -fPIC, but the generated position-independent code can be only linked into executables. Usually these options are used to compile code that will be linked using the -pie GCC option.

-fpie and -fPIE both define the macros pie and PIE. The macros have the value 1 for -fpie and 2 for -fPIE.

  -fPIC与-fpic都是在编译时加入的选项,用于生成位置无关的代码(Position-Independent-Code)。这两个选项都是可以使代码在加载到内存时使用相对地址,所有对固定地址的访问都通过全局偏移表(GOT)来实现。-fPIC和-fpic最大的区别在于是否对GOT的大小有限制。-fPIC对GOT表大小无限制,所以如果在不确定的情况下,使用-fPIC是更好的选择。但是如果你要动态库链接静态库时,是需要把这个取消的,不然编译就会出错了,原因是静态库本质上是一堆**.o**文件的集合,地址空间是绝对,也就是不是*position-independent code (PIC)*的,所以如果动态库加上-fPIC自然会导致编译出错,出错如下所示:

relocation R_X86_64_PC32 against symbol `xxxxxxx' can not be used when making a shared object; recompile with -fPIC
final link failed: Bad value

如果需要在加上-fPIC的话,就可以在*CMakeLists.txt*中加上这句就好了:``set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")``.
上面是关于-fPIC的相关知识以及cmake要如何配置,但是我遇到的问题是:我们库上的某个模块默认编译出来的是静态库,但是我的动态库并且即使我的*CMakeLists.txt*上没有加上-fPIC这个flag,在生成的Makefile里依然有这个选项,如下图所示,这导致我后面链接时就会出现前面提到的-fPIC错误了。![makefile -fPIC](/medias/cmake1/makefile_fPIC.png)
直到现在(2021.06.21)并没有找到原因在哪里。然后解决办法时,让库上的模块生成名字相同的动态库和成静态库,然后在*CMakeLists.txt*里指定使用动态库。
网上我看好多人说,如果使用``target_link_libraries()``不指定库的类型,优先链接动态库(也有人说优先静态库),不知道是否个版本有关还是网友误解了,从官网我也没找到对应的依据,在我的``cmake 3.10.2``中,不显示指定库的类型,默认是优先链接静态库的。

## 2. 如何指定cmake链接库的类型
如果要显示的制定链接静态库还是动态库,只需要加上后缀即可,如下所示:
```cmake
#```````linux ``````````
#Dynamic library 
target_link_libraries(xxx demo.so) 
target_link_libraries(xxx libdemo.so) 
target_link_libraries(xxx -ldemo.so) 

#staic library 
target_link_libraries(xxx demo.a) 
target_link_libraries(xxx libdemo.a) 
target_link_libraries(xxx -ldemo.a) 

#```````windows(msvc) ``````````
#Dynamic library 
target_link_libraries(xxx demo.dll) 
target_link_libraries(xxx libdemo.dll) 
target_link_libraries(xxx -ldemo.dll) 

#staic library 
target_link_libraries(xxx demo.lib) 
target_link_libraries(xxx libdemo.lib) 
target_link_libraries(xxx -ldemo.lib) 

3. 即生成静态库也生成动态库

  在第一节提到了,针对-fPIC的那个解决方案是即生成动态库也生成静态库。那如何才能即生成静态库又生成动态库呢,如果直接写上,注: add_library如果不加任何选项,默认生成静态库

add_library(${PROJECT_NAME} demo.c)
add_library(${PROJECT_NAME} SHARED demo.c)

是会出现如下错误的:

cmake error

因为官方文档已经描述的很清楚了,意思就是,使用 add_library( ...) 生成目标文件时不能使用相同 。:

add_library(<name> [STATIC | SHARED | MODULE]
            [EXCLUDE_FROM_ALL]
            [source1] [source2 ...])

Adds a library target called <name> to be built from the source files listed in the command invocation. (The source files can be omitted here if they are added later using target_sources().) The <name> corresponds to the logical target name and must be globally unique within a project. The actual file name of the library built is constructed based on conventions of the native platform (such as lib<name>.a or <name>.lib).

所以解决方案是:首先生成libdemo-static.a,然后使用 set_target_properties() 指令的 OUTPUT_NAME 参数,将libdemo-static.a 重命名为 libdemo.a,即可生成名字相同的动态库和成静态库。
具体就是如下所示(未测试):

cmake_minimum_required(VERSION 3.0)
project(demo)

add_library(demo SHARE demo.c)
add_library(demo-static tigerLogging.c)
SET_TARGET_PROPERTIES(demo-static PROPERTIES OUTPUT_NAME "demo")

4. 关于-Wl,--no-undefined

动态库和静态库--随便谈谈

  静态库和动态库的区别,我计划使用一篇文章(或者一个大章节)来记录一下我的理解,所以这里就不展开讲解了。
静态库:本质上和.o文件的压缩包没啥区别。
静态库:特别接近一个可执行文件,或者编译链接时就可以理解为一个可行文件。

  基于上面静态库和动态库的特性,软件模块编译成库时,我基本都会将软件模块编译成动态库而不是静态库,最后给别人用时,是使用静态库还是动态库,就具体情况具体分析了。这么做的好处是,可以保证你的软件模块最大程度的和别人解耦,因为有一个链接的过程。但是但是如果编译动态库时,没有加上-Wl,--no-undefined这个选项,就会导致,如果一个函数只有声明,没有定义,也能正确的生成动态库,但到最后生成可执行文件后,加载阶段借回报undefined reference to `xxxx`。为了让错误在编译动态库时即报出来,这样在团队合作开发就可以更顺畅了,就可以在加上-Wl,--no-undefined,这个选项的意思是:如果so里有未定义符号,则编译不通过。在CMakeLists.txt上加上如下语句:

set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-undefined")

三、 写在最后

  正如前面提到的,cmake我也是边用边学。但是其实这就要求你得有一定的其他linux基础,比如Makefile/gcc基础,这样就能知道编译时还有这些选项,在遇到问题时才会想起去搜在cmake要如何实现;当然如果基础知识足够牢固,比如对动态库定义理解非常到位,也能对第二章第4节提到东西,会有很高的技术敏感度,此刻就会模糊搜索,或者将自己的大致疑惑向别人描述,以获得帮助。所以对我来说,我基本不挑知识和方向,啥都看看,啥都学学,这样才能在遇到问题时,保持很高的敏感度,猜测出大致的解决方向/方案。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇