Goブログ

C? Go? Cgo!

Andrew Gerrand
2011年3月17日

はじめに

Cgoを使用すると、GoパッケージからCコードを呼び出すことができます。いくつかの特別な機能を備えたGoソースファイルが与えられた場合、cgoは単一のGoパッケージに結合できるGoファイルとCファイルを出力します。

例として、Cのrandom関数とsrandom関数をラップする2つの関数 - RandomSeed - を提供する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が「擬似パッケージ」、つまりcgoによってCの名前空間への参照として解釈される特別な名前であるためです。

randパッケージには、Cパッケージへの4つの参照が含まれています。C.randomC.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ドキュメントを参照してください。

この例でまだ検討していない1つの詳細は、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の文字列は、ヌル終端のcharの配列で表されます。

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の呼び出しは、char配列の先頭へのポインターを返すため、関数が終了する前に、unsafe.Pointerに変換し、C.freeでメモリ割り当てを解放します。cgoプログラムの一般的なイディオムは、defer割り当て直後に解放することです(特に、後続のコードが単一の関数呼び出しよりも複雑な場合)。次に、Printの書き換えで示します。

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の例では、より高度な概念を示しています。

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

次の記事:Gobs of data
前の記事:Goがより安定化
ブログインデックス