2016-05-11 19 views
2

Go v1.6から開始cgoはポインタをCコードgolang/go#12416に渡すルールを変更しました。 WikiからCコードから動的Goコールバックを呼び出す例は、もう動作しません。cgoを使用してCコードへのポインタを渡す

package main 

import (
    "fmt" 
    "unsafe" 
) 

/* 
    extern void go_callback_int(void* 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(void* pfoo) { 
     go_callback_int(pfoo, 5); 
     } 
*/ 
import "C" 

//export go_callback_int 
func go_callback_int(pfoo unsafe.Pointer, p1 C.int) { 
    foo := *(*func(C.int))(pfoo) 
    foo(p1) 
} 

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

// we store it in a global variable so that the garbage collector 
// doesn't clean up the memory for any temporary variables created. 
var MyCallbackFunc = MyCallback 

func Example() { 
    C.CallMyFunction(unsafe.Pointer(&MyCallbackFunc)) 
} 

func main() { 
    Example() 
} 

出力は次のようになります。

panic: runtime error: cgo argument has Go pointer to Go pointer 

今日これを行うための適切な方法は何ですか? uintptr_tに変換することで、言語からポインタを隠すようなハックがないことが好ましい。

答えて

4

Goから開始1.6 cgoには新しいルールがあります。

Goコードは、それが指すGoメモリにGoポインタが含まれていない場合、GoポインタをCに渡すことがあります。

[source]

これらの規則は、実行時にチェックし、違反したプログラムがクラッシュした場合されています。現時点では、環境変数GODEBUG=cgocheck=0を使用してチェックを無効にすることは可能です。しかし、将来、それは動作を停止する可能性があります。

したがって、ポインタが指しているメモリがGo関数/メソッドポインタを格納している場合、Cコードへのポインタを渡すことはできません。この制限を克服するにはいくつかの方法がありますが、ほとんどの場合、特定のIDと実際のポインタの間の対応関係を表す同期データ構造を格納する必要があります。この方法では、ポインタではなくCコードにIDを渡すことができます。

この問題を解決するためのコードは次のようになります。

package gocadllback 

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

このコードは(更新)wiki pageから来ています。

-1

これは、コールバック関数で何をする必要があるかによって異なりますが、機能するかもしれないトリックは、Go関数を渡すのではなく、必要な機能を持つ構造体へのポインタです。例えば

package main 

import (
    "fmt" 
    "unsafe" 
) 

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

//export go_callback_int 
func go_callback_int(pfoo unsafe.Pointer, p1 C.int) { 
    foo := (*Test)(pfoo) 
    foo.cb(p1) 
} 

type Test struct { 
} 

func (s *Test) cb(x C.int) { 
    fmt.Println("callback with", x) 
} 

func main() { 
    data := &Test{} 
    C.CallMyFunction(unsafe.Pointer(&data)) 
} 

別のオプションは、あなたがあなたの例では持っていたとして、グローバル変数を使用して、このようにC.

へのポインタで有用な何かを渡す見送るために次のようになります。

package main 

import (
    "fmt" 
    "unsafe" 
) 

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

//export go_callback_int 
func go_callback_int(_ unsafe.Pointer, p1 C.int) { 
    MyCallbackFunc(p1) 
} 

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

// we store it in a global variable so that the garbage collector 
// doesn't clean up the memory for any temporary variables created. 
var MyCallbackFunc = MyCallback 

func main() { 
    C.CallMyFunction(nil) 
} 

おそらくCコールバックが何をする必要があるかによって異なります。

これらのいずれも動作しない場合は、チャネルを使用して必要な値を送信して(範囲内に保つ)いくつかのトリックを行うことができます。

+0

新しいcgoルールの状態は次のようになっています。Go関数を渡すのではなく、関数の構造体へのポインタを渡すのではなく、あなたが示唆していることを理解している限り、 _Goコードは、それが指すGoメモリがGoポインタを含まないという条件で、GoポインタをCに渡すことができる。 さらに、ランタイムチェックを使用してルールを適用します。 –

関連する問題