將 CGO 與 Pkg-Config 和 自定義動態庫位置一起使用
我在這個月初寫過一篇關於 Go 程式使用 C 動態庫的文章。那篇文章構建了一個 C 語言動態庫,並編寫了一個使用該動態庫的 Go 程式。但其中的 Go 程式碼只有動態庫和程式在同一個資料夾下才能正確工作。
這個限制導致無法使用 go get 命令直接下載,編譯,並安裝這個程式的工作版本。我不想程式碼需要預先安裝依賴或者在呼叫 go-get 之後執行任何指令碼和命令才能正確執行。Go 的工具套件不會把 C 語言的動態庫拷貝到 bin 目錄下,因為我無法在 go-get 命令完成後,就執行程式。這簡直是不可接受的,必須有辦法讓我能夠在執行完 Go-get 之後,就獲得一個正確執行程式。
解決這個問題,需要兩個步驟。第一步,我需要使用包配置檔案 (package configuration file) 來指定 CGO 的編譯器和連結器。第二步,我需要為作業系統設定一個環境變數,讓它能在不需要將二進位制檔案拷貝到 bin 目錄下,找到二進位制檔案。
如果你找找看,你會發現有些標準庫同樣也有一個包配置 (.pc) 檔案。一個名為 pkg-config 的特殊程式被構建工具(如 gcc)用於從這些檔案中檢索資訊。
如果我們檢視標頭檔案的標準檔案,例如 /usr/lib 或 /usr/local/lib ,你會發現一個名為 pkgconfig 的資料夾。預設情況下,pkg-config 程式可以找到這些位置中存在的包配置檔案。
檢視 libcrypto.pc 檔案,您可以看到格式以及它如何提供編譯器和連結器資訊。
這個特定的檔案看起來非常整潔清晰,因為他在最簡格式的前提下包含了所需要的引數。
如果想了解更多關於這些檔案的資訊,請閱讀網頁: https://www.freedesktop.org/wiki/Software/pkg-config/
檔案頭部的 prefix 的變數是最重要的。這個變數指定庫和標頭檔案被安裝的基礎目錄 (base folder)。
另外一個需要注意的事情是, 你不能使用環境變數來幫助指定一條路徑位置 。如果你這麼做,構建工具在定位它所需要的任何檔案都會有類似的問題 (you will have problems with the build tools locating any of the files it needs.)。 這個環境變數最終會一個字串的形式提供給編譯工具。請記住這一點,因為它很重要。
以下引數在終端執行這個 pkg-config 命令:
pkg-config – cflags – libs libcrypto
這些引數要求 pkg-config 程式顯示 libcrypto 這個 .pc 型別檔案所設定的編譯器和連結器引數。
這是應該返回的:
-lcrypto -lz
讓我們看一下,為了我所工作的一個專案而下載和安裝在 /usr/local 目錄下的 ImageMagick 的一個包配置檔案:
這個檔案有些稍微複雜。你會注意到它指定了它所需要的 MagickCode 庫以及一些作為環境變數的引數。
當我對這個檔案執行 pkg-config 程式時,我得到以下反饋資訊:
pkg-config – cflags – libs MagickWand -fopenmp -DMAGICKCORE_HDRI_ENABLE=0 -DMAGICKCORE_QUANTUM_DEPTH=16 -I/usr/local/include/ImageMagick-6-L/usr/local/lib -lMagickWand-6.Q16 -lMagickCore-6.Q16
你能看到標頭檔案和庫檔案路徑是絕對路徑。在包配置檔案中定義的其他引數都出現在命令的返回結果中。
現在,我們對包配置檔案有了些瞭解,並且知道如何使用 pkg-config 工具。讓我們看看我為了 Go 語言中使用 C 動態庫 這篇文章對這個專案的修改。 這個專案現在使用一個包配置檔案和新的 cgo 引數。
在我們開始之前,我必須先抱歉,因為針對這個專案所構建的動態庫只能夠在 Mac 上編譯。上面我所提到文章說明了原因。動態庫的預編譯版本已經存在在版本控制系統中,如果你不在 Mac 上工作,那麼這個專案就無法正常地編譯,但是專案的思路,設定和結構都是正確的。
開啟一個終端視窗,然後執行以下命令 :
cd $HOME export GOPATH=$HOME/keyboard export PKG_CONFIG_PATH=$GOPATH/src/github.com/goinggo/keyboard/pkgconfig export DYLD_LIBRARY_PATH=$GOPATH/src/github.com/goinggo/keyboard/DyLib go get Github.com/goinggo/keyboard
在執行完這些命令後,你將會從 GoingGo/keyboard 倉庫下載所有的程式碼到你 Home 目錄下一個名字為 keyboard 的子目錄。
你會注意到 Go 工具套件能夠下載,編譯,安裝 keyboard 程式。儘管標頭檔案和動態連結庫沒有在預設目錄 /usr 和 /usr/local 下。
在 /bin 目錄下,我們有一個單獨的可執行程式,但動態連結庫不在這個目錄下,連結庫只存放在 DyLib 目錄下。
在專案中有一個名字為 pkgconfig 的新的資料夾。在該檔案下的包配置檔案讓這一切稱為可能。
為了利用這個包配置檔案,對 main.go 的原始碼做了修改。
如果我們切換到 bin 目錄下,並且執行程式,我們能看到它能正常工作。
cd $GOPATH/bin ./keyboard
當我們開始程式時,它會馬上要求我們輸入些字元。輸入些字元並且輸入 q 字母來退出這個程式。
只有當作業系統查詢到這個程式所依賴的動態連結庫的時候,程式執行才是可能的。
讓我們看一下什麼樣的程式碼修改讓程式能夠執行。檢視 main.go 的原始碼,看看我們是如何引用新的包配置檔案的。
這是第一個博文的原始碼。在這個版本中,我直接指定了編譯器和連結器的引數。標頭檔案和動態連結庫的位置是通過相對路徑找到的。
package main /* #cgo CFLAGS: -I../DyLib #cgo LDFLAGS: -L. -lkeyboard #include <keyboard.h> */ import "C"
這是修改後的程式碼。在這個程式碼中,我告訴 CGO 使用 pkg-config 程式來尋找編譯和連結的引數。包配置檔案的名字在結尾處被指定。
package main /* #cgo pkg-config: – define-variable=prefix=. GoingGoKeyboard #include <keyboard.h> */ import "C"
注意一下,pkg-config 程式使用 -define-variable 引數。這個設定是讓一切運轉的訣竅。讓我們馬上回過頭來看看。
對我們的包配置檔案,執行 pkg-config 程式:
pkg-config – cflags – libs GoingGoKeyboard -I$GOPATH/src/github.com/goinggo/keyboard/DyLib -L$GOPATH/src/github.com/goinggo/keyboard/DyLib -lkeyboard
如果仔細觀察呼叫的輸出,你會看到些我告訴你的錯誤的用法。$GOPATH 環境變數是執行時提供的。
開啟在 pkgconfig 目錄下的包配置檔案,你會看到 pkg-config 程式沒有撒謊。在檔案的頭部,我正在使用 $GOPATH 設定一條路徑的字首路徑 (prefix variable)。 那為什麼一切都有效?
讓我們使用在 main.go 程式碼中相同的選項執行這個程式:
pkg-config – cflags – libs GoingGoKeyboard – define-variable=prefix=. -I./DyLib -L./DyLib -lkeyboard
你看到有什麼不同嗎?在第一次執行 pkg-config 程式時,我們獲得的路徑中使用 $GOPAHT 這樣一個字串的,因為這就是字首變數的設定方式。第二次執行時,我們將字首變數的值覆蓋到當前目錄, 得到我們想要的返回。
還記得我們在使用 Go 工具之前設定的環境變數嗎?
PKG_CONFIG_PATH=$GOPATH/src/github.com/goinggo/keyboard/pkgconfig
PKG_CONFIG_PATH 環境變數告訴 pkg-config 程式,它可以在哪裡找到不在任何預設位置的軟體包配置檔案。我們的 GoingGoKeyboard.pc 檔案就是這樣被 pkg-config 程式找到的。
最後一個要解釋的謎團是,作業系統如何找到執行我們程式所需要的動態庫。還記得我們在使用 Go 工具之前設定的這個環境變數嗎?
export DYLD_LIBRARY_PATH=$GOPATH/src/github.com/goinggo/keyboard/DyLib
DYLD_LIBRARY_PATH 環境變數告訴作業系統在哪裡還可以查詢動態庫。
在 /usr/local 資料夾中安裝動態庫可以使事情保持簡單。預設情況下,所有構建工具都配置為在這個資料夾中查詢。但是,如果對自己的或第三方庫檔案使用預設位置,需要在執行 Go 工具之前執行額外的安裝步驟。通過使用包配置檔案,向 pkg-config 程式傳遞所需的選項,使用 CGO 的 Go 程式可以部署安裝即可執行的構建。
還有一個我們提到的好處,你可以使用這種技術來將第三方庫安裝到一個臨時的路徑下進行測試使用。這讓你在不想使用這個第三庫時,可以很方便地進行移除。
如果您想在 Windows 或 Ubuntu 的機器上嘗試這些程式碼或概念,請閱讀 Go 語言中使用 C 動態庫 ,瞭解如何構建您自己的動態庫以供自己進行實驗。