HelloWorld CMake Demo 03:CMake中构建静态库与动态库及其使用
?
繼續(xù)完善Hello World,建立它的共享庫,包括靜態(tài)庫和動態(tài)庫。
?
本節(jié)的任務:
1,建立一個靜態(tài)庫和動態(tài)庫,提供HelloFunc函數(shù)供其他程序編程使用,HelloFunc向終端輸出Hello World字符串。
2,安裝頭文件與共享庫。
3,編寫一個程序使用創(chuàng)建的共享庫(靜態(tài)庫和動態(tài)庫)。
?
一,準備工作:
在/home/ccj/CMakeDemo目錄建立t3目錄,用于存放本節(jié)涉及到的工程。
cd /home/ccj/CMakeDemo
mkdir t3
?
二,建立共享庫
cd /backup/cmake/t3
mkdir lib
?
在t3目錄下建立CMakeLists.txt,內(nèi)容如下:
PROJECT(HELLOLIB)
# 通過在主工程文件CMakeLists.txt中修改ADD_SUBDIRECTORY (lib) 指令來指定一個編譯輸出位置;
# 指定本工程中動態(tài)庫libhello.so生成的位置,即 build/lib;
ADD_SUBDIRECTORY(lib)
# 也可以通過變更為其他的位置,如
# ADD_SUBDIRECTORY(lib lib_new)
# 則,動態(tài)庫libhello.so生成的位置變?yōu)?/span> build/lib_new;
?
在lib目錄下建立兩個源文件hello.cpp與 hello.h
hello.cpp內(nèi)容如下:
?
#include "hello.h"using namespace std;void HelloFunc(){cout << "Hello World\n";}?
?
hello.h內(nèi)容如下:
?
#ifndef HELLO_H#define HELLO_H#include <stdio.h>void HelloFunc();#endif?
?
?
在lib目錄下建立CMakeLists.txt,內(nèi)容如下:
注意:這里我們將Section 7之前的CMakeLists.txt中應有的內(nèi)容提供如下:
SET (LIBHELLO_SRC hello.cpp)
# SET (LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
?
# three kinds of libraries:
# 1. shared, i.e., dynamic library,動態(tài)庫文件擴展名常為 "*.so";
# 2. static, i.e., static library,靜態(tài)庫文件擴展名常為 "*.a";
# 而且,我們通常希望靜態(tài)庫和動態(tài)庫文件名保持一致,只是擴展名不同;
# 3. module, this parameter is valid only when dyld is supported;
# otherwise, will be considered as shared
# 添加動態(tài)庫,關(guān)鍵詞為shared,你不需要寫全libhello.so,
# 只需要填寫hello即可,cmake系統(tǒng)會自動為你生成 libhello.X
ADD_LIBRARY (hello SHARED ${LIBHELLO_SRC})
# 添加靜態(tài)庫,關(guān)鍵詞為static,
# ADD_LIBRARY (hello STATIC ${LIBHELLO_SRC})
# 仍然用hello作為target名時,是不能成功創(chuàng)建所需的靜態(tài)庫的,
# 因為hello作為一個target是不能重名的,故把上面的hello修改為hello_static
# 同理,你不需要寫全libhello_static.a
# 只需要填寫 hello_static 即可,cmake系統(tǒng)會自動為你生成 libhello_static.X
ADD_LIBRARY (hello_static STATIC ${LIBHELLO_SRC})
?
# 按照一般的習慣,靜態(tài)庫名字跟動態(tài)庫名字應該是一致的,只是擴展名不同;
# 即:靜態(tài)庫名為 libhello.a;動態(tài)庫名為libhello.so ;
# 所以,希望 "hello_static" 在輸出時,不是"hello_static",而是以"hello"的名字顯示,故設(shè)置如下:
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在構(gòu)建一個新的target時,會嘗試清理掉其他使用這個名字的庫,
# 因此,在構(gòu)建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)
?
# 按照規(guī)則,動態(tài)庫是應該包含一個版本號的,
# VERSION指代動態(tài)庫版本,SOVERSION指代API版本。
SET_TARGET_PROPERTIES (hello PROPERTIES VERSION 1.2 SOVERSION 1)
?
# 我們需要將libhello.a, libhello.so.x以及hello.h安裝到系統(tǒng)目錄,才能真正讓其他人開發(fā)使用,
# 在本例中我們將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)
?
?
三,編譯動態(tài)共享庫:
?
Note:
動態(tài)庫: *.so (Linux), or *.dll (Windows);
靜態(tài)庫: *.a (Linux), or *.lib (Windows);
?
仍然采用out-of-source編譯的方式,按照習慣,我們建立一個build目錄,在build
目錄中
cmake ..
make
這時,你就可以在lib目錄得到一個libhello.so,這就是我們期望的共享庫。
如果你要指定libhello.so生成的位置,可以通過:
- 在主工程文件CMakeLists.txt中修改ADD_SUBDIRECTORY (lib) 指令來指定一個編譯輸出位置;
- 或者在 lib/CMakeLists.txt中添加SET (LIBRARY_OUTPUT_PATH <路徑>) 來指定一個新的位置。
?
這兩者的區(qū)別我們上一節(jié)已經(jīng)提到了,所以,這里不再贅述,下面,我們解釋一下一個新的指令ADD_LIBRARY。
?
ADD_LIBRARY(libname [SHARED|STATIC|MODULE]
[EXCLUDE_FROM_ALL] source1 source2 ... sourceN)
你不需要寫全 libhello.so,只需要填寫hello即可,cmake系統(tǒng)會自動為你生成 libhello.X
?
類型有三種:
- SHARED,動態(tài)庫;
- STATIC,靜態(tài)庫
- MODULE,在使用dyld的系統(tǒng)有效,如果不支持dyld,則被當作SHARED對待。
?
EXCLUDE_FROM_ALL參數(shù)的意思是這個庫不會被默認構(gòu)建,除非有其他的組件依賴或者手動構(gòu)建。
?
四,添加靜態(tài)庫:
同樣使用上面的指令,我們在支持動態(tài)庫的基礎(chǔ)上再為工程添加一個靜態(tài)庫,按照一般的習
慣,靜態(tài)庫名字跟動態(tài)庫名字應該是一致的,只不過后綴是.a罷了。
下面我們用這個指令再來添加靜態(tài)庫:
?
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})
?
然后再在build目錄進行外部編譯,我們會發(fā)現(xiàn),靜態(tài)庫根本沒有被構(gòu)建,仍然只生成了一個動態(tài)庫。因為hello作為一個target是不能重名的,所以,靜態(tài)庫構(gòu)建指令無效。
如果我們把上面的hello修改為hello_static:
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
就可以構(gòu)建一個libhello_static.a的靜態(tài)庫了。但是這種結(jié)果顯示不是我們想要的,因為我們需要的是名字相同的靜態(tài)庫和動態(tài)庫,but due to the?target名稱是唯一的,所以,我們肯定不能通過ADD_LIBRARY指令來實現(xiàn)了。這時候我們需要用到
另外一個指令:
SET_TARGET_PROPERTIES,
其基本語法是:
SET_TARGET_PROPERTIES (target1 target2 ...PROPERTIES prop1 value1 prop2 value2 ...)
這條指令可以用來設(shè)置輸出的名稱,對于動態(tài)庫,還可以用來指定動態(tài)庫版本和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.
?
讓我們來檢查一下最終的構(gòu)建結(jié)果,我們發(fā)現(xiàn),libhello.a已經(jīng)構(gòu)建完成,位于build/lib目錄中,但是libhello.so消失了。這個問題的原因是:cmake在構(gòu)建一個新的target時,會嘗試清理掉其他使用這個名字的庫,因為,在構(gòu)建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)
這時候,我們再次進行構(gòu)建,會發(fā)現(xiàn)build/lib目錄中同時生成libhello.so和libhello.a了。
?
五,動態(tài)庫版本號
按照規(guī)則,動態(tài)庫是應該包含一個版本號的,我們可以看一下系統(tǒng)的動態(tài)庫,一般情況是
libhello.so.1.2
libhello.so ->libhello.so.1
libhello.so.1->libhello.so.1.2
為了實現(xiàn)動態(tài)庫版本號,我們?nèi)匀恍枰褂?/span>SET_TARGET_PROPERTIES指令。
具體使用方法如下:
SET_TARGET_PROPERTIES (hello PROPERTIES VERSION 1.2 SOVERSION 1)
VERSION指代動態(tài)庫版本,SOVERSION指代API版本。
將上述指令加入lib/CMakeLists.txt中,重新構(gòu)建看看結(jié)果。
在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安裝到系統(tǒng)目錄,才能真正讓其他人開發(fā)使用,在本例中我們將hello的共享庫安裝到<prefix>/lib目錄,將hello.h安裝<prefix>/include/hello目錄。
利用上一節(jié)了解到的INSTALL指令,我們向lib/CMakeLists.txt中添加如下指令:
INSTALL (TARGETS hello hello_static LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
INSTALL (FILES hello.h DESTINATION include/hello)
注意,靜態(tài)庫要使用ARCHIVE關(guān)鍵字;動態(tài)庫要使用LIBRARY關(guān)鍵字。
?
通過:
cmake -DCMAKE_INSTALL_PREFIX=/usr/local ..
make
sudo make install
我們就可以將頭文件和共享庫安裝到系統(tǒng)目錄/usr/local/lib和/usr/local/include/hello中了。
如下圖:
?
七,編寫程序以使用構(gòu)建好的共享庫:
?
前幾個sections中,我們已經(jīng)完成了libhello靜、動態(tài)庫的構(gòu)建以及安裝,本section中我們需要編寫一個程序使用構(gòu)建好的共享庫。
?
1)在~/CMakeDemo/t3/ 目錄下建立src目:
并編寫源文件main.cpp,內(nèi)容如下:
?
#include "hello.h"int main(){HelloFunc();return 0;}?
?
編寫工程主文件CMakeLists.txt
PROJECT(NEWHELLO)
ADD_SUBDIRECTORY(src)
?
編寫src/CMakeLists.txt
ADD_EXECUTABLE(main main.cpp)
?
2)外部構(gòu)建
按照習慣,在build目錄下,使用cmake ..方式構(gòu)建。
過程:
cmake ..
make
?
構(gòu)建失敗,如果需要查看細節(jié),可以使用 make VERBOSE=1來構(gòu)建。
錯誤結(jié)果如下:
錯誤1:
/home/ccj/CMakeDemo/t3/src/main.cpp:1:19: fatal error: hello.h: No such file or directory
#include "hello.h"
?
3)引入頭文件搜索路徑。
hello.h位于/usr/local/include/hello目錄中,并沒有位于系統(tǒng)標準的頭文件路徑里,(有人會說了,白癡啊,你就不會include <hello/hello.h>,同志,要這么干,我這一節(jié)就沒什么可寫了,只能選擇一個glib或者libX11來寫了,這些代碼寫出來很多同志是看不懂的)
為了讓我們的工程能夠找到hello.h頭文件,我們需要引入一個新的指令
INCLUDE_DIRECTORIES,其完整語法為:
INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
這條指令可以用來向工程添加多個特定的頭文件搜索路徑,路徑之間用空格分割,如果路徑中包含了空格,可以使用雙引號將它括起來,默認的行為是追加到當前的頭文件搜索路徑的后面,你可以通過兩種方式來進行控制搜索路徑添加的方式:
- CMAKE_INCLUDE_DIRECTORIES_BEFORE,通過SET這個cmake變量為on,可以將添加的頭文件搜索路徑放在已有路徑的前面。
- 通過AFTER或者BEFORE參數(shù),也可以控制是追加還是置前。
?
現(xiàn)在我們在src/CMakeLists.txt中添加一個頭文件搜索路徑,方式很簡單,加入:
INCLUDE_DIRECTORIES(/usr/local/include/hello) (注意:這里我們暫且使用絕對路徑。)
?
進入build目錄,重新進行構(gòu)建,這時找不到hello.h的錯誤已經(jīng)消失,但是出現(xiàn)了一個新的錯誤:
?
錯誤2:
CMakeFiles/main.dir/main.o: In function `main':
main.cpp:(.text+0x5): undefined reference to `HelloFunc()'
?
?
因為我們并沒有link到共享庫libhello上。
?
4)為target添加動態(tài)共享庫
我們現(xiàn)在需要完成的任務是將目標文件鏈接到libhello,這里我們需要引入兩個新的指令
LINK_DIRECTORIES 和 TARGET_LINK_LIBRARIES
LINK_DIRECTORIES的全部語法是:
LINK_DIRECTORIES (directory1 directory2 ...)
這個指令非常簡單,添加非標準的共享庫搜索路徑,比如,在工程內(nèi)部同時存在共享庫和可執(zhí)行二進制,在編譯時就需要指定一下這些共享庫的路徑。這個例子中我們沒有用到這個指令。
?
TARGET_LINK_LIBRARIES的全部語法是:
TARGET_LINK_LIBRARIES(target library1 <debug | optimized> library2 ...)
這個指令可以用來為target添加需要鏈接的共享庫,本例中是一個可執(zhí)行文件,但是同樣可以用于為自己編寫的共享庫添加共享庫鏈接。
?
為了解決我們前面遇到的"HelloFunc()"未定義錯誤,我們需要作的是向
src/CMakeLists.txt中添加如下指令:
TARGET_LINK_LIBRARIES(main hello) 也可以寫成 TARGET_LINK_LIBRARIES(main libhello.so)
這里的hello指的是我們之前構(gòu)建好的動態(tài)共享庫libhello.so.
?
?
進入build目錄重新進行構(gòu)建。
cmake ..
make
這時我們就得到了一個連接到libhello的可執(zhí)行程序main,位于build/src目錄,
運行main的結(jié)果是
讓我們來檢查一下main的鏈接情況:
$ ldd src/main
可以清楚的看到main確實鏈接了共享庫libhello,而且鏈接的是動態(tài)庫libhello.so.1 (Note: 雖然show了這個結(jié)果,但是沒有理解明白,到底鏈接什么庫)
?
?
?
5)那如何鏈接到靜態(tài)庫呢?
方法很簡單:
將TARGET_LINK_LIBRRARIES指令修改為:
TARGET_LINK_LIBRARIES(main libhello.a)
重新構(gòu)建后再來看一下main的鏈接情況
$ ldd src/main
說明,main確實鏈接到了靜態(tài)庫libhello.a (Note: 雖然show了這個結(jié)果,但是沒有理解明白,到底鏈接什么庫)
?
?
?
6)特殊的環(huán)境變量:CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH
?
務必注意CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH這兩個是 環(huán)境變量而不是cmake變量。使用方法是要在bash中用export或者在csh中使用set命令設(shè)置或者CMAKE_INCLUDE_PATH=/home/include cmake ..等方式。
這兩個變量主要是用來解決以前autotools工程中 --extra-include-dir等參數(shù)的支持的。也就是,如果頭文件沒有存放在常規(guī)路徑(/usr/include, /usr/local/include等),則可以通過這些變量進行彌補。
我們以本例中的hello.h為例,它存放在/usr/include/hello目錄,所以直接查找肯定是找不到的。 前面我們直接使用了絕對路徑INCLUDE_DIRECTORIES(/usr/local/include/hello)告訴工程這個頭文件目錄。
為了將提高程序的可移植性,我們可以使用CMAKE_INCLUDE_PATH環(huán)境變量來進行,使用bash的方法如下:
注: bash中export的作用是將用戶定義的變量轉(zhuǎn)換成環(huán)境變量。
export CMAKE_INCLUDE_PATH=/usr/local/include/hello
?
然后在頭文件中將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存放的路徑,就是因為我們設(shè)置了環(huán)境變量CMAKE_INCLUDE_PATH。
如果你不使用FIND_PATH,CMAKE_INCLUDE_PATH變量的設(shè)置是不會起作用的,你不能指望它會直接為編譯器命令添加參數(shù)-I<CMAKE_INCLUDE_PATH>。
?
同理,CMAKE_LIBRARY_PATH可以用在FIND_LIBRARY中。同樣,因為這些變量直接為FIND_指令所使用,所以所有使用FIND_指令的cmake模塊都會受益。
?
八,小結(jié):
本文中,我們談到了:
1)創(chuàng)建靜、動態(tài)庫:
- 如何通過ADD_LIBRARY指令構(gòu)建動態(tài)庫(*.so)和靜態(tài)庫(*.a)。
- 如何通過SET_TARGET_PROPERTIES同時構(gòu)建同名的動態(tài)庫和靜態(tài)庫。
- 如何通過SET_TARGET_PROPERTIES控制動態(tài)庫版本。
- 使用INSTALL指令來安裝頭文件和動態(tài)庫和靜態(tài)庫。
2)使用已經(jīng)構(gòu)建的共享庫libhello和外部頭文件:
- 如何通過INCLUDE_DIRECTORIES指令加入非標準的頭文件搜索路徑。
- 如何通過LINK_DIRECTORIES指令加入非標準的庫文件搜索路徑。
- 如果通過TARGET_LINK_LIBRARIES為庫或可執(zhí)行二進制加入庫鏈接。
- 以及解釋了如果鏈接到靜態(tài)庫。
?
到這里為止,您應該基本可以使用cmake工作了,但是還有很多高級的話題沒有探討,比如編譯條件檢查、編譯器定義、平臺判斷、如何跟pkgconfig配合使用等等。
到這里,或許你可以理解前面講到的"cmake的使用過程其實就是學習cmake語言,并編寫cmake程序的過程",既然是"cmake語言",自然涉及到變量、語法等. 以后,我們將拋開程序的話題,看看常用的CMAKE變量以及一些基本的控制語法規(guī)則。
?
九,Reference:
?
Ref 1. CMake Practice – Cjacker;
轉(zhuǎn)載于:https://www.cnblogs.com/glory-of-family/p/4003953.html
總結(jié)
以上是生活随笔為你收集整理的HelloWorld CMake Demo 03:CMake中构建静态库与动态库及其使用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ubuntu apt-get insta
- 下一篇: CentOS 6.5 初始值