HelloWorld CMake CMake中構建靜態庫與動態庫及其使用
繼續完善Hello World,建立它的共享庫, 包括靜態庫和動態庫。
本節的任務:
1,建立一個靜態庫和動態庫,提供HelloFunc函式供其他程式程式設計使用,HelloFunc
向終端輸出Hello World字串。
2,安裝標頭檔案與共享庫。
3, 編寫一個程式使用建立的共享庫(靜態庫和動態庫)。
cd /home/ccj/CMakeDemo
mkdir t3
cd /backup/cmake/t3
mkdir lib
在t3目錄下建立CMakeLists.txt,內容如下:
PROJECT(HELLOLIB)
# 通過在主工程檔案CMakeLists.txt中修改ADD_SUBDIRECTORY (lib) 指令來指定一個編譯輸出位置;
# 指定本工程中靜態庫libhello.so生成的位置,即 build/lib;
ADD_SUBDIRECTORY(lib)
# 也可以通過變更為其他的位置,如
# ADD_SUBDIRECTORY(lib lib_new)
# 則,靜態庫libhello.so生成的位置變為 build/lib_new;
在lib目錄下建立兩個原始檔hello.cpp與 hello.h
hello.cpp內容如下:
#include "hello.h"
using namespace std;
void HelloFunc(){
cout << "Hello World/n";
}
hello.h內容如下:
#ifndef HELLO_H
#define HELLO_H
#include <stdio.h>
void HelloFunc();
#endif
在lib目錄下建立CMakeLists.txt,內容如下:
注意: 這裡我們將Section 7之前的CMakeLists.txt中應有的內容提供如下:
SET (LIBHELLO_SRC hello.cpp)
# SET (LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
# three kinds of libraries:
# 1. shared, i.e., dynamic library,動態庫副檔名常為 "*.so";
# 2. static, i.e., static library, 靜態庫副檔名常為 "*.a";
# 而且,我們通常希望靜態庫和動態庫檔名保持一致,只是副檔名不同;
# 3. module, this parameter is valid only when dyld is supported;
# otherwise, will be considered as shared
# 新增動態庫,關鍵詞為shared,你不需要寫全libhello.so,
# 只需要填寫hello即可,cmake系統會自動為你生成 libhello.X
ADD_LIBRARY (hello SHARED ${LIBHELLO_SRC})
# 新增靜態庫,關鍵詞為static,
# ADD_LIBRARY (hello STATIC ${LIBHELLO_SRC})
# 仍然用hello作為target名時,是不能成功建立所需的靜態庫的,
# 因為hello作為一個target是不能重名的, 故把上面的hello修改為hello_static
# 同理,你不需要寫全libhello_static.a
# 只需要填寫hello即可,cmake系統會自動為你生成 libhello_static.X
ADD_LIBRARY (hello_static STATIC ${LIBHELLO_SRC})
# 按照一般的習慣,靜態庫名字跟動態庫名字應該是一致的,只是副檔名不同;
# 即:靜態庫名為 libhello.a; 動態庫名為libhello.so ;
# 所以,希望 "hello_static" 在輸出時,不是"hello_static",而是以"hello"的名字顯示,故設定如下:
SET_TARGET_PROPERTIES (hello_static PROPERTIES OUTPUT_NAME "hello")
GET_TARGET_PROPERTY (OUTPUT_VALUE hello_static OUTPUT_NAME)
MESSAGE (STATUS "This is the hello_static OUTPUT_NAME: " ${OUTPUT_VALUE})
# cmake在構建一個新的target時,會嘗試清理掉其他使用這個名字的庫,
# 因此,在構建libhello.a時,就會清理掉libhello.so.
# 為了迴避這個問題,比如再次使用SET_TARGET_PROPERTIES定義 CLEAN_DIRECT_OUTPUT屬性。
SET_TARGET_PROPERTIES (hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES (hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
# 按照規則,動態庫是應該包含一個版本號的,
# VERSION指代動態庫版本,SOVERSION指代API版本。
SET_TARGET_PROPERTIES (hello PROPERTIES VERSION 1.2 SOVERSION 1)
# 我們需要將libhello.a, libhello.so.x以及hello.h安裝到系統目錄,才能真正讓其他人開發使用,
# 在本例中我們將hello的共享庫安裝到<prefix>/lib目錄;
# 將hello.h安裝<prefix>/include/hello目錄。
INSTALL (TARGETS hello hello_static LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
INSTALL (FILES hello.h DESTINATION include/hello)
:
仍然採用out-of-source編譯的方式,按照習慣,我們建立一個build目錄,在build
目錄中
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,動態庫;
- STATIC,靜態庫
- MODULE,在使用dyld的系統有效,如果不支援dyld,則被當作SHARED對待。
EXCLUDE_FROM_ALL引數的意思是這個庫不會被預設構建,除非有其他的元件依賴或者手動構建。
同樣使用上面的指令,我們在支援動態庫的基礎上再為工程新增一個靜態庫,按照一般的習
慣,靜態庫名字跟動態庫名字應該是一致的,只不過字尾是.a罷了。
下面我們用這個指令再來新增靜態庫:
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})
然後再在build目錄進行外部編譯,我們會發現,靜態庫根本沒有被構建,仍然只生成了
一個動態庫。因為hello作為一個ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
就可以構建一個libhello_static.a的靜態庫了。
這種結果顯示不是我們想要的,我們需要的是名字相同的靜態庫和動態庫,因為target名
稱是唯一的,所以,我們肯定不能通過ADD_LIBRARY指令來實現了。這時候我們需要用到
另外一個指令:
SET_TARGET_PROPERTIES,
其基本語法是:
SET_TARGET_PROPERTIES (target1 target2 ...PROPERTIES prop1 value1 prop2 value2 ...)
這條指令可以用來設定輸出的名稱,對於動態庫,還可以用來指定動態庫版本和API版本。
在本例中,我們需要作的是向lib/CMakeLists.txt中新增一條:
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
這樣,我們就可以同時得到libhello.so 和 libhello.a兩個庫了。
與SET_TARGET_PROPERTIES對應的指令是:
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
目錄,將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/loal ..
make
sudo make install
我們就可以將標頭檔案和共享庫安裝到系統目錄/usr/local/lib和/usr/local/include/hello中了。
如下圖:
前幾個sections中,我們已經完成了libhello靜、動態庫的構建以及安裝,本section中我們需要
編寫一個程式使用構建好的共享庫。
)在目錄下建立目:#include "hello.h"
int main(){
HelloFunc();
return 0;
}
編寫工程主檔案CMakeLists.txt
PROJECT(NEWHELLO)
ADD_SUBDIRECTORY(src)
編寫src/CMakeLists.txt
ADD_EXECUTABLE(main main.cpp)
)外部構建cmake ..
make
構建失敗,如果需要檢視細節,可以使用 make VERBOSE=1來構建。
錯誤結果如下:
1
)引入標頭檔案搜尋路徑。
hello.h位於/usr/local/include/hello目錄中,並沒有位於,同志,要這麼幹,我這
一節就沒什麼可寫了,只能選擇一個glib或者libX11來寫了,這些程式碼寫出來很多同志
是看不懂的)
為了讓我們的工程能夠找到hello.h標頭檔案,我們需要引入一個新的指令
INCLUDE_DIRECTORIES,其完整語法為:
INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
這條指令可以用來向工程新增多個特定的標頭檔案搜尋路徑,路徑之間用現在我們在src/CMakeLists.txt中新增一個頭檔案搜尋路徑,方式很簡單,加入:
INCLUDE_DIRECTORIES(/usr/local/include/hello)
進入build目錄,重新進行構建,這時找不到hello.h的錯誤已經消失,但是出現了一個新的錯誤:
2
main.cpp:(.text+0x5): undefined reference to `HelloFunc()'
因為我們並沒有link到共享庫libhello上。
)target這個指令可以用來為target新增需要連結的共享庫,本例中是一個可執行檔案,但是同樣可以用於為自己編寫的共享庫新增共享庫連結。
為了解決我們前面遇到的"HelloFunc()"未定義錯誤,我們需要作的是向
src/CMakeLists.txt中新增如下指令:
也可以寫成
進入build目錄重新進行構建。
cmake ..
make
這時我們就得到了一個連線到libhello的可執行程式main,位於build/src目錄,
執行main的結果是
讓我們來檢查一下main的連結情況:
$ ldd src/main
雖然了這個結果,但是沒有理解明白,到底連結什麼庫
可以清楚的看到main確實連結了共享庫libhello,而且連結的是動態庫libhello.so.1
$ ldd src/main
雖然了這個結果,但是沒有理解明白,到底連結什麼庫
說明,main確實連結到了靜態庫libhello.a
)特殊的環境變數:和務必注意CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH這兩個是環境變數來進行,使用bash的方法如下:
然後在標頭檔案中將INCLUDE_DIRECTORIES(/usr/local/include/hello)替換為:
FIND_PATH(myHeader hello.h)
IF(myHeader)
INCLUDE_DIRECTORIES(${myHeader})
ENDIF(myHeader)
上述的一些指令我們在後面會介紹。這裡簡單說明一下,FIND_PATH用來在指定路徑中搜索檔名,比如:FIND_PATH (myHeader NAMES hello.h PATHS /usr/local/include /usr/local/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模組都會受益。
到這裡為止,您應該基本可以使用cmake工作了,但是還有很多高階的話題沒有探討,比
如編譯條件檢查、編譯器定義、平臺判斷、如何跟pkgconfig配合使用等等。
到這裡,或許你可以理解前面講到的"cmake的使用過程其實就是學習cmake語言,並編寫
cmake程式的過程",既然是"cmake語言",自然涉及到變數、語法等. 下一節,我們將拋開程式的話題,看看常用的CMAKE變數以及一些基本的控制語法規則。