Cmake基础示例:如何编译【跨平台】的动态库和应用程序

系统 Linux
这篇文章,主要是把视频中的示例代码进行简化,只使用一个动态库和一个可执行文件,使用cmake构建工具,演示在 Windows 和 Linux 这两个平台下的构建过程。

[[441811]]

大家好,我是道哥,今天我为大伙儿解说的技术知识点是:【使用 cmake 来构建跨平台的动态库和应用程序】。

在很久之前,曾经在B站上传过几个小视频,介绍了在Windows和Linux这两个平台下,如何通过cmake和make这两个构建工具,来编译、链接动态库、静态库以及可执行程序。

视频中的示例代码是提前写好的,因此重点就放在构建(Build)环节了。主要是介绍了动态库与动态库之间、应用程序与动态库之间的引用等等。

对动态库、静态库比较熟悉的小伙伴,应该很容易就能理解其中的内容。但是对 C 语言不熟悉的朋友,看起来还是有一点点障碍。

这篇文章,主要是把视频中的示例代码进行简化,只使用一个动态库和一个可执行文件,使用cmake构建工具,演示在 Windows 和 Linux 这两个平台下的构建过程。

本文的内容很基础,算是使用 cmake 来构建跨平台程序的入门教程吧!

示例代码

首先看一下测试代码的全貌:

  1. mylib:只有一个源文件,编译输出一个动态库;
  2. myapp:也只有一个源文件,链接 mylib 动态库,编译输出一个可执行程序;

mylib

在mylib目录中,一共有3个文件:mylib.h, mylib.c 以及 CMakeLists.txt,内容分别如下:

  1. // mylib/mylib.h w文件 
  2.  
  3. #ifndef _MY_LIB_ 
  4. #define _MY_LIB_ 
  5.  
  6. #ifdef MY_LINUX 
  7.     #define MYLIB_API       extern 
  8. #else 
  9.     #ifdef MYLIB_EXPORT 
  10.         #define MYLIB_API   __declspec(dllexport) 
  11.     #else 
  12.         #define MYLIB_API   __declspec(dllimport) 
  13.     #endif 
  14. #endif 
  15.  
  16. MYLIB_API int my_add(int num1, int num2); 
  17. MYLIB_API int my_sub(int num1, int num2); 
  18.  
  19. #endif  // _MY_LIB_ 

以上这个代码,主要是用在Windows系统的动态导出库,在 Linux 系统中,不是必要的。

补充:在 windows 系统中,编译动态库时会生成 xxx.dll 和 xxx.lib。xxx.dll 中是真正的库文件指令,xxx.lib 中仅仅是符号表。

具体来说:在 Windows 系统中,当编译动态库的时候,打开(定义)宏 MYLIB_EXPORT,下面这个宏生效:

  1. #define MYLIB_API   __declspec(dllexport) 

这样的话,两个函数 my_add 和 my_sub 的符号才可能被导出到 mylib.lib 文件中。

当这个动态库被应用程序(myapp)使用的时候,myapp.c在 include mylib.h 的时,关闭宏 MYLIB_EXPORT,此时下面这个宏就生效:

  1. #define MYLIB_API   __declspec(dllimport) 

为了简化宏定义的复杂度,这里就不考虑静态库了。

看完了头文件,再来看看源文件mylib.c:

  1. // mylib/mylib.c 文件 
  2.  
  3. #include "mylib.h" 
  4.  
  5. int my_add(int num1, int num2) 
  6.     return (num1 + num2); 
  7.  
  8. int my_sub(int num1, int num2) 
  9.     return (num1 - num2); 

