CからGo関数を呼び出す 質問する

CからGo関数を呼び出す 質問する

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

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

私が見つけたものは次のとおりです:

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

Cgo ドキュメント

Golangメーリングリスト投稿

これに経験のある人はいますか? 簡単に言うと、完全に Go で書かれた PAM モジュールを作成しようとしています。

ベストアンサー1

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 側でも C 側でもコメントでマークする必要があるということですextern。つまり、使用したいコールバックはすべてパッケージ内で定義する必要があります。

パッケージのユーザーがカスタム コールバック関数を指定できるようにするには、上記とまったく同じアプローチを使用しますが、ユーザーのカスタム ハンドラー (通常の Go 関数) をパラメーターとして指定し、それを として C 側に渡しますvoid*。その後、パッケージ内のコールバック ハンドラーがそれを受け取って呼び出します。

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

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

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

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

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()この関数は、コールバックの関数ポインタをパラメータとして受け取らないことに注意してください。代わりに、ユーザーが指定したコールバックは、そのハンドラとユーザー自身のユーザーデータ値の両方を保持するカスタム構造体にパックされます。これをgoGetFiles()ユーザーデータパラメータとして渡します。

// 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(...)

おすすめ記事