使用 cgo 将函数指针传递给 C 代码

从 Go v1.6 开始,cgo 更改了将指针传递给 C 代码golang/go#12416 的规则。从 wiki 的 C 代码调用动态 Go 回调的示例不再有效。


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 来隐藏语言中的指针这样的技巧。


喵喵时光机
浏览 592回答 3
3回答

当年话下

从 Go 1.6 开始,cgo 有了新的规则。Go 代码可以将 Go 指针传递给 C,前提是它指向的 Go 内存不包含任何 Go 指针。在运行时检查这些规则,如果违反程序崩溃。目前可以使用GODEBUG=cgocheck=0环境变量禁用检查。但在未来,这可能会停止工作。因此,如果指针指向的内存存储了 Go 函数/方法指针,则无法再将指针传递给 C 代码。有几种方法可以克服这个限制,但我想在大多数方法中你应该存储一个同步的数据结构,它表示某个 id 和实际指针之间的对应关系。这样,您可以将 id 传递给 C 代码,而不是指针。解决此问题的代码可能如下所示:package gocallbackimport (    "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_intfunc 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.Mutexvar index intvar 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 页面。

眼眸繁星

解决方案分为三个部分。//export在 Go 函数上使用注释告诉cgo工具为其生成 C 包装器 ( https://pkg.go.dev/cmd/cgo#hdr-C_references_to_Go )。在cgoC 序言中前向声明该 C 标识符,以便您可以在 Go 代码 ( https://golang.org/issue/19837 ) 中引用 C 包装器。使用 Ctypedef将该指针转换为正确的 C 类型,解决一个cgo错误 ( https://golang.org/issue/19835 )。把它们放在一起:package main/*static void invoke(void (*f)()) {    f();}void go_print_hello();  // https://golang.org/issue/19837typedef void (*closure)();  // https://golang.org/issue/19835*/import "C"import "fmt"//export go_print_hellofunc go_print_hello() {    fmt.Println("Hello, !")}func main() {    C.invoke(C.closure(C.go_print_hello))}

慕容3067478

这完全取决于您需要对回调函数执行什么操作 - 但一个可能有效的技巧是不传递 Go 函数,而是传递一个指向带有您想要的函数的结构的指针。例如:package mainimport (    "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_intfunc 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 mainimport (    "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_intfunc 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 = MyCallbackfunc main() {    C.CallMyFunction(nil)}这可能取决于您的 C 回调需要做什么。如果这些都不起作用,则可以使用通道做一些技巧来发送您需要的值(并将其保持在范围内)。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go