天天看點

as 怎麼将多個cpp檔案代碼編譯成so_通過 C 代碼調用 Go

as 怎麼将多個cpp檔案代碼編譯成so_通過 C 代碼調用 Go
對 Golang 感興趣的同學歡迎關注公衆号:golang-experts

我們知道在 Go 中可以通過 Cgo 來調用 C 代碼的,那麼反過來能不能用 C 來調用 Go 呢?答案是可以的,通過動态連結庫的方式。

在談論具體操作之前,我們先來讨論一下通過 C 來調用 Go 的使用場景。Go 相比于 C 的好處在于開發效率高,特别寫網絡。如果我們之前有一些老的系統已經用 C/C++ 寫完了,這個時候需要打個 patch,而且這個對于性能也沒有特别的要求,但是對時間有要求,那麼這個時候使用 Go 來開發,然後通過 C 來調用也不失為一種好方法。

0. 動态連結

我們先來了解一下動态連結,以及靜态連結。我們知道在 GNU 編譯系統上一個 C 語言程式(比如 main.c )從代碼到運作需要經過一下幾步:預處理、編譯、彙編、連結。使用 gcc 編譯的時候加上 -v 參數可以看到這幾個步驟(可能沒有預處理步驟,因為現在很多編譯器已經将預處理合并到了編譯這一步裡面)。其中各個步驟的作用如下:

  1. 預處理:将代碼中的引用進行代碼替換。main.c -> main.i 。
  2. 編譯:将 main.i 翻譯成一個彙編檔案 main.s。
  3. 彙編:将 main.s 翻譯成一個可重定位的目标檔案 main.o。
  4. 連結:将 main.o 和其他依賴子產品組合成一個可執行的目标檔案。

靜态連結将上面第 3 步生成的一組可重定位的目标檔案生成一個完全連結的、可以加載和運作的可執行檔案作為輸出。這個過程中主要包括兩步:符号解析和重定位。符号包括函數、變量等,符号解析就是将我們代碼中引用别的子產品中的符号和它的定義關聯起來。重定位是為了保證程式運作的時候可以執行到正确的記憶體位置。通俗地說:靜态連結就是多個子產品的目标檔案組合成一個可執行目标檔案。

靜态連結的壞處在于我們程式依賴的每個子產品都需要整合進最終的可執行檔案,如果有一些基礎子產品是大家的程式的共享那就意味着記憶體中存在多份,這無疑是一種浪費。另外,每次依賴子產品的改變都需要我們的可執行檔案重新進行一次編譯連結,這是非常不合理的。舉個例子,作業系統如果是完全靜态連結的話,那麼每一個作業系統更新檔都意味着我們要重新編譯一次作業系統代碼,然而實際上并沒有,因為更新檔使用的是動态連結的方式。

動态連結的輸入不再是目标檔案 .o,而是動态連結庫 .so。編譯系統看到 .so 就明白了:“哦,這個函數位址運作的時候才能确定。”那麼是如何确定的呢?拿 GNU 編譯系統來說,使用的技術是延遲綁定(lazy binding)。延遲綁定的實作通過兩個關鍵資料結構:全局偏移量表(Global Offset Table, GOT)和過程連結表(Procedure Linkage Table, PLT)。感興趣的同學可以自行搜尋,或者參考《深入了解計算機系統》的第七章 “連結”。

上面說了這麼多,我們先來使用 C 語言建構一個自己的 so 檔案。首先是頭檔案:legendtkl.h。

//legendtkl.h
           

下面是 .c 檔案:legendtkl.c 。

#include 
           

再編寫一個測試程式:test.c。

#include 
           

靜态連結方式(-v 是為了看到具體的步驟):

$ gcc -v test.c legendtkl.c -o test1
           

動态連結需要先建構我們自己的 so 檔案。

$ gcc -shared -fpic -o liblegendtkl.so legendtkl.c
           

然後編譯。

$ gcc -v test.c -o test2 ./liblegendtkl.so
           

test1 就是靜态連結生成的可執行檔案。test2 是動态連結生成的目标檔案。test1 移動到任何位置都可以運作;而 test2 的運作目錄必須保證存在 liblegendtkl.so (可以将 so 傳遞絕對路徑)。另外不出以為的話:test1 的檔案大小要比 test2 要大。

動态連結還有一個好處是我們修改 legendtkl.c 然後編譯成 so 檔案,并把之前的 so 檔案替換。運作 test2 發現我們新加的功能已經有了,畢竟動态連結。

1. Go 代碼編譯成 so

Go 代碼編譯成 so 也很簡單,首先編寫 Go 代碼。

package 
           

建構 so 檔案。Go 建構出來的 so 檔案比較大,因為 Go 有 runtime。

$ go build -o legendtkl.so -buildmode=c-shared legendtkl.go
           

這個時候正常會輸出生成兩個檔案:legendtkl.h 和 legendtkl.so。頭檔案中負責做一些類型轉換。截取部分内容如下。

#ifndef GO_CGO_PROLOGUE_H
           

2. C 代碼引用

編寫測試代碼:test.c。

#include 
           

編譯

$ gcc test.c -o test ./legendtkl.so
           

運作

3
I am bar, not foo!
           

3. 結語

其實生成 so 檔案之後,其他語言也是可以調用的,這裡就不贅述了。

此專欄長期接受投稿,歡迎大家投稿 Go 相關的原創文章。

繼續閱讀