CからGo関数を呼び出す


150

Cプログラム(たとえば、カーネルモジュールなど)とのインターフェイスとしてGoで記述された静的オブジェクトを作成しようとしています。

GoからのC関数の呼び出しに関するドキュメントは見つかりましたが、他の方法についてはあまりわかりません。私が見つけたのは、それは可能ですが複雑であるということです。

これが私が見つけたものです:

CとGo間のコールバックに関するブログ投稿

Cgoのドキュメント

Golangメーリングリストの投稿

誰もがこれを経験していますか?つまり、完全にGoで記述されたPAMモジュールを作成しようとしています。


11
少なくともGoで作成されていないスレッドからはできません。私はこれについて何度も激怒し、これが修正されるまでGoでの開発を中止しました。
Matt Joiner

可能だそうです。解決策はありませんか?
beatgammit '25年

Goは別の呼び出し規約とセグメント化されたスタックを使用します。gccgoでコンパイルされたGoコードをCコードにリンクできるかもしれませんが、私は自分のシステムでgccgoをビルドしていないため、これを試していません。
mkb

私は今、SWIGを使用して、それをしようとしている、と私は希望に満ちたよ...私は仕事には何ももらっていない、まだ... = '(私はメーリングリストに投稿さもののうまくいけば、誰かが私に慈悲を持っている。。
beatgammit

2
CからGoコードを呼び出すことはできますが、現時点では、GoランタイムをCアプリに埋め込むことはできません。これは重要ですが、微妙な違いです。
tylerl 2011年

回答:


126

CからGoコードを呼び出すことができますが、混乱を招きます。

このプロセスの概要は、リンク先のブログ投稿に記載されています。しかし、それがあまり役に立たないことがわかります。以下は、不要なビットのない短いスニペットです。少しわかりやすくなります。

package foo

// extern int goCallbackHandler(int, int);
//
// static int doAdd(int a, int b) {
//     return goCallbackHandler(a, b);
// }
import "C"

//export goCallbackHandler
func goCallbackHandler(a, b C.int) C.int {
    return a + b
}

// This is the public function, callable from outside this package.
// It forwards the parameters to C.doAdd(), which in turn forwards
// them back to goCallbackHandler(). This one performs the addition
// and yields the result.
func MyAdd(a, b int) int {
   return int( C.doAdd( C.int(a), C.int(b)) )
}

すべてが呼び出される順序は次のとおりです。

foo.MyAdd(a, b) ->
  C.doAdd(a, b) ->
    C.goCallbackHandler(a, b) ->
      foo.goCallbackHandler(a, b)

ここで覚えておくべき重要な点は、コールバック関数//exportには、Go側とexternC側のようにコメントを付ける必要があるということです。つまり、使用するコールバックはパッケージ内で定義する必要があります。

パッケージのユーザーがカスタムコールバック関数を提供できるようにするために、上記とまったく同じアプローチを使用しますが、Cに渡されるパラメーターとしてユーザーのカスタムハンドラー(単なる通常のGo関数)を提供しますサイドとしてvoid*。次に、パッケージ内のcallbackhandlerによって受信され、呼び出されます。

現在使用しているより高度な例を使用してみましょう。この場合、かなり重いタスクを実行するC関数があります。それは、USBデバイスからファイルのリストを読み取ります。これには時間がかかる場合があるため、アプリに進捗状況を通知する必要があります。これを行うには、プログラムで定義した関数ポインターを渡します。呼び出されるたびに、進行状況の情報をユーザーに表示するだけです。よく知られている署名があるため、独自のタイプを割り当てることができます。

type ProgressHandler func(current, total uint64, userdata interface{}) int

このハンドラーは、ユーザーが保持する必要があるすべてのものを保持できるinterface {}値とともに、いくつかの進捗情報(受信したファイルの現在の数とファイルの総数)を受け取ります。

次に、このハンドラーを使用できるように、CとGoの配管を記述する必要があります。幸いにも、ライブラリーから呼び出したいC関数を使用すると、タイプのユーザーデータ構造体を渡すことができますvoid*。つまり、保持したいものは何でも保持でき、質問もされず、そのままGoの世界に戻されます。このすべてを機能させるために、Goから直接ライブラリ関数を呼び出すのではなく、名前を付けるCラッパーを作成しますgoGetFiles()。GodataコールバックをuserdataオブジェクトとともにCライブラリに実際に提供するのは、このラッパーです。

package foo

// #include <somelib.h>
// extern int goProgressCB(uint64_t current, uint64_t total, void* userdata);
// 
// static int goGetFiles(some_t* handle, void* userdata) {
//    return somelib_get_files(handle, goProgressCB, userdata);
// }
import "C"
import "unsafe"

このgoGetFiles()関数は、コールバックの関数ポインターをパラメーターとして取りません。代わりに、ユーザーが提供したコールバックは、そのハンドラーとユーザー自身のuserdata値の両方を保持するカスタム構造体にパックされています。これをgoGetFiles()userdataパラメータとして渡します。

// This defines the signature of our user's progress handler,
type ProgressHandler func(current, total uint64, userdata interface{}) int 

