使用 cgo 将函数指针传递给 C 代码
passing function pointer to the C code using 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()
}
输出如下所示:
panic: runtime error: cgo argument has Go pointer to Go pointer
今天这样做的正确方法是什么?最好不要通过将指针转换为 uintptr_t.
来隐藏语言中的指针。
这完全取决于您需要对回调函数执行的操作 - 但一个可能有效的技巧是不传递 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 回调需要做什么。
如果这些都不起作用,则可能无法对通道进行一些技巧来发送您需要的值(并将其保持在范围内)。
从Go 1.6开始cgo有新规则。
Go code may pass a Go pointer to C provided that the Go memory to which it points does not contain any Go pointers.
这些规则在运行时进行检查,如果违反则程序崩溃。目前可以使用 GODEBUG=cgocheck=0
环境变量禁用检查。但在未来,这可能会停止工作。
因此,如果它指向的内存存储了 Go function/method 指针,则不再可能将指针传递给 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)
}
此代码来自(已更新)wiki page。
解决方案分为三个部分。
在 Go 函数上使用 //export
注释告诉 cgo
工具为其生成 C 包装器 (https://pkg.go.dev/cmd/cgo#hdr-C_references_to_Go)。
前向声明cgo
C 序言中的C 标识符,以便您可以在Go 代码中引用C 包装器(https://golang.org/issue/19837)。
使用 C typedef
将该指针转换为正确的 C 类型,解决 cgo
错误 (https://golang.org/issue/19835)。
综合起来:
package main
/*
static void invoke(void (*f)()) {
f();
}
void go_print_hello(); // https://golang.org/issue/19837
typedef void (*closure)(); // https://golang.org/issue/19835
*/
import "C"
import "fmt"
//export go_print_hello
func go_print_hello() {
fmt.Println("Hello, !")
}
func main() {
C.invoke(C.closure(C.go_print_hello))
}
从 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.
来隐藏语言中的指针。这完全取决于您需要对回调函数执行的操作 - 但一个可能有效的技巧是不传递 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 回调需要做什么。
如果这些都不起作用,则可能无法对通道进行一些技巧来发送您需要的值(并将其保持在范围内)。
从Go 1.6开始cgo有新规则。
Go code may pass a Go pointer to C provided that the Go memory to which it points does not contain any Go pointers.
这些规则在运行时进行检查,如果违反则程序崩溃。目前可以使用 GODEBUG=cgocheck=0
环境变量禁用检查。但在未来,这可能会停止工作。
因此,如果它指向的内存存储了 Go function/method 指针,则不再可能将指针传递给 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)
}
此代码来自(已更新)wiki page。
解决方案分为三个部分。
在 Go 函数上使用
//export
注释告诉cgo
工具为其生成 C 包装器 (https://pkg.go.dev/cmd/cgo#hdr-C_references_to_Go)。前向声明
cgo
C 序言中的C 标识符,以便您可以在Go 代码中引用C 包装器(https://golang.org/issue/19837)。使用 C
typedef
将该指针转换为正确的 C 类型,解决cgo
错误 (https://golang.org/issue/19835)。
综合起来:
package main
/*
static void invoke(void (*f)()) {
f();
}
void go_print_hello(); // https://golang.org/issue/19837
typedef void (*closure)(); // https://golang.org/issue/19835
*/
import "C"
import "fmt"
//export go_print_hello
func go_print_hello() {
fmt.Println("Hello, !")
}
func main() {
C.invoke(C.closure(C.go_print_hello))
}