#六、如何使用外部共享庫和頭文件
[TOC]
抱歉,本節仍然繼續折騰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/mainlinux-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 變量以及一些基本的控制語法規則。