The Go Blog

C? Go? Cgo!

Andrew Gerrand
2011年3月17日

はじめに

Cgo を使用すると、Go パッケージから C コードを呼び出すことができます。いくつかの特殊な機能で記述された Go ソースファイルが与えられると、cgo は Go および C ファイルを出力し、これらを組み合わせて単一の Go パッケージにすることができます。

例を挙げると、ここに C の random および srandom 関数をラップする RandomSeed の 2 つの関数を提供する Go パッケージがあります。

package rand

/*
#include <stdlib.h>
*/
import "C"

func Random() int {
    return int(C.random())
}

func Seed(i int) {
    C.srandom(C.uint(i))
}

インポートステートメントから始めて、ここで何が起こっているかを見てみましょう。

rand パッケージは "C" をインポートしますが、標準 Go ライブラリにはそのようなパッケージはありません。これは、C が「擬似パッケージ」であり、C の名前空間への参照として cgo によって解釈される特殊な名前であるためです。

rand パッケージには、C パッケージへの 4 つの参照が含まれています。C.random および C.srandom の呼び出し、C.uint(i) の変換、および import ステートメントです。

Random 関数は、標準 C ライブラリの random 関数を呼び出し、結果を返します。C では、random は C 型の 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 ドキュメントを参照してください。

この例でまだ調べていない詳細は、import ステートメントの上のコメントです。

/*
#include <stdlib.h>
*/
import "C"

Cgo はこのコメントを認識します。#cgo に続いてスペース文字で始まる行は削除されます。これらは cgo のディレクティブになります。残りの行は、パッケージの C 部分をコンパイルする際のヘッダーとして使用されます。この場合、これらの行は単一の #include ステートメントだけですが、ほとんど任意の C コードにすることができます。#cgo ディレクティブは、パッケージの C 部分をビルドする際にコンパイラとリンカーにフラグを提供するために使用されます。

制限があります。プログラムが //export ディレクティブを使用する場合、コメント内の C コードは定義(int f() { return 1; })ではなく、宣言(extern int f();)のみを含めることができます。//export ディレクティブを使用して、Go 関数を C コードからアクセス可能にすることができます。

#cgo および //export ディレクティブは、cgo ドキュメントに記載されています。

文字列など

Go とは異なり、C には明示的な文字列型がありません。C の文字列は、ゼロ終端された文字の配列で表されます。

Go と C の文字列間の変換は、C.CStringC.GoString、および C.GoStringN 関数を使用して行われます。これらの変換は、文字列データのコピーを作成します。

次の例では、stdio ライブラリの C の 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 ツールは特別な "C" インポートを認識し、これらのファイルに対して自動的に cgo を使用します。

その他の cgo リソース

cgo コマンドのドキュメントには、C 擬似パッケージとビルドプロセスに関する詳細情報があります。Go ツリーの cgo の例は、より高度な概念を示しています。

最後に、これらすべてが内部でどのように機能するかに興味がある場合は、ランタイムパッケージの cgocall.go の冒頭のコメントをご覧ください。

次の記事: 大量のデータ
前の記事: Go の安定性が向上
ブログインデックス