C? Go? Cgo!
C? Go? Cgo! By Andrew Gerrand
はじめに
cgoを使うことでGoパッケージを通してCコードを呼び出せます。ある特徴を持って書かれたGoのソースファイルが与えられると、cgoは単一のGoパッケージにまとめることが可能なGoとCのファイルを出力します。
例として、ここにCの関数 random と srandom をラップした2つの関数(Random と Seed)を提供するGoパッケージがあります。
package rand
/*
#include <stdlib.h>
*/
import "C"
func Random() int {
return int(C.random())
}
func Seed(i int) {
C.srandom(C.uint(i))
}
ここで何が起きているかを、importの文から見ていきましょう。
rand パッケージは "C" をインポートしていますが、Goのライブラリにはそのようなパッケージがないことに気づくでしょう。C は「疑似パッケージ」で、Cの名前空間への参照としてcgoにより解釈される特別な名前です。
rand パッケージは C パッケージに対する4つの参照を含んでいます。C.random と C.srandom の呼び出し、変換 C.uint(i)、そして import 文です。
Random 関数はC標準ライブラリの random 関数を呼び出し、その結果を返します。C言語において random は long 型の値を返しますが、これはcgoでは C.long 型として表されます。この値は、このパッケージの外部でGoコードから使われる前に、通常のGoの型変換によってGoの型へ変換しなければなりません。
func Random() int {
return int(C.random())
}
型変換を明示的に示すための一時変数を用いた同様の関数は以下のとおりです。
func Random() int {
var r C.long = C.random()
return int(r)
}
Seed 関数はある意味で逆のことをします。その関数は正規のGoの型 int を引数にとり、それをCの型 unsigned int に変換し、Cの関数 srandom に渡します。
func Seed(i int) {
C.srandom(C.uint(i))
}
cgoは unsigned int を C.uint として認識していることに注意してください。数値型名の完全なリストは cgo documentation をご覧ください。
この例においてまだ考察していない詳細部分は、import 文の上にあるコメントです。
/*
#include <stdlib.h>
*/
import "C"
cgoはこのコメントを認識しています。 #cgo とそれに続く単一のスペースで始まる行は全て取り除かれます。それらはcgoのための指示子となります。パッケージのCの部分をコンパイルする際に残りの行はヘッダーとして用いられます。この場合それらの行はちょうど単一の #include 文となりますが、それらはほぼ全てのCコードとなりえます。パッケージのCの部分をビルドする際に、コンパイラやリンカのためのフラグを提供するために #cgo が使われます。
制限があります。//export 指示子を使用する場合、コメント中のCコードには宣言(extern int f();)のみを含められ、定義(int f() { return 1; })を含めることはできません。Cコードからアクセス可能なGo関数を作るために //export 指示子を使えます。
#cgo と //export 指示子に関するドキュメントはcgo documentationにあります。
Strings と things
Goと違い、Cには明示的なstring型がありません。Cにおける文字列は、NULL終端された文字配列によって表されます。
GoとCの間の文字列変換は、C.CString、C.GoString、そして C.GoStringN 関数を用いて行えます。それらの変換は文字列データのコピーを生成します。
以下の例はCの stdio ライブラリの fputs 関数を用いて標準出力に文字列を書き込む Print 関数の実装です。
package print
// #include <stdio.h>
// #include <stdlib.h>
import "C"
import "unsafe"
func Print(s string) {
cs := C.CString(s)
C.fputs(cs, (*C.FILE)(C.stdout))
C.free(unsafe.Pointer(cs))
}
Cコードによって生成されたメモリアロケーションは、Goのメモリマネージャーから認識されません。C.CString (または任意のCのメモリアロケーション)を用いてCの文字列を生成するときは、C.free を呼び出してメモリを解放することを忘れてはいけません。
C.CString の呼び出しは文字配列の最初を示すポインタを返すので、関数から抜け出す前にそのポインタを unsafe.Pointer に変換し、C.free を用いてメモリアロケーションを解放します。cgoプログラムにおける共通のイディオムは、この書き換えた Print のように、アロケートしたあとすぐに defer によって解放することです(特に、あとに続くコードが一つの関数呼び出しよりも複雑な場合)。
func Print(s string) {
cs := C.CString(s)
defer C.free(unsafe.Pointer(cs))
C.fputs(cs, (*C.FILE)(C.stdout))
}
cgoのパッケージをビルドする
cgoのパッケージをビルドするために、いつもどおり go build または go install を使いましょう。go toolは特別な "C" インポートを認識し、それらのファイルのために自動的にcgoを使います。
さらなるcgoのリソース
cgo command のドキュメントにCの擬似パッケージやビルドプロセスについてより詳しく載っています。Goディレクトリツリー内の cgo examples により高度な概念が示されています。
最後に、これが内部でどのように動いているか興味があれば、ランタイムパッケージの cgocall.go の先頭のコメントをご覧ください。
By Andrew Gerrand
あわせて読みたい
- HTTP/2 Server Push
- Introducing HTTP Tracing
- Generating code
- Introducing the Go Race Detector
- Go maps in action
- go fmt your code
- Organizing Go code
- Debugging Go programs with the GNU Debugger
- The Go image/draw package
- The Go image package
- The Laws of Reflection
- Error handling and Go
- “First Class Functions in Go”
- Profiling Go Programs
- A GIF decoder: an exercise in Go interfaces
- Introducing Gofix
- Godoc: documenting Go code
- Gobs of data
- JSON and Go
- Go Slices: usage and internals
- Go Concurrency Patterns: Timing out, moving on
- Defer, Panic, and Recover
- Share Memory By Communicating
- JSON-RPC: a tale of interfaces