cmake 基礎示例:如何編譯【跨平臺】的動態庫和應用程序
作 者:道哥,10 + 年嵌入式開發老兵,專注於:C/C++、嵌入式、Linux。
目錄
-
示例代碼
-
mylib
-
myapp
-
Linux 下構建過程
-
cmake 配置
-
make 編譯
-
編譯、執行
-
Windows 下構建過程
-
cmake cofigure
-
build
-
調試
別人的經驗,我們的階梯!
大家好,我是道哥,今天我爲大夥兒解說的技術知識點是:【使用 cmake 來構建跨平臺的動態庫和應用程序】。
在很久之前,曾經在 B 站上傳過幾個小視頻,介紹了在Windows
和Linux
這兩個平臺下,如何通過cmake
和make
這兩個構建工具,來編譯、鏈接動態庫、靜態庫以及可執行程序。
視頻中的示例代碼是提前寫好的,因此重點就放在構建 (Build) 環節了。主要是介紹了動態庫與動態庫之間、應用程序與動態庫之間的引用等等。
對動態庫、靜態庫比較熟悉的小夥伴,應該很容易就能理解其中的內容。但是對 C 語言不熟悉的朋友,看起來還是有一點點障礙。
這篇文章,主要是把視頻中的示例代碼進行簡化,只使用一個動態庫和一個可執行文件,使用cmake
構建工具,演示在 Windows 和 Linux 這兩個平臺下的構建過程。
本文的內容很基礎,算是使用 cmake 來構建跨平臺程序的入門教程吧!
示例代碼
首先看一下測試代碼的全貌:
mylib:只有一個源文件,編譯輸出一個動態庫;
myapp:也只有一個源文件,鏈接 mylib 動態庫,編譯輸出一個可執行程序;
mylib
在mylib
目錄中,一共有3
個文件:mylib.h, mylib.c 以及 CMakeLists.txt,內容分別如下:
// mylib/mylib.h w文件
#ifndef _MY_LIB_
#define _MY_LIB_
#ifdef MY_LINUX
#define MYLIB_API extern
#else
#ifdef MYLIB_EXPORT
#define MYLIB_API __declspec(dllexport)
#else
#define MYLIB_API __declspec(dllimport)
#endif
#endif
MYLIB_API int my_add(int num1, int num2);
MYLIB_API int my_sub(int num1, int num2);
#endif // _MY_LIB_
以上這個代碼,主要是用在Windows
系統的動態導出庫,在 Linux 系統中,不是必要的。
補充:在 windows 系統中,編譯動態庫時會生成 xxx.dll 和 xxx.lib。xxx.dll 中是真正的庫文件指令,xxx.lib 中僅僅是符號表。
具體來說:在 Windows
系統中,當編譯動態庫的時候,打開 (定義) 宏 MYLIB_EXPORT
,下面這個宏生效:
#define MYLIB_API __declspec(dllexport)
這樣的話,兩個函數 my_add
和 my_sub
的符號纔可能被導出到 mylib.lib 文件中。
當這個動態庫被應用程序 (myapp
) 使用的時候,myapp.c
在 include mylib.h
的時,關閉宏 MYLIB_EXPORT
,此時下面這個宏就生效:
#define MYLIB_API __declspec(dllimport)
爲了簡化宏定義的複雜度,這裏就不考慮靜態庫了。
看完了頭文件,再來看看源文件mylib.c
:
// mylib/mylib.c 文件
#include "mylib.h"
int my_add(int num1, int num2)
{
return (num1 + num2);
}
int my_sub(int num1, int num2)
{
return (num1 - num2);
}
最後再來看一下mylib/CMakeLists.txt
文件:
// mylib/CMakeLists.txt 文件
CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
PROJECT(mylib VERSION 1.0.0)
# 自定義宏,代碼中可以使用
ADD_DEFINITIONS(-DMYLIB_EXPORT)
# 頭文件
INCLUDE_DIRECTORIES(./)
# 源文件
FILE(GLOB MYLIB_SRCS "*.c")
# 編譯目標
ADD_LIBRARY(${PROJECT_NAME} SHARED ${MYLIB_SRCS})
關於cmake
的語法就不多說了,這裏只用到了其中很少的一部分。
注意其中的一點:ADD_DEFINITIONS(-DMYLIB_EXPORT)
,因爲這個CMakeLists.txt
是用來編譯動態庫的,因此在Windows
平臺下,每一個導出符號的前面需要加上 __declspec(dllexport)
,因此需要打開宏定義:MYLIB_EXPORT。
myapp
應用程序的代碼就更簡單了,只有兩個文件:myapp.c 和 CMakeLists.txt,內容如下:
// myapp/myapp.c 文件
#include <stdio.h>
#include <stdlib.h>
#include "mylib.h"
int main(int argc, char *argv[])
{
int ret1, ret2;
int a = 5;
int b = 2;
ret1 = my_add(a, b);
ret2 = my_sub(a, b);
printf("ret1 = %d \n", ret1);
printf("ret2 = %d \n", ret2);
getchar();
return 0;
}
HelloWorld
級別的代碼,不需要多解釋!CMakeLists.txt
內容如下:
// myapp/CMakeLists.txt 文件
CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
PROJECT(myapp VERSION 1.0.0)
# 頭文件路徑
INCLUDE_DIRECTORIES(./include)
# 庫文件路徑
LINK_DIRECTORIES(./lib)
# 源文件
FILE(GLOB MYAPP_SRCS "*.c")
# 編譯目標
ADD_EXECUTABLE(${PROJECT_NAME} ${MYAPP_SRCS})
# 依賴的動態庫
TARGET_LINK_LIBRARIES(${PROJECT_NAME} mylib)
最後一行 TARGET_LINK_LIBRARIES(${PROJECT_NAME} mylib)
說明要鏈接mylib
這個動態庫。
那麼到哪個目錄下去查找相應的頭文件和庫文件呢?
通過這兩行來指定查找目錄:
# 頭文件路徑
INCLUDE_DIRECTORIES(./include)
# 庫文件路徑
LINK_DIRECTORIES(./lib)
這個兩個目錄暫時還不存在,待會編譯的時候我們再手動創建。
可以讓 mylib 在編譯時的輸出文件,自動拷貝到指定的目錄。但是爲了不把問題複雜化,某些操作步驟通過手動操作來完成,這樣也能更清楚的理解其中的鏈接過程。
最後就剩下最外層的CMakeLists.txt
文件了:
CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
PROJECT(cmake_demo VERSION 1.0.0)
SET(CMAKE_C_STANDARD 99)
# 自定義宏,代碼中可以使用
if (CMAKE_HOST_UNIX)
ADD_DEFINITIONS(-DMY_LINUX)
else ()
ADD_DEFINITIONS(-DMY_WINDOWS)
endif()
ADD_SUBDIRECTORY(mylib)
ADD_SUBDIRECTORY(myapp)
它所做的主要工作就是:根據不同的平臺,定義相應的宏,並且添加了mylib
和myapp
這兩個子文件夾。
Linux 下構建過程
cmake 配置
爲了不污染源文件目錄,在最外層目錄下新建build
目錄,然後執行cmake
指令:
$ cd ~/tmp/cmake_demo/
$ mkdir build
$ cd build/
$ ls
$ cmake ..
此時,在build
目錄下,產生如下文件:
CMakeCache.txt CMakeFiles cmake_install.cmake Makefile myapp mylib
make 編譯
我們可以分別進入mylib
和myapp
目錄,執行make
指令來單獨編譯,也可以直接在build
目錄下編譯所有的目標。
現在就直接在build
目錄下編譯所有目標:
$ cd ~/tmp/cmake_demo/build
$ make
Scanning dependencies of target mylib
[ 25%] Building C object mylib/CMakeFiles/mylib.dir/mylib.c.o
[ 50%] Linking C shared library libmylib.so
[ 50%] Built target mylib
Scanning dependencies of target myapp
[ 75%] Building C object myapp/CMakeFiles/myapp.dir/myapp.c.o
~/tmp/cmake_demo/myapp/myapp.c:4:19: fatal error: mylib.h: 沒有那個文件或目錄
#include "mylib.h"
^
compilation terminated.
myapp/CMakeFiles/myapp.dir/build.make:62: recipe for target 'myapp/CMakeFiles/myapp.dir/myapp.c.o' failed
make[2]: *** [myapp/CMakeFiles/myapp.dir/myapp.c.o] Error 1
CMakeFiles/Makefile2:140: recipe for target 'myapp/CMakeFiles/myapp.dir/all' failed
make[1]: *** [myapp/CMakeFiles/myapp.dir/all] Error 2
Makefile:83: recipe for target 'all' failed
make: *** [all] Error 2
從提示信息中看出:已經編譯生成了 ./mylib/libmylib.so 文件,但是在編譯可執行程序 myapp 時遇到了錯誤:找不到 mylib.h 文件!
在剛纔介紹myapp/CMakeLists.txt
文件時說到:應用程序查找頭文件的目錄是 myapp/include, 查找庫文件的目錄是 myapp/lib。
但是這2
個目錄以及相應的頭文件、庫文件都不存在!
因此我們需要手動創建,並且把頭文件mylib.h
和庫文件libmylib.so
拷貝進去,操作過程如下:
$ cd ~/tmp/cmake_demo/myapp/
$ mkdir include lib
$ cp ~/tmp/cmake_demo/mylib/mylib.h ./include/
$ cp ~/tmp/cmake_demo/build/mylib/libmylib.so ./lib/
注意:剛纔編譯生成的庫文件libmylib.so
是在build
目錄下。
準備好頭文件和庫文件之後,再次編譯一下:
$ cd ~/tmp/cmake_demo/build/
$ make
[ 50%] Built target mylib
[ 75%] Building C object myapp/CMakeFiles/myapp.dir/myapp.c.o
[100%] Linking C executable myapp
[100%] Built target myapp
此時,就在 build/myapp 目錄下生成可執行文件myapp
了。
測試、執行
$ cd ~/tmp/cmake_demo/build/myapp
$ ./myapp
ret1 = 7
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
項目,改成可執行程序的全路徑:
"program": "F:/tmp/cmake_demo/build/myapp/Debug/myapp.exe"
然後再次按下F5
鍵,這回終於可以正確執行了:
此時,就可以在mylib.c
或者myapp.c
中設置斷點,然後進行單步調試程序了:
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/96UDODHgy9k6pIEf9DXPJw