1. 程式人生 > >HelloWorld CMake CMake中構建靜態庫與動態庫及其使用

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.solibhello.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_PATHCMAKE_INCLUDE_PATH變數的設定是不會起作用的,你不能指

望它會直接為編譯器命令新增引數-I<CMAKE_INCLUDE_PATH>

 

同理,CMAKE_LIBRARY_PATH可以用在FIND_LIBRARY中。同樣,因為這些變數直接為FIND_指令所使用,所以所有使用FIND_指令的cmake模組都會受益。

 

到這裡為止,您應該基本可以使用cmake工作了,但是還有很多高階的話題沒有探討,比

如編譯條件檢查、編譯器定義、平臺判斷、如何跟pkgconfig配合使用等等。

到這裡,或許你可以理解前面講到的"cmake的使用過程其實就是學習cmake語言,並編寫

cmake程式的過程",既然是"cmake語言",自然涉及到變數、語法等. 下一節,我們將拋開程式的話題,看看常用的CMAKE變數以及一些基本的控制語法規則。