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

Iak*_*dov 8 go cgo

从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()
}
Run Code Online (Sandbox Code Playgroud)

输出如下所示:

panic: runtime error: cgo argument has Go pointer to Go pointer
Run Code Online (Sandbox Code Playgroud)

今天这样做的正确方法是什么?最好不要通过将指针转换为uintptr_t来隐藏指针.

Iak*_*dov 10

从Go 1.6 cgo开始有新的规则.

Go代码可以将Go指针传递给C,前提是它指向的Go内存不包含任何Go指针.

[资源]

在运行时期间检查这些规则,如果违反了程序崩溃.目前,可以使用GODEBUG=cgocheck=0环境变量禁用检查.但在未来可能会停止工作.

因此,如果指向它的内存存储Go函数/方法指针,则不可能再传递指向C代码的指针.有几种方法可以克服这些限制,但我想在大多数方法中你应该存储一个同步数据结构,它代表某个id和实际指针之间的对应关系.这样您就可以将id传递给C代码,而不是指针.

解决此问题的代码可能如下所示:

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)
}
Run Code Online (Sandbox Code Playgroud)

此代码来自(更新的)Wiki页面.


bcm*_*lls 8

该解决方案分为三个部分。

\n
    \n
  1. 使用//exportGo 函数的注释告诉cgo工具为其生成 C 包装器 ( https://pkg.go.dev/cmd/cgo#hdr-C_references_to_Go )。

    \n
  2. \n
  3. 在 C 序言中前向声明 C 标识符cgo,以便您可以在 Go 代码中引用 C 包装器 ( https://golang.org/issue/19837 )。

    \n
  4. \n
  5. 使用 Ctypedef将该指针转换为正确的 C 类型,解决cgo错误 ( https://golang.org/issue/19835 )。

    \n
  6. \n
\n

把它们放在一起:

\n
package main\n\n/*\nstatic void invoke(void (*f)()) {\n    f();\n}\n\nvoid go_print_hello();  // https://golang.org/issue/19837\n\ntypedef void (*closure)();  // https://golang.org/issue/19835\n*/\nimport "C"\n\nimport "fmt"\n\n//export go_print_hello\nfunc go_print_hello() {\n    fmt.Println("Hello, \xef\xa0\x80!")\n}\n\nfunc main() {\n    C.invoke(C.closure(C.go_print_hello))\n}\n
Run Code Online (Sandbox Code Playgroud)\n