【轉】《CMake實踐》筆記三:構建靜態庫(.a) 與 動態庫(.so) 及 如何使用外部共享庫和標頭檔案
五、靜態庫與動態庫構建
讀者雲,太能羅唆了,一個Hello World就折騰了兩個大節。OK,從本節開始,我們不再折騰Hello World了,我們來折騰Hello World的共享庫。
本節的任務:
1、建立一個靜態庫和動態庫,提供HelloFunc函式供其他程式程式設計使用,HelloFunc向終端輸出Hello World字串。
2、安裝標頭檔案與共享庫。
(一)、準備工作:
在/backup/cmake目錄建立t3目錄,用於存放本節涉及到的工程
(二)、建立共享庫
cd /backup/cmake/t3
mkdir lib
在t3目錄下建立CMakeLists.txt,內容如下:
PROJECT(HELLOLIB)
ADD_SUBDIRECTORY(lib)
在lib目錄下建立兩個原始檔hello.c與hello.h
hello.c內容如下:
#include "hello.h"
void HelloFunc()
{
printf("Hello World\n");
}
hello.h內容如下:
#ifndef HELLO_H
#define HELLO_H
#include <stdio.h>
void HelloFunc();
#endif
在lib目錄下建立CMakeLists.txt,內容如下:
SET(LIBHELLO_SRC hello.c)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
(三)、編譯共享庫
仍然採用 out-of-source
cmake ..
make
這時,你就可以在lib目錄得到一個libhello.so,這就是我們期望的共享庫。
如果你要指定libhello.so生成的位置,可以通過在主工程檔案CMakeLists.txt中修改ADD_SUBDIRECTORY(lib)指令來指定一個編譯輸出位置或者在lib/CMakeLists.txt中新增SET(LIBRARY_OUTPUT_PATH <路徑>)來指定一個新的位置。這兩者的區別我們上一節已經提到了,所以,這裡不再贅述,下面,我們解釋一下一個新的指令ADD_LIBRARY:
ADD_LIBRARY(libname [SHARED|STATIC|MODULE][EXCLUDE_FROM_ALL]source1 source2 ... sourceN)
你不需要寫全libhello.so,只需要填寫hello即可,cmake系統會自動為你生成libhello.X。型別有三種:
- SHARED,動態庫(副檔名為.so)
- STATIC,靜態庫(副檔名為.a)
- MODULE,在使用dyld的系統有效,如果不支援dyld,則被當作SHARED對待。
- EXCLUDE_FROM_ALL 引數的意思是這個庫不會被預設構建,除非有其他的元件依賴或者手工構建。
(四)、新增靜態庫
同樣使用上面的指令,我們在支援動態庫的基礎上再為工程新增一個靜態庫,按照一般的習慣,靜態庫名字跟動態庫名字應該是一致的,只不過字尾是.a罷了。下面我們用這個指令再來新增靜態庫:
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})
然後再在build目錄進行外部編譯,我們會發現,靜態庫根本沒有被構建,仍然只生成了一個動態庫。因為hello作為一個target是不能重名的,所以,靜態庫構建指令無效。
如果我們把上面的hello修改為hello_static:
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
就可以構建一個libhello_static.a的靜態庫了。這種結果顯示不是我們想要的,我們需要的是名字相同的靜態庫和動態庫,因為target名稱是唯一的,所以,我們肯定不能通過ADD_LIBRARY指令來實現了。這時候我們需要用到另外一個指令:
SET_TARGET_PROPERTIES,其基本語法是:
SET_TARGET_PROPERTIES(target1 target2 ...PROPERTIES prop1 value1prop2 value2 ...)這條指令可以用來設定輸出的名稱,對於動態庫,還可以用來指定動態庫版本和API版本。在本例中,我們需要作的是向lib/CMakeLists.txt中新增一條:
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
這樣,我們就可以同時得到libhello.so/libhello.a兩個庫了。與他對應的指令是:
GET_TARGET_PROPERTY(VAR target property)
具體用法如下例,我們向lib/CMakeListst.txt中新增:
GET_TARGET_PROPERTY(OUTPUT_VALUE hello_static OUTPUT_NAME)
MESSAGE(STATUS "This is the hello_static OUTPUT_NAME:" ${OUTPUT_VALUE})如果沒有這個屬性定義,則返回 NOTFOUND。
讓我們來檢查一下最終的構建結果,我們發現,libhello.a已經構建完成,位於build/lib目錄中,但是libhello.so去消失了。這個問題的原因是:cmake在構建一個新的target時,會嘗試清理掉其他使用這個名字的庫,因為,在構建libhello.a時,就會清理掉libhello.so.為了迴避這個問題,比如再次使用SET_TARGET_PROPERTIES定義
CLEAN_DIRECT_OUTPUT屬性。向lib/CMakeLists.txt中新增:
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
這時候,我們再次進行構建,會發現build/lib目錄中同時生成了 libhello.so 和 libhello.a
(五)、動態庫版本號
按照規則,動態庫是應該包含一個版本號的,我們可以看一下系統的動態庫,一般情況是
libhello.so.1.2
libhello.so ->libhello.so.1
libhello.so.1->libhello.so.1.2
為了實現動態庫版本號,我們仍然需要使用SET_TARGET_PROPERTIES指令。具體使用方法如下:
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
VERSION指代動態庫版本,SOVERSION指代API版本。將上述指令加入lib/CMakeLists.txt中,重新構建看看結果。
在build/lib目錄會生成:
libhello.so.1.2
libhello.so.1->libhello.so.1.2
libhello.so ->libhello.so.1
(六)、安裝共享庫和標頭檔案
以上面的例子,我們需要將libhello.a, libhello.so.x以及hello.h安裝到系統目錄,才能真正讓其他人開發使用,在本例中我們將hello的共享庫安裝到<prefix>/lib目錄,將hello.h安裝到<prefix>/include/hello目錄。
利用上一節瞭解到的INSTALL指令,我們向lib/CMakeLists.txt中新增如下指令:
INSTALL(TARGETS hello hello_static
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
INSTALL(FILES hello.h DESTINATION include/hello)
注意,靜態庫要使用ARCHIVE關鍵字通過:
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
make
make install
我們就可以將標頭檔案和共享庫安裝到系統目錄/usr/lib和/usr/include/hello中了。
(七)、小結
本小節,我們談到了:
如何通過 ADD_LIBRARY 指令構建動態庫和靜態庫。
如何通過 SET_TARGET_PROPERTIES 同時構建同名的動態庫和靜態庫。
如何通過 SET_TARGET_PROPERTIES 控制動態庫版本
最終使用上一節談到的 INSTALL 指令來安裝標頭檔案和動態、靜態庫。
在下一節,我們需要編寫另一個高階一點的Hello World來演示怎麼使用我們已經構建的構建的共享庫libhello和外部標頭檔案。
以下是我跟著做的截圖,確實很方便:(這裡有點失誤,設定了個CC的環境變數,所以這裡找的是arm-linux-gcc去了。。本意是用gcc編譯的)
六、如何使用外部共享庫和標頭檔案
抱歉,本節仍然繼續折騰Hello World。
上一節我們已經完成了libhello動態庫的構建以及安裝,本節我們的任務很簡單:
編寫一個程式使用我們上一節構建的共享庫。
1、準備工作:
請在/backup/cmake目錄建立t4目錄,本節所有資源將儲存在t4目錄。
2、重複以前的步驟,建立src目錄,編寫原始檔main.c,內容如下:
#include <hello.h>
int main()
{
HelloFunc();
return 0;
}
編寫工程主檔案CMakeLists.txt
PROJECT(NEWHELLO)
ADD_SUBDIRECTORY(src)
編寫src/CMakeLists.txt
ADD_EXECUTABLE(main main.c)
上述工作已經嚴格按照我們前面季節提到的內容完成了。
3、外部構建
按照習慣,仍然建立build目錄,使用cmake ..方式構建。
過程:
cmake ..
make
構建失敗,如果需要檢視細節,可以使用第一節提到的方法
make VERBOSE=1
來構建
錯誤輸出為是:
/backup/cmake/t4/src/main.c:1:19: error: hello.h: 沒有那個檔案或目錄
4、引入標頭檔案搜尋路徑
hello.h位於/usr/include/hello目錄中,並沒有位於系統標準的標頭檔案路徑,(有人會說了,白痴啊,你就不會include <hello/hello.h>,同志,要這麼幹,我這一節就沒什麼可寫了,只能選擇一個glib或者libX11來寫了,這些程式碼寫出來很多同志是看不懂的)
為了讓我們的工程能夠找到hello.h標頭檔案,我們需要引入一個新的指令
INCLUDE_DIRECTORIES,其完整語法為:
INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
這條指令可以用來向工程新增多個特定的標頭檔案搜尋路徑,路徑之間用空格分割,如果路徑中包含了空格,可以使用雙引號將它括起來,預設的行為是追加到當前的標頭檔案搜尋路徑的後面,你可以通過兩種方式來進行控制搜尋路徑新增的方式:
(1)、CMAKE_INCLUDE_DIRECTORIES_BEFORE,通過SET這個cmake變數為on,可以將新增的標頭檔案搜尋路徑放在已有路徑的前面。
(2)、通過AFTER或者BEFORE引數,也可以控制是追加還是置前。現在我們在src/CMakeLists.txt中新增一個頭檔案搜尋路徑,方式很簡單,加入:INCLUDE_DIRECTORIES(/usr/include/hello)
進入build目錄,重新進行構建,這是找不到hello.h的錯誤已經消失,但是出現了一個新的錯誤:
main.c:(.text+0x12): undefined reference to `HelloFunc'
因為我們並沒有link到共享庫libhello上。
5、為target新增共享庫
我們現在需要完成的任務是將目標檔案連結到libhello,這裡我們需要引入兩個新的指令
LINK_DIRECTORIES和TARGET_LINK_LIBRARIES
LINK_DIRECTORIES的全部語法是:
LINK_DIRECTORIES(directory1 directory2 ...)
這個指令非常簡單,新增非標準的共享庫搜尋路徑,比如,在工程內部同時存在共享庫和可執行二進位制,在編譯時就需要指定一下這些共享庫的路徑。這個例子中我們沒有用到這個指令。
TARGET_LINK_LIBRARIES的全部語法是:
TARGET_LINK_LIBRARIES(target library1<debug | optimized> library2...)
這個指令可以用來為target新增需要連結的共享庫,本例中是一個可執行檔案,但是同樣可以用於為自己編寫的共享庫新增共享庫連結。為了解決我們前面遇到的HelloFunc未定義錯誤,我們需要作的是向src/CMakeLists.txt中新增如下指令:
TARGET_LINK_LIBRARIES(main hello)
也可以寫成
TARGET_LINK_LIBRARIES(main libhello.so)
這裡的hello指的是我們上一節構建的共享庫libhello.
進入build目錄重新進行構建。
cmake ..
make
這是我們就得到了一個連線到libhello的可執行程式main,位於build/src目錄,執行main的結果是輸出:
Hello World
讓我們來檢查一下main的連結情況:
ldd src/main
linux-gate.so.1 => (0xb7ee7000)
libhello.so.1 => /usr/lib/libhello.so.1 (0xb7ece000)
libc.so.6 => /lib/libc.so.6 (0xb7d77000)
/lib/ld-linux.so.2 (0xb7ee8000)
可以清楚的看到main確實連結了共享庫libhello,而且連結的是動態庫libhello.so.1
那如何連結到靜態庫呢?
方法很簡單:
將TARGET_LINK_LIBRRARIES指令修改為:
TARGET_LINK_LIBRARIES(main libhello.a)
重新構建後再來看一下main的連結情況
ldd src/main
linux-gate.so.1 => (0xb7fa8000)
libc.so.6 => /lib/libc.so.6 (0xb7e3a000)
/lib/ld-linux.so.2 (0xb7fa9000)
說明,main確實連結到了靜態庫libhello.a
6、特殊的環境變數 CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH
務必注意,這兩個是環境變數而不是cmake變數。使用方法是要在bash中用export或者在csh中使用set命令設定或者CMAKE_INCLUDE_PATH=/home/include cmake ..等方式。
這兩個變數主要是用來解決以前autotools工程中--extra-include-dir等引數的支援的。也就是,如果標頭檔案沒有存放在常規路徑(/usr/include, /usr/local/include等),則可以通過這些變數就行彌補。我們以本例中的hello.h為例,它存放在/usr/include/hello目錄,所以直接查詢肯定是找不到的。前面我們直接使用了絕對路徑INCLUDE_DIRECTORIES(/usr/include/hello)告訴工程這個標頭檔案目錄。為了將程式更智慧一點,我們可以使用CMAKE_INCLUDE_PATH來進行,使用bash的方法如下:
export CMAKE_INCLUDE_PATH=/usr/include/hello然後在標頭檔案中將INCLUDE_DIRECTORIES(/usr/include/hello)替換為:
FIND_PATH(myHeader hello.h)
IF(myHeader)
INCLUDE_DIRECTORIES(${myHeader})
ENDIF(myHeader)
上述的一些指令我們在後面會介紹。這裡簡單說明一下,FIND_PATH用來在指定路徑中搜索檔名,比如:
FIND_PATH(myHeader NAMES hello.h PATHS /usr/include/usr/include/hello)
這裡我們沒有指定路徑,但是,cmake仍然可以幫我們找到hello.h存放的路徑,就是因為我們設定了環境變數CMAKE_INCLUDE_PATH。如果你不使用FIND_PATH,CMAKE_INCLUDE_PATH變數的設定是沒有作用的,你不能指望它會直接為編譯器命令新增引數-I<CMAKE_INCLUDE_PATH>。以此為例,CMAKE_LIBRARY_PATH可以用在FIND_LIBRARY中。同樣,因為這些變數直接為FIND_指令所使用,所以所有使用FIND_指令的cmake模組都會受益。
7、小節
本節我們探討了:
如何通過INCLUDE_DIRECTORIES指令加入非標準的標頭檔案搜尋路徑。
如何通過LINK_DIRECTORIES指令加入非標準的庫檔案搜尋路徑。
如果通過TARGET_LINK_LIBRARIES為庫或可執行二進位制加入庫連結。
並解釋瞭如果連結到靜態庫。
到這裡為止,您應該基本可以使用cmake工作了,但是還有很多高階的話題沒有探討,比如編譯條件檢查、編譯器定義、平臺判斷、如何跟pkgconfig配合使用等等。
到這裡,或許你可以理解前面講到的“cmake的使用過程其實就是學習cmake語言並編寫cmake程式的過程”,既然是“cmake語言”,自然涉及到變數、語法等。
下一節,我們將拋開程式的話題,看看常用的CMAKE變數以及一些基本的控制語法規則。
未完,待續。。。。
這裡關於LINK_DIRECTORIES似乎有個bug。基本上按照上述所寫方法,只是我沒有將測試用的hello.h和libhello.so安裝在系統的usr/lib目錄下。於是我就需要用到這個LINK_DIRECTORIES指定我的庫檔案的目錄。main.c的CMakeLists.txt如下:
ADD_EXECUTABLE(main main.c)
FIND_PATH(myHeader hello.h)
IF(myHeader)
INCLUDE_DIRECTORIES(${myHeader})
ENDIF(myHeader)
TARGET_LINK_LIBRARIES(main libhello.so)
LINK_DIRECTORIES(/home/wideking/cmakeDemo/libhello/bulid)
在編譯的時候它就是找不到我的libhello.so檔案。goole了一下,有人試過將ADD_EXECUTABLE和LINK_DIRECTORIES互換了下位置就行了。我也試了試,提示錯誤,必須先有main,才能有庫的連結,確實也應該這樣。於是再在後面增加一行ADD_EXECUTABLE就變成了這樣:
ADD_EXECUTABLE(main main.c)
FIND_PATH(myHeader hello.h)
IF(myHeader)
INCLUDE_DIRECTORIES(${myHeader})
ENDIF(myHeader)
TARGET_LINK_LIBRARIES(main libhello.so)
LINK_DIRECTORIES(/home/wideking/cmakeDemo/libhello/bulid)
ADD_EXECUTABLE(main main.c)
雖然有構建的時候有warning,但是這樣才能正常的使用。我是在cmake2.6版本中測試的。不知道這個是我沒理解對還是咋的。。。。
相關推薦
【轉】《CMake實踐》筆記三:構建靜態庫(.a) 與 動態庫(.so) 及 如何使用外部共享庫和標頭檔案
五、靜態庫與動態庫構建讀者雲,太能羅唆了,一個Hello World就折騰了兩個大節。OK,從本節開始,我們不再折騰Hello World了,我們來折騰Hello World的共享庫。本節的任務:1、建立一個靜態庫和動態庫,提供HelloFunc函式供其他程式程式設計使用,H
《CMake實踐》筆記三:構建靜態庫與動態庫 及 如何使用外部共享庫和標頭檔案
五、靜態庫與動態庫構建 讀者雲,太能羅唆了,一個Hello World就折騰了兩個大節。OK,從本節開始,我們不再折騰Hello World了,我們來折騰Hello World的共享庫。 本節的任務: 1、建立一個靜態庫和動態庫,提供HelloFunc函式供
《CMake實踐》筆記三:構建靜態庫(.a) 與 動態庫(.so) 及 如何使用外部共享庫和標頭檔案
五、靜態庫與動態庫構建 讀者雲,太能羅唆了,一個Hello World就折騰了兩個大節。OK,從本節開始,我們不再折騰Hello World了,我們來折騰Hello World的共享庫。 本節的任務: 1、建立一個靜態庫和動態庫,提供HelloFunc函式供其他程式程式設計使用,Hell
【轉】JMeter學習(三)元件的作用域與執行順序
ces ner 處理器 規則 fig 子節點 控制器 conf 節點 1.元件的作用域 JMeter中共有8類可被執行的元件(測試計劃與線程組不屬於元件),這些元件中,取樣器是典型的不與其它元件發生交互作用的元件,邏輯控制器只對其子節點的取樣器有效,而其它元件(config
【轉】Nodejs學習筆記(一)--- 簡介及安裝Node.js開發環境
ack 目錄 javascrip 難度 時間 網站開發 clas jetbrains 常用 目錄 學習資料 簡介 安裝Node.js npm簡介 開發工具 Sublime Node.js開發環境配置 擴展:安裝多版本管理器 學習資料 1.深入淺出Node.j
【轉】Android開發筆記(序)寫在前面的目錄
animator 進程間通信 scrip cst 調用 receiver 手勢 打包 數據庫基礎 原文:http://blog.csdn.net/aqi00/article/details/50012511 知識點分類 一方面寫寫自己走過的彎路掉進去的坑,避免以後
【轉】Python3學習筆記(urllib模塊的使用)
nal 方法 utf 網址 pin des IE tps erer 原文地址:https://www.cnblogs.com/Lands-ljk/p/5447127.html 1.基本方法 urllib.request.urlopen(url, data=None, [ti
【轉】VMware虛擬機三種網絡模式詳解
童鞋 tro 修改 遠程連接 www 學習交流 退出 con 現在 由於Linux目前很熱門,越來越多的人在學習Linux,但是買一臺服務放家裏來學習,實在是很浪費。那麽如何解決這個問題?虛擬機軟件是很好的選擇,常用的虛擬機軟件有VMware Workstations和
【轉】MongoDB學習筆記(查詢)
順序 god ... ive HR 操作 方式 mar obj 原文地址 MongoDB學習筆記(查詢) 基本查詢: 構造查詢數據。 > db.test.findOne() { "_id" : ObjectId("4fd58ecbb9ac507e96276f1a")
【轉】Verilog學習筆記簡單功能實現(八)...............異步FIFO
另一個 gif 多個 可靠 基本原理 drs bar next 不同 基本原理: 1.讀寫指針的工作原理 寫指針:總是指向下一個將要被寫入的單元,復位時,指向第1個單元(編號為0)。 讀指針:總是指向當前要被讀出的數據,復位時,指向第1個單元(編號為0)
【轉】VMware虛擬機三種網絡模式超詳解
編輯器 網卡 host 子網 什麽 script network 如何解決 技術 【原文】https://www.toutiao.com/i6596228488666022403/ 由於Linux目前很熱門,越來越多的人在學習Linux,但是買一臺服務放家裏來學習,實在是很
【轉】請說出三種減少頁面載入時間的方法。
1.優化圖片 2.影象格式的選擇(GIF:提供的顏色較少,可用在一些對顏色要求不高的地方) 3.優化CSS(壓縮合並css,如margin-top,margin-left...) 4.網址後加斜槓(如ww
【轉】請說出三種減少頁面加載時間的方法。
www com 無法 如果 壓縮合並 class 是什麽 .com 高度 1.優化圖片 2.圖像格式的選擇(GIF:提供的顏色較少,可用在一些對顏色要求不高的地方) 3.優化CSS(壓縮合並css,如margin-top,margin-left...) 4.網
【轉】Hadoop學習--第二篇:史上最詳細的Hadoop環境搭建
GitChat 作者:鳴宇淳 原文: 史上最詳細的Hadoop環境搭建 前言 Hadoop在大資料技術體系中的地位至關重要,Hadoop是大資料技術的基礎,對Hadoop基礎知識的掌握的紮實程度,會決定在大資料技術道路上走多遠。 這是一篇入門文章,Hadoop的學
【轉】 VMWare虛擬機器提示:鎖定檔案失敗,打不開磁碟的解決辦法
如果執行虛擬機器時,物理機突然崩潰,則會導致這種問題。 這是因為虛擬機器在執行的時候,會鎖定檔案,防止被修改,而如果突然系統崩潰了,虛擬機器就來不急把已經鎖定的檔案解鎖,所以你在啟動的時候,就會提示無法鎖定檔案,如下圖: 解決方法如下: 開啟你存放虛擬機器系統硬碟的所在資料夾,注意
【轉】SQL四種語言:DDL,DML,DCL,TCL
1.DDL(Data Definition Language)資料庫定義語言statements are used to define the database structure or schema. DDL是SQL語言的四大功能之一。 用於定義資料庫的三級結構,包括外模式、概念模式、內模式及
【django3】Django學習筆記3:Model,Template,View 基本概念
轉載:http://www.cnblogs.com/weichsel/archive/2012/10/16/2725554.html,侵權必刪 總體結構 Django是MTV結構,即:Model, Template, View &nb
【MVC】.NET實踐(三)—對資料庫的資料進行刪除與修改
在主介面新增修改和刪除的超連結(Index.cshtml) <table id="tbList"> <tr> <th>id&l
【轉】時衝的CSDN:Linux系統各個目錄的作用
以下僅用於個人梳理,排版方便閱讀記憶(原文更優): from my typora: Linux檔案系統 LINUX有四種基本檔案系統型別: 普通檔案、目錄檔案、連線檔案和特殊檔案,可用file命令來識別。 [email protected]:~$
【轉】最佳實踐 —— 詳細談談如何減小APK體積
本篇文章轉載自http://www.cnblogs.com/soaringEveryday/p/5254520.html 作者寫的很詳細 格式略有調整 以下正文 隨著Android移動開發的需求越來越複雜,我們不可避免的遇到釋出出去的apk體積越來越