這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
為了能夠重用已有的C語言庫,我們在使用Golang開發項目或系統的時候難免會遇到Go和C語言混合編程,這時很多人都會選擇使用cgo。 話說cgo這個東西可算得上是讓人又愛又恨,好處在於它可以讓你快速重用已有的C語言庫,無需再用Golang重造一遍輪子,而壞處就在於它會在一定程度 上削弱你的系統效能。關於cgo的種種劣跡,Dave Cheney大神在他的部落格上有一篇專門的文章《cgo is not Go》,感興趣的同學可以看一看。但話說回來,有時候為了快速開發滿足項目需求,使用cgo也實在是不得已而為之。
在Golang中使用cgo調用C庫的時候,如果需要引用很多不同的第三方庫,那麼使用#cgo CFLAGS:和#cgo LDFLAGS:的方式會引入很多行代碼。首先這會導致代碼很醜陋,最重要的是如果引用的不是標準庫,標頭檔路徑和庫檔案路徑寫死的話就會很麻煩。一旦第 三方庫的安裝路徑變化了,Golang的代碼也要跟著變化,所以使用pkg-config無疑是一種更為優雅的方法,不管庫的安裝路徑有何變化,我們都不 需要修改Go代碼,接下來本博主就用一個簡單的例子來說明如何在cgo命令中使用pkg-config。 首先假定我們在路徑/home/ubuntu/third-parties/hello下安裝了一個名稱為hello的第三方C語言庫,其目錄結構如下所示,在hello_world.h中只定義了一個介面函數hello,該函數接收一個char *字串作為變數並調用printf將其列印到標準輸出。 # tree /home/ubuntu/third-parties/hello//home/ubuntu/third-parties/hello/├── include│ └── hello_world.h└── lib ├── libhello.so └── pkgconfig └── hello.pc 為了保證pkg-config能夠找到這個C語言庫,我們要為這個庫產生一個描述檔案,也就是lib/pkgconfig目錄下的hello.pc,其內容如下,有不瞭解該設定檔內容的看客們可以去搜尋一下pkg-config的相關文檔。 # cat hello.pc prefix=/home/ubuntu/third-parties/helloexec_prefix=${prefix}libdir=${exec_prefix}/libincludedir=${exec_prefix}/includeName: helloDescription: The hello library just for testing pkgconfigVersion: 0.1Libs: -lhello -L${libdir}Cflags: -I${includedir} 完成pkg-config描述檔案的建立後,還需要將該描述檔案的路徑資訊添加到PKG_CONFIG_PATH環境變數中,只有這樣 pkg-config才能正確擷取這個C語言庫的相關資訊。此外,我們還需要將該C語言庫的庫檔案路徑添加到LD_LIBRARY_PATH環境變數中, 具體命令如下: # export PKG_CONFIG_PATH=/home/ubuntu/third-parties/hello/lib/pkgconfig# pkg-config --list-all | grep libhellolibhello libhello - The hello library just for testing pkgconfig# export LD_LIBRARY_PATH=/home/ubuntu/third-parties/hello/lib 在完成以上一系列準備工作之後,我們就可以開始編寫Golang代碼了,以下是Golang調用C語言介面的程式碼範例,我們只需要#cgo pkg-config: libhello和#include < hello_world.h >兩行語句即可實現對hello函數的調用。如果C語言庫的安裝路徑發生了變化,只需修改hello.pc這個描述檔案即可,Golang代碼無需重新修改和編譯。 package main// #cgo pkg-config: libhello// #include < stdlib.h >// #include < hello_world.h >import "C"import ("unsafe")func main() {msg := "Hello, world!"cmsg := C.CString(msg)C.hello(cmsg)C.free(unsafe.Pointer(cmsg))} 最後,編譯該程式碼,查看可執行程式是否正確連結了C語言庫,執行程式驗證能否正確調用庫函數功能。 # go build hello_world.go # ldd hello_worldlinux-vdso.so.1 => (0x00007ffff63d3000)libhello.so => /home/ubuntu/third-parties/hello/lib/libhello.so (0x00007fc31c0e1000)libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fc31bec3000)libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc31bafe000) /lib64/ld-linux-x86-64.so.2 (0x00007fc31c2e3000)# ./hello_world Hello, world! 在以上步驟中需要關注的有兩個地方:1)建立C語言庫的pkg-config設定檔並將設定檔的路徑添加到環境變數 PKG_CONFIG_PATH中;2)C語言庫檔案的路徑添加到環境變數LD_LIBRARY_PATH中,如果沒有這一步,Go語言程式可以編譯成 功,但是可執行檔無法正確串連到C語言庫,會出現如下情況: # ldd hello_worldlinux-vdso.so.1 => (0x00007fffa49e2000)libhello.so => not foundlibpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007feb0fe93000)libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007feb0face000) /lib64/ld-linux-x86-64.so.2 (0x00007feb100b1000)