// This is an internal type which will pack the users callback function and userdata.
// It is an instance of this type that we will actually be sending to the C code.
type progressRequest struct {
   f ProgressHandler  // The user's function pointer
   d interface{}      // The user's userdata.
}

//export goProgressCB
func goProgressCB(current, total C.uint64_t, userdata unsafe.Pointer) C.int {
    // This is the function called from the C world by our expensive 
    // C.somelib_get_files() function. The userdata value contains an instance
    // of *progressRequest, We unpack it and use it's values to call the
    // actual function that our user supplied.
    req := (*progressRequest)(userdata)

    // Call req.f with our parameters and the user's own userdata value.
    return C.int( req.f( uint64(current), uint64(total), req.d ) )
}

// This is our public function, which is called by the user and
// takes a handle to something our C lib needs, a function pointer
// and optionally some user defined data structure. Whatever it may be.
func GetFiles(h *Handle, pf ProgressFunc, userdata interface{}) int {
   // Instead of calling the external C library directly, we call our C wrapper.
   // We pass it the handle and an instance of progressRequest.

   req := unsafe.Pointer(&progressequest{ pf, userdata })
   return int(C.goGetFiles( (*C.some_t)(h), req ))
}

Cバインディングについては以上です。ユーザーのコードは非常に簡単です。

package main

import (
    "foo"
    "fmt"
)

func main() {
    handle := SomeInitStuff()

    // We call GetFiles. Pass it our progress handler and some
    // arbitrary userdata (could just as well be nil).
    ret := foo.GetFiles( handle, myProgress, "Callbacks rock!" )

    ....
}

// This is our progress handler. Do something useful like display.
// progress percentage.
func myProgress(current, total uint64, userdata interface{}) int {
    fc := float64(current)
    ft := float64(total) * 0.01

    // print how far along we are.
    // eg: 500 / 1000 (50.00%)
    // For good measure, prefix it with our userdata value, which
    // we supplied as "Callbacks rock!".
    fmt.Printf("%s: %d / %d (%3.2f%%)\n", userdata.(string), current, total, fc / ft)
    return 0
}

これはすべて、実際よりもはるかに複雑に見えます。前の例とは異なり、呼び出し順序は変更されていませんが、チェーンの最後に2つの追加呼び出しがあります。

順序は次のとおりです。

foo.GetFiles(....) ->
  C.goGetFiles(...) ->
    C.somelib_get_files(..) ->
      C.goProgressCB(...) ->
        foo.goProgressCB(...) ->
           main.myProgress(...)

はい、別のスレッドが出てくると、これらすべてが私たちの顔で爆発する可能性があることを私は理解しています。具体的には、Goによって作成されていないもの。残念ながら、それは現時点では現状です。
2005年

17
これは本当に良い答えです。質問に直接答えることはありませんが、答えがないためです。いくつかの情報源によると、エントリポイントはGoである必要があり、Cにすることはできません。これは本当に問題を解決したので、これを正しいものとしてマークしています。ありがとう!
beatgammit、2011年

@jimtそれはどのようにガベージコレクタと統合されますか?具体的には、プライベートプログレスリクエストインスタンスはいつ収集されますか?(Go to New、つまりunsafe.Pointer)。また、SQL * 3のようなAPIでvoid *ユーザーデータだけでなく、ユーザーデータ用のオプションの「削除」関数も使用しますか?これを使用してGCとやり取りし、「Go側がそれを参照しなくなった場合は、そのユーザーデータを取り戻すことはできますか?」と伝えます。
ddevienne

6
:あなたはシンプルなテクニックを示し答えを探しているなら、ゴー1.5のとおりC.を参照してください、この質問から行くを呼び出すためのより良いサポートがあるstackoverflow.com/questions/32215509/...
ガブリエル南部の

2
Go 1.6以降では、このアプローチは機能せず、「呼び出しが戻った後、CコードはGoポインターのコピーを保持しない可能性があります。」ルールおよび実行時の「パニック:ランタイムエラー:cgo引数にはGoポインターへのGoポインターがあります」エラー
kaspersky

56

gccgoを使用する場合、混乱を招くことはありません。これはここで動作します:

foo.go

package main

func Add(a, b int) int {
    return a + b
}

bar.c

#include <stdio.h>

extern int go_add(int, int) __asm__ ("example.main.Add");

int main() {
  int x = go_add(2, 3);
  printf("Result: %d\n", x);
}

Makefile

all: main

main: foo.o bar.c
    gcc foo.o bar.c -o main

foo.o: foo.go
    gccgo -c foo.go -o foo.o -fgo-prefix=example

clean:
    rm -f main *.o

コードを文字列で実行するgo package main func Add(a, b string) int { return a + b }と、「undefined _go_string_plus」エラーが発生します
TruongSinh

2
TruongSinh、おそらくの代わりにを使用cgoしたいでしょう。golang.org/cmd/cgoを参照してください。そういえば、.goファイルで「文字列」タイプを使用し、.cファイルを変更して関数を含めることは完全に可能です。これは機能します:ix.io/dZBgogccgo__go_string_plus
Alexander


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.