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))
}

CからGo関数を呼び出す

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つの関数の形で、一方からもう一方への変換手段を提供します。

覚えておくべき重要な点の1つは、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

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

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

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

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

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構造体を使用することはできません。プログラムがコンパイルにパスしても、期待どおりには動作しません。使用する場合は、構造体をバイト配列/スライスとして読み書きする必要があります。

もう1つの問題は、一部の型がGoの対応するものよりも低い配置要件を持つことであり、その型がCでは配置されているがGoのルールでは配置されていない場合、その構造体はGoでは単純に表現できません。例としてはこれがあります(issue 7560

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

Goのcomplex64は8バイトの配置を持ちますが、Cは4バイトしか持ちません(Cは内部的に複素数をstruct { float real; float imag; }として扱い、基本型として扱わないため)、このT構造体にはGo表現がありません。この場合、構造体のレイアウトを制御できる場合は、複素数を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を使用するには、まずgccコンパイラ(例:mingw-w64)をインストールし、gcc.exeなどをPATH環境変数に設定する必要があります。そうしないと、cgoでのコンパイルは機能しません。

環境変数

Goのos.Getenv()は、C.setenv()によって設定された変数を見ることができません。

テスト

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


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