Go Wiki: cgo

はじめに

まず、https://pkg.go.dev/cmd/cgo が主要な cgo のドキュメントです。

https://go.dokyumento.jp/blog/cgo にも良い入門記事があります。

基本

Go ソースファイルが "C" をインポートしている場合、それは cgo を使用しています。Go ファイルは import "C" の行の直前のコメントに記述されているものすべてにアクセスでき、他の Go ファイルのすべての cgo コメント、およびビルドプロセスに含まれるすべての C ファイルにリンクされます。

cgo コメントと import ステートメントの間に空白行があってはならないことに注意してください。

C 側から来るシンボルにアクセスするには、パッケージ名 C を使用します。つまり、Go コードから C 関数 printf() を呼び出したい場合は、C.printf() と記述します。printf のような可変引数メソッドはまだサポートされていないため(issue 975)、C メソッド「myprint」でラップします。

package cgoexample

/*
##include <stdio.h>
##include <stdlib.h>

void myprint(char* s) {
    printf("%s\n", s);
}
*/
import "C"

import "unsafe"

func Example() {
    cs := C.CString("Hello from stdio\n")
    C.myprint(cs)
    C.free(unsafe.Pointer(cs))
}

Go から C 関数を呼び出す

cgo を使用して Go コードから呼び出された C コードから、トップレベルの Go 関数と関数変数の両方を呼び出すことが可能です。

グローバル関数

Go は特別な //export コメントを使用して、その関数を C コードで利用できるようにします。注:エクスポートを使用している場合、プリアンブルで C 関数を定義することはできません。

例えば、foo.c と foo.go の 2 つのファイルがあり、foo.go には以下が含まれます。

package gocallback

import "fmt"

/*
##include <stdio.h>
extern void ACFunction();
*/
import "C"

//export AGoFunction
func AGoFunction() {
    fmt.Println("AGoFunction()")
}

func Example() {
    C.ACFunction()
}

foo.c には以下が含まれます。

##include "_cgo_export.h"
void ACFunction() {
    printf("ACFunction()\n");
    AGoFunction();
}

関数変数

次のコードは、C コードから Go コールバックを呼び出す例を示しています。ポインタ渡しのルールにより、Go コードは関数値を直接 C に渡すことができません。代わりに間接参照を使用する必要があります。この例ではミューテックス付きのレジストリを使用していますが、C に渡せる値から Go 関数にマップする方法は他にもたくさんあります。

package gocallback

import (
    "fmt"
    "sync"
)

/*
extern void go_callback_int(int foo, int p1);

// normally you will have to define function or variables
// in another separate C file to avoid the multiple definition
// errors, however, using "static inline" is a nice workaround
// for simple functions like this one.
static inline void CallMyFunction(int foo) {
    go_callback_int(foo, 5);
}
*/
import "C"

//export go_callback_int
func go_callback_int(foo C.int, p1 C.int) {
    fn := lookup(int(foo))
    fn(p1)
}

func MyCallback(x C.int) {
    fmt.Println("callback with", x)
}

func Example() {
    i := register(MyCallback)
    C.CallMyFunction(C.int(i))
    unregister(i)
}

var mu sync.Mutex
var index int
var fns = make(map[int]func(C.int))

func register(fn func(C.int)) int {
    mu.Lock()
    defer mu.Unlock()
    index++
    for fns[index] != nil {
        index++
    }
    fns[index] = fn
    return index
}

func lookup(i int) func(C.int) {
    mu.Lock()
    defer mu.Unlock()
    return fns[i]
}

func unregister(i int) {
    mu.Lock()
    defer mu.Unlock()
    delete(fns, i)
}

Go 1.17 以降では、runtime/cgo パッケージは runtime/cgo.Handle メカニズムを提供し、上記の例を簡略化します。

package main

import (
    "fmt"
    "runtime/cgo"
)

/*
##include <stdint.h>

extern void go_callback_int(uintptr_t h, int p1);
static inline void CallMyFunction(uintptr_t h) {
    go_callback_int(h, 5);
}
*/
import "C"

//export go_callback_int
func go_callback_int(h C.uintptr_t, p1 C.int) {
    fn := cgo.Handle(h).Value().(func(C.int))
    fn(p1)
}

func MyCallback(x C.int) {
    fmt.Println("callback with", x)
}

func main() {
    h := cgo.NewHandle(MyCallback)
    C.CallMyFunction(C.uintptr_t(h))
    h.Delete()
}

関数ポインタコールバック

C コードはエクスポートされた Go 関数を明示的な名前で呼び出すことができます。しかし、C プログラムが関数ポインタを必要とする場合、ゲートウェイ関数を記述する必要があります。これは、cgo ツールが呼び出されるべき C のスタブを生成するため、Go 関数のアドレスを取得して C コードに渡すことができないためです。次の例は、特定の型の関数ポインタを必要とする C コードと統合する方法を示しています。

これらのソースファイルを $GOPATH/src/ccallbacks/ に配置します。以下でコンパイルして実行します。

$ gcc -c clibrary.c
$ ar cru libclibrary.a clibrary.o
$ go build
$ ./ccallbacks
Go.main(): calling C function with callback to us
C.some_c_func(): calling callback with arg = 2
C.callOnMeGo_cgo(): called with arg = 2
Go.callOnMeGo(): called with arg = 2
C.some_c_func(): callback responded with 3

goprog.go

package main

/*
##cgo CFLAGS: -I .
##cgo LDFLAGS: -L . -lclibrary

##include "clibrary.h"

int callOnMeGo_cgo(int in); // Forward declaration.
*/
import "C"

import (
    "fmt"
    "unsafe"
)

//export callOnMeGo
func callOnMeGo(in int) int {
    fmt.Printf("Go.callOnMeGo(): called with arg = %d\n", in)
    return in + 1
}

