CGO 編譯和鏈接參數


CGO 編譯和鏈接參數

編譯和鏈接參數是每一個C/C++程序員需要經常面對的問題。構建每一個C/C++應用均需要經過編譯和鏈接兩個步驟,CGO也是如此。 本節我們將簡要討論CGO中經常用到的編譯和鏈接參數的用法。

編譯參數:CFLAGS/CPPFLAGS/CXXFLAGS

編譯參數主要是頭文件的檢索路徑,預定義的宏等參數。理論上來說C和C++是完全獨立的兩個編程語言,它們可以有着自己獨立的編譯參數。 但是因為C++語言對C語言做了深度兼容,甚至可以將C++理解為C語言的超集,因此C和C++語言之間又會共享很多編譯參數。 因此CGO提供了CFLAGS/CPPFLAGS/CXXFLAGS三種參數,其中CFLAGS對應C語言編譯參數(以.c后綴名)、 CPPFLAGS對應C/C++ 代碼編譯參數(.c,.cc,.cpp,.cxx)、CXXFLAGS對應純C++編譯參數(.cc,.cpp,*.cxx)。

鏈接參數:LDFLAGS

鏈接參數主要包含要鏈接庫的檢索目錄和要鏈接庫的名字。因為歷史遺留問題,鏈接庫不支持相對路徑,我們必須為鏈接庫指定絕對路徑。 cgo 中的 ${SRCDIR} 為當前目錄的絕對路徑。經過編譯后的C和C++目標文件格式是一樣的,因此LDFLAGS對應C/C++共同的鏈接參數。

pkg-config

為不同C/C++庫提供編譯和鏈接參數是一項非常繁瑣的工作,因此cgo提供了對應pkg-config工具的支持。 我們可以通過#cgo pkg-config xxx命令來生成xxx庫需要的編譯和鏈接參數,其底層通過調用 pkg-config xxx --cflags生成編譯參數,通過pkg-config xxx --libs命令生成鏈接參數。 需要注意的是pkg-config工具生成的編譯和鏈接參數是C/C++公用的,無法做更細的區分。

pkg-config工具雖然方便,但是有很多非標准的C/C++庫並沒有實現對其支持。 這時候我們可以手工為pkg-config工具創建對應庫的編譯和鏈接參數實現支持。

比如有一個名為xxx的C/C++庫,我們可以手工創建/usr/local/lib/pkgconfig/xxx.bc文件:

Name: xxx
Cflags:-I/usr/local/include
Libs:-L/usr/local/lib –lxxx2

其中Name是庫的名字,CflagsLibs行分別對應xxx使用庫需要的編譯和鏈接參數。如果bc文件在其它目錄, 可以通過PKG_CONFIG_PATH環境變量指定pkg-config工具的檢索目錄。

而對應cgo來說,我們甚至可以通過PKG_CONFIG 環境變量可指定自定義的pkg-config程序。 如果是自己實現CGO專用的pkg-config程序,只要處理--cflags--libs兩個參數即可。

下面的程序是macos系統下生成Python3的編譯和鏈接參數:

// py3-config.go
func main() {
    for _, s := range os.Args {
        if s == "--cflags" {
            out, _ := exec.Command("python3-config", "--cflags").CombinedOutput()
            out = bytes.Replace(out, []byte("-arch"), []byte{}, -1)
            out = bytes.Replace(out, []byte("i386"), []byte{}, -1)
            out = bytes.Replace(out, []byte("x86_64"), []byte{}, -1)
            fmt.Print(string(out))
            return
        }
        if s == "--libs" {
            out, _ := exec.Command("python3-config", "--ldflags").CombinedOutput()
            fmt.Print(string(out))
            return
        }
    }
}

然后通過以下命令構建並使用自定義的pkg-config工具:

$ go build -o py3-config py3-config.go
$ PKG_CONFIG=./py3-config go build -buildmode=c-shared -o gopkg.so main.go

go get 鏈

在使用go get獲取Go語言包的同時會獲取包依賴的包。比如A包依賴B包,B包依賴C包,C包依賴D包: pkgA -> pkgB -> pkgC -> pkgD -> ...。再go get獲取A包之后會依次線獲取BCD包。 如果在獲取B包之后構建失敗,那么將導致鏈條的斷裂,從而導致A包的構建失敗。

鏈條斷裂的原因有很多,其中常見的原因有:

不支持某些系統, 編譯失敗
依賴 cgo, 用戶沒有安裝 gcc
依賴 cgo, 但是依賴的庫沒有安裝
依賴 pkg-config, windows 上沒有安裝
依賴 pkg-config, 沒有找到對應的 bc 文件
依賴 自定義的 pkg-config, 需要額外的配置
依賴 swig, 用戶沒有安裝 swig, 或版本不對

仔細分析可以發現,失敗的原因中和CGO相關的問題占了絕大多數。這並不是偶然現象, 自動化構建C/C++代碼一直是一個世界難題,到目前位置也沒有出現一個大家認可的統一的C/C++管理工具。

因為用了cgo,比如gcc等構建工具是必須安裝的,同時盡量要做到對主流系統的支持。 如果依賴的C/C++包比較小並且有源代碼的前提下,可以優先選擇從代碼構建。

多個非main包中導出C函數

官方文檔說明導出的Go函數要放main包,但是真實情況是其它包的Go導出函數也是有效的。 因為導出后的Go函數就可以當作C函數使用,所以必須有效。但是不同包導出的Go函數將在同一個全局的名字空間,因此需要小心避免重名的問題。 如果是從不同的包導出Go函數到C語言空間,那么cgo自動生成的_cgo_export.h文件將無法包含全部到處的函數聲明, 我們必須通過手寫頭文件的方式什么導出的全部函數。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM