為了能夠重用已有的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/hello
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${exec_prefix}/include
Name: hello
Description: The hello library just for testing pkgconfig
Version: 0.1
Libs: -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 libhello
libhello 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_world
linux-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_world
linux-vdso.so.1 => (0x00007fffa49e2000)
libhello.so => not found
libpthread.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)