假設(並非完全假設,這里有 demo)你正在編寫一個程序包,用於連接 Go 和其它一些提供大量 C 結構體內存的程序。這些結構可能是系統調用的結果,也可能是一個庫給你提供的純粹信息性內容。無論哪種情況,你都希望將這些結構傳遞給你的程序包的用戶,以便他們可以使用這些結構執行操作。在你的包中,你可以直接使用 cgo 提供的 C. 類型。但這有點惱人(這些整型它們沒有對應的原生 Go 類型,使得與常規 Go 代碼交互需要亂七八糟的強制轉換),並且對於其它導入你的包的代碼沒有幫助。因此,你需要以某種方式使用原生的 Go 結構體。
一種方式是手動為這些 C 結構體的定義你自己的 Go 版本。這有兩個缺點。這太枯燥了(還很容易出錯),並且不能保證你能獲得與 C 完全相同的內存布局(后者通常但並非總是很重要)。幸運的是有一種更好的方法,那就是使用 cgo 的 -godefs
功能或多或少地為你自動生成結構體聲明。生成結果並不總是完美的,但可能會為你帶來最大的收益。
使用 -godefs
的起點是特殊的 cgo Go 源文件,該文件需要將某些 Go 類型聲明為某些 C 類型。例如:
// +build ignore
package kstat
// #include <kstat.h>
import "C"
type IO C.kstat_io_t
type Sysinfo C.sysinfo_t
const Sizeof_IO = C.sizeof_kstat_io_t
const Sizeof_SI = C.sizeof_sysinfo_t
這些常量對於喜歡較真的人很有用,可以用來在后面對比檢查 Go 類型的 unsafe.Sizeof()
和 C 類型的大小是否一致。
運行 go tool cgo -godefs .go
,它將打印一系列帶有導出字段和所有內容的標准 Go 類型到標准輸出。然后,你可以將其保存到文件中並使用。如果你認為 C 類型可能會更改,則應將生成的文件保留下來,這樣就避免重新生成文件遇到的很多麻煩。如果 C 類型基本上是固定的,則可以使用 godoc 對生成的輸出進行注釋。 cgo 會考慮類型匹配問題,它會把原始的 C 結構中存在的 padding 也插入到輸出中。
我不知道如果原始的 C 結構體不可能在 Go 中重建出來,cgo 會怎么辦。 比如 Go 需要 padding,但是 C 不需要。希望它會指出錯誤。這是你以后可能要檢查這些 sizeof 的原因之一。
-godefs
最大的限制是與 cgo 通常具有的限制相同:它沒有對 C 聯合類型(union)的真正支持,因為 Go 確實沒有這個。如果你的 C 結構體中有聯合,你得自己弄清楚如何處理它們;我相信 cgo 把這些轉換為大小合適的 uint8 數組,但這對於實際訪問內容不是很有用。
這里有兩個問題。假設你有一個嵌入了另一個結構體類型的結構體:
struct cpu_stat {
struct cpu_sysinfo cpu_sysinfo;
struct cpu_syswait cpu_syswait;
struct vminfo cpu_vminfo;
}
在這里,你必須給 cgo 一些幫助,方式是在主結構體類型之前創建嵌入結構類型的 Go 版本:
type Sysinfo C.struct_cpu_sysinfo
type Syswait C.struct_cpu_syswait
type Vminfo C.struct_cpu_vminfo
type CpuStat C.struct_cpu_stat
然后 cgo 才能生成正確的內嵌的 Go 結構的 CpuStat 結構。如果不這樣做,你將獲得一個 CpuStat 結構類型,該結構類型具有不完整的類型信息,其中的 Sysinfo
等字段將引用名為 _Ctype_…
的未在任何地方定義的類型。
順便說一句,我在這確實是指 Sysinfo
,而不是 Cpu_sysinfo
。cgo 足夠聰明,可以從結構字段名稱中刪除這種常見的前綴。我不知道它的算法是怎樣的,但至少是有用的。
第二個問題是嵌入了匿名結構:
struct mntinfo_kstat {
....
struct {
uint32_t srtt;
uint32_t deviate;
} m_timers[4];
....
}
不幸的是,cgo 根本無法處理這種問題。具體可以去看 issue 5253 ,你有兩個選擇,第一種是使用建議的 CL 修復,這個目前仍然適用於 src/cmd/cgo/gcc.go 並且能夠工作(對我來說)。如果你不想構建自己的 Go 工具鏈(或者如果 CL 不再適用或無法工作),另一種解決方案是創建一個新的 C 頭文件,該文件具有整個結構體的變體,通過創建具名結構體去除結構體的匿名化。
struct m_timer {
uint32_t srtt;
uint32_t deviate;
}
struct mntinfo_kstat_cgo {
....
struct m_timer m_timers [4];
....
}
然后,在你的 Go 文件中,
...
// #include "myhacked.h"
...
type MTimer C.struct_m_timer
type Mntinfo C.struct_mntinfo_kstat_cgo
除非你搞錯了,否則兩個 C 結構體應具有完全相同的大小和布局,並且彼此完全兼容。現在你可以在你的版本上使用 -godefs
了,記住按照前面問題 1 的處理,需要為 m_timer
創建明確的 Go 類型。 如果你飄了(你認為你不在需要重新生成這些內容了),你可以在生成的 Go 文件中逆轉這個過程,重新將 MTimer 類型匿名化到結構體中(因為 Go 對匿名結構體有很好的支持)。因為你沒有更改實際內容,只是改了類型聲明,所以結果應該與原始的布局相同。
PS:-godefs
的輸入文件被設置為不被正常 go build
過程構建,因為它只用於 godefs 生成。如果這個文件也被包含在 go build
構建的源碼中,你會得到關於 Go 類型多處定義的構建錯誤。必然的結果是,你不需要將此文件和任何相關 .h 文件與軟件包的常規 .go 文件放在同一目錄。你可以把他們放在子目錄,或者放在完全獨立的位置。
(我認為該 package
行在 godefs .go 文件中唯一要做的就是設置 cgo 將在輸出中打印的軟件包名稱。)
via: https://utcc.utoronto.ca/~cks/space/blog/programming/GoCGoCompatibleStructs
作者:ChrisSiebenmann 譯者:befovy 校對:polaris1119