func main() {
    fmt.Printf("Go.main(): calling C function with callback to us\n")
    C.some_c_func((C.callback_fcn)(unsafe.Pointer(C.callOnMeGo_cgo)))
}

cfuncs.go

package main

/*

##include <stdio.h>

// The gateway function
int callOnMeGo_cgo(int in)
{
    printf("C.callOnMeGo_cgo(): called with arg = %d\n", in);
    int callOnMeGo(int);
    return callOnMeGo(in);
}
*/
import "C"

clibrary.h

##ifndef CLIBRARY_H
##define CLIBRARY_H
typedef int (*callback_fcn)(int);
void some_c_func(callback_fcn);
##endif

clibrary.c

##include <stdio.h>

##include "clibrary.h"

void some_c_func(callback_fcn callback)
{
    int arg = 2;
    printf("C.some_c_func(): calling callback with arg = %d\n", arg);
    int response = callback(2);
    printf("C.some_c_func(): callback responded with %d\n", response);
}

Go 文字列と C 文字列

Go 文字列と C 文字列は異なります。Go 文字列は、長さと文字列の最初の文字へのポインタの組み合わせです。C 文字列は、最初の文字へのポインタのみで、ヌル文字 '\0' の最初のインスタンスで終端されます。

Go は、次の 3 つの関数という形で、一方から他方への変換手段を提供します。

  • func C.CString(goString string) *C.char
  • func C.GoString(cString *C.char) string
  • func C.GoStringN(cString *C.char, length C.int) string

覚えておくべき重要なことは、C.CString() は適切な長さの新しい文字列を割り当てて返します。つまり、C 文字列はガベージコレクションされず、**あなた**が解放する必要があります。これを行う標準的な方法は次のとおりです。

// #include <stdlib.h>
import "C"
import "unsafe"
...
    var cmsg *C.char = C.CString("hi")
    defer C.free(unsafe.Pointer(cmsg))
    // do something with the C string

もちろん、C.free() を呼び出すために defer を使用する必要はありません。C 文字列はいつでも自由に解放できますが、それが確実に実行されるようにするのはあなたの責任です。

C 配列を Go スライスに変換する

C 配列は通常、ヌル終端されるか、どこか別の場所に長さが保持されます。

Go は、C 配列から新しい Go バイトスライスを作成するために次の関数を提供します。

  • func C.GoBytes(cArray unsafe.Pointer, length C.int) []byte

C 配列でバックアップされた Go スライスを作成するには(元のデータをコピーせずに)、実行時にこの長さを取得し、非常に大きな配列へのポインタへの型変換を使用し、その後、必要な長さにスライスする必要があります(Go 1.2 以降を使用している場合は cap も設定することを忘れないでください)。例えば(実行可能な例については https://go.dokyumento.jp/play/p/XuC0xqtAIC を参照)

import "C"
import "unsafe"
...
        var theCArray *C.YourType = C.getTheArray()
        length := C.getTheArrayLength()
        slice := (*[1 << 28]C.YourType)(unsafe.Pointer(theCArray))[:length:length]

Go 1.17 以降では、プログラムは代わりに unsafe.Slice を使用でき、同様に C 配列でバックアップされた Go スライスになります。

import "C"
import "unsafe"
...
        var theCArray *C.YourType = C.getTheArray()
        length := C.getTheArrayLength()
        slice := unsafe.Slice(theCArray, length) // Go 1.17

Go のガベージコレクタが基になる C 配列と相互作用しないこと、および C 側で解放された場合、スライスを使用する Go コードの動作は非決定論的であることに留意することが重要です。

一般的な落とし穴

構造体アラインメントの問題

Go はパック構造体(例えば、最大アラインメントが 1 バイトの構造体)をサポートしていないため、Go でパックされた C 構造体を使用することはできません。プログラムがコンパイルをパスしても、期待どおりに動作しません。使用するには、構造体をバイト配列/スライスとして読み書きする必要があります。

もう一つの問題は、Go の対応する型よりもアラインメント要件が低い型があり、その型が C ではアラインされているが Go のルールではアラインされていない場合、その構造体は Go で表現できないことです。例を挙げるとこれです(issue 7560)。

struct T {
    uint32_t pad;
    complex float x;
};

Go の complex64 は 8 バイトのアラインメントを持ちますが、C は 4 バイトしかありません(C は complex float を内部的に struct { float real; float imag; } として扱い、基本型ではないため)。この T 構造体は Go 表現を単に持ちません。この場合、構造体のレイアウトを制御できるのであれば、complex float を 8 バイトにアラインするように移動する方が良く、移動したくない場合は、この形式を使用すると 8 バイトにアラインする(そして 4 バイトを無駄にする)ように強制されます。

struct T {
   uint32_t pad;
   __attribute__((align(8))) complex float x;
};

ただし、構造体のレイアウトを制御できない場合は、cgo がその構造体を同等の Go 構造体に変換できないため、その構造体用のアクセサ C 関数を定義する必要があります。

//export とプリアンブルでの定義

Go ソースファイルが //export ディレクティブを使用している場合、コメント内の C コードは宣言のみ(extern int f();)を含めることができ、定義(int f() { return 1; } または int n;)は含めることができません。注:プリアンブルで定義された小さな関数については、static inline のトリックを使用してこの制限を回避できます(完全な例は上記を参照)。

Windows

Windows で cgo を使用するには、cgo でコンパイルする前に、まず gcc コンパイラ(例えば mingw-w64)をインストールし、PATH 環境変数に gcc.exe(など)が設定されている必要があります。

環境変数

Go の os.Getenv() は C.setenv() で設定された変数を認識しません。

テスト

_test.go ファイルは cgo を使用できません。


このコンテンツはGo Wikiの一部です。