最后再来看一下mylib/CMakeLists.txt文件:

  1. // mylib/CMakeLists.txt 文件 
  2.  
  3. CMAKE_MINIMUM_REQUIRED(VERSION 3.5) 
  4. PROJECT(mylib VERSION 1.0.0) 
  5.  
  6. # 自定义宏,代码中可以使用  
  7. ADD_DEFINITIONS(-DMYLIB_EXPORT) 
  8.  
  9. # 头文件 
  10. INCLUDE_DIRECTORIES(./) 
  11.  
  12. # 源文件 
  13. FILE(GLOB MYLIB_SRCS "*.c"
  14.  
  15. # 编译目标 
  16. ADD_LIBRARY(${PROJECT_NAME} SHARED ${MYLIB_SRCS}) 

关于cmake的语法就不多说了,这里只用到了其中很少的一部分。

注意其中的一点:ADD_DEFINITIONS(-DMYLIB_EXPORT),因为这个CMakeLists.txt是用来编译动态库的,因此在Windows平台下,每一个导出符号的前面需要加上 __declspec(dllexport),因此需要打开宏定义:MYLIB_EXPORT。

myapp

应用程序的代码就更简单了,只有两个文件:myapp.c 和 CMakeLists.txt,内容如下:

  1. // myapp/myapp.c 文件 
  2.  
  3. #include <stdio.h> 
  4. #include <stdlib.h> 
  5.  
  6. #include "mylib.h" 
  7.  
  8. int main(int argc, char *argv[]) 
  9.     int ret1, ret2; 
  10.     int a = 5; 
  11.     int b = 2; 
  12.      
  13.     ret1 = my_add(a, b); 
  14.     ret2 = my_sub(a, b); 
  15.     printf("ret1 = %d \n", ret1); 
  16.     printf("ret2 = %d \n", ret2); 
  17.     getchar(); 
  18.     return 0; 

HelloWorld级别的代码,不需要多解释!CMakeLists.txt内容如下:

  1. // myapp/CMakeLists.txt 文件 
  2.  
  3. CMAKE_MINIMUM_REQUIRED(VERSION 3.5) 
  4. PROJECT(myapp VERSION 1.0.0) 
  5.  
  6. # 头文件路径 
  7. INCLUDE_DIRECTORIES(./include) 
  8.  
  9. # 库文件路径 
  10. LINK_DIRECTORIES(./lib) 
  11.  
  12. # 源文件 
  13. FILE(GLOB MYAPP_SRCS "*.c"
  14.  
  15. # 编译目标 
  16. ADD_EXECUTABLE(${PROJECT_NAME} ${MYAPP_SRCS}) 
  17.  
  18. # 依赖的动态库 
  19. TARGET_LINK_LIBRARIES(${PROJECT_NAME} mylib) 

最后一行 TARGET_LINK_LIBRARIES(${PROJECT_NAME} mylib) 说明要链接mylib这个动态库。

那么到哪个目录下去查找相应的头文件和库文件呢?

通过这两行来指定查找目录:

  1. # 头文件路径 
  2. INCLUDE_DIRECTORIES(./include) 
  3.  
  4. # 库文件路径 
  5. LINK_DIRECTORIES(./lib) 

这个两个目录暂时还不存在,待会编译的时候我们再手动创建。

可以让 mylib 在编译时的输出文件,自动拷贝到指定的目录。但是为了不把问题复杂化,某些操作步骤通过手动操作来完成,这样也能更清楚的理解其中的链接过程。

最后就剩下最外层的CMakeLists.txt文件了:

  1. CMAKE_MINIMUM_REQUIRED(VERSION 3.5) 
  2. PROJECT(cmake_demo VERSION 1.0.0) 
  3.  
  4. SET(CMAKE_C_STANDARD 99) 
  5.  
  6. # 自定义宏,代码中可以使用  
  7. if (CMAKE_HOST_UNIX) 
  8.     ADD_DEFINITIONS(-DMY_LINUX) 
  9. else () 
  10.     ADD_DEFINITIONS(-DMY_WINDOWS) 
  11. endif() 
  12.       
  13. ADD_SUBDIRECTORY(mylib) 
  14. ADD_SUBDIRECTORY(myapp) 

它所做的主要工作就是:根据不同的平台,定义相应的宏,并且添加了mylib和myapp这两个子文件夹。

Linux 下构建过程

cmake 配置

为了不污染源文件目录,在最外层目录下新建build目录,然后执行cmake指令:

  1. $ cd ~/tmp/cmake_demo/ 
  2. $ mkdir build 
  3. $ cd build/ 
  4. $ ls 
  5. $ cmake .. 

此时,在build目录下,产生如下文件:

  1. CMakeCache.txt  CMakeFiles  cmake_install.cmake  Makefile  myapp  mylib 

make 编译

我们可以分别进入mylib和myapp目录,执行make指令来单独编译,也可以直接在build目录下编译所有的目标。

现在就直接在build目录下编译所有目标:

  1. $ cd ~/tmp/cmake_demo/build 
  2. $ make 
  3. Scanning dependencies of target mylib 
  4. [ 25%] Building C object mylib/CMakeFiles/mylib.dir/mylib.c.o 
  5. [ 50%] Linking C shared library libmylib.so 
  6. [ 50%] Built target mylib 
  7. Scanning dependencies of target myapp 
  8. [ 75%] Building C object myapp/CMakeFiles/myapp.dir/myapp.c.o 
  9. ~/tmp/cmake_demo/myapp/myapp.c:4:19: fatal error: mylib.h: 没有那个文件或目录 
  10.  #include "mylib.h" 
  11.                    ^ 
  12. compilation terminated. 
  13. myapp/CMakeFiles/myapp.dir/build.make:62: recipe for target 'myapp/CMakeFiles/myapp.dir/myapp.c.o' failed 
  14. make[2]: *** [myapp/CMakeFiles/myapp.dir/myapp.c.o] Error 1 
  15. CMakeFiles/Makefile2:140: recipe for target 'myapp/CMakeFiles/myapp.dir/all' failed 
  16. make[1]: *** [myapp/CMakeFiles/myapp.dir/all] Error 2 
  17. Makefile:83: recipe for target 'all' failed 
  18. make: *** [all] Error 2 

从提示信息中看出:已经编译生成了 ./mylib/libmylib.so 文件,但是在编译可执行程序 myapp 时遇到了错误:找不到 mylib.h 文件!

在刚才介绍myapp/CMakeLists.txt文件时说到:应用程序查找头文件的目录是 myapp/include, 查找库文件的目录是 myapp/lib。

但是这2个目录以及相应的头文件、库文件都不存在!

因此我们需要手动创建,并且把头文件mylib.h和库文件libmylib.so拷贝进去,操作过程如下:

  1. $ cd ~/tmp/cmake_demo/myapp/ 
  2. $ mkdir  include lib 
  3. $ cp ~/tmp/cmake_demo/mylib/mylib.h ./include/ 
  4. $ cp ~/tmp/cmake_demo/build/mylib/libmylib.so ./lib/ 

注意:刚才编译生成的库文件libmylib.so是在build目录下。

准备好头文件和库文件之后,再次编译一下:

  1. $ cd ~/tmp/cmake_demo/build/ 
  2. $ make 
  3. [ 50%] Built target mylib 
  4. [ 75%] Building C object myapp/CMakeFiles/myapp.dir/myapp.c.o 
  5. [100%] Linking C executable myapp 
  6. [100%] Built target myapp 

此时,就在 build/myapp 目录下生成可执行文件myapp了。

测试、执行

  1. $ cd ~/tmp/cmake_demo/build/myapp 
  2. $ ./myapp 
  3. ret1 = 7  
  4. ret2 = 3 

完美!

由于我们是在build目录下编译的,编译过程中所有的输出和中间文件,都放在build目录下,一点都没有污染源文件。

Windows 下构建过程

把Linux系统中的build文件夹删除,然后把测试代码压缩,复制到Windows系统中继续测试。

在Windows下编译,一般就很少使用命令行了,大部分都使用VS或者VSCode来编译。

打开 VSCode,然后打开测试代码文件夹 cmake_demo:

因为需要使用cmake工具来构建,所以需要在VSCode安装 cmake 插件。(如何安装 VSCode 插件就不赘述了)

第一步: cmake 配置

按下键盘 ctrl + shift + p,在命令窗口中选择 Cmake: Configure,如果没看到这个选项,就手动输入前面的几个字符,然后就可以智能匹配到:

在第一次 Configure 的时候,会弹出下面的选项,来选择编译器:

我们这里选择 64 位的 amd64。

配置的结果输出在最下面窗口中的output标签中,如下所示:

这就表明cmake配置成功,正确的执行了每一个文件夹下的 CMakeLists.txt 文件。

这个时候,来看一下资源管理器中有啥变化:自动生成了 build 目录,其中的文件如下:

看来,流程与Linux系统中都是一样的,只不过这里是VSCode主动帮我们做了一些事情。

第二步: 编译

配置之后,下一步就是编译了。

按下 shift + F7,或者单击VSCode底部的 Build 图标:

弹出编译目标列表:

这里选择 ALL_BUILD,也就是编译所有的目标:mylib 和 myapp,输出如下:

来看一下编译的输出文件:

mylib.dll 就是编译得到的动态链接库,mylib.lib是导入符号。

myapp.exe 是编译得到的可执行程序。

第三步: 执行

我们先在命令行窗口中执行一下myapp.exe:

提示错误:找不到动态链接库!

手动把mylib.dll拷贝到myuapp.exe同一个目录下,然后再执行一次 myapp.exe:

完美!

但是,既然已经用VSCode来编译了,那就继续在VSCode中进行代码调试吧。

按下调试快捷键 F5,第一次会弹出调试器选择项:

选择 LLDB,然后弹出错误对话框:

因为我们没有提供相应的配置文件来告诉VSCode调试哪一个可执行程序。

单击[OK]之后,VSCode 会自动为我们生成 .vscode/launcher.json 文件,内容如下:

把其中的program项目,改成可执行程序的全路径:

  1. "program""F:/tmp/cmake_demo/build/myapp/Debug/myapp.exe" 

然后再次按下F5键,这回终于可以正确执行了:

此时,就可以在mylib.c或者myapp.c中设置断点,然后进行单步调试程序了:

本文转载自微信公众号「IOT物联网小镇」

 

责任编辑:姜华 来源: IOT物联网小镇
相关推荐

2021-05-07 08:00:19

应用程序框架

2021-04-08 11:10:07

C语言版本Cmake

2023-10-29 09:13:56

GolangGo

2015-01-14 09:41:28

跨平台移动应用Linux开发

2010-08-12 15:52:34

Flex应用程序

2023-02-10 14:54:20

编译工具cmake

2013-10-31 10:44:54

IDE工具

2015-01-06 13:42:45

跨平台开发APP工具

2020-09-23 14:33:01

Golang桌面开发GUI

2016-05-27 15:44:12

H5LeanCloudWex5

2021-05-21 07:59:40

应用程序设计动态库函数

2011-03-22 14:12:17

LAMP

2010-02-01 10:43:10

C++跨平台应用

2009-07-21 15:14:32

预编译应用程序ASP.NET

2022-12-22 08:01:09

Vue测试库测试

2009-07-01 13:54:41

Servlet和JSP

2023-10-30 10:34:20

Golang数据库

2011-05-31 13:34:22

应用开发iPad

2021-02-23 23:06:31

数据库Redis技术

2022-09-08 11:45:18

云计算多云IT
点赞
收藏

51CTO技术栈公众号