如何将Go语言结构体的方法作为C回调函数传递

4
在Go源代码中,我有以下内容:
type T struct {
    // some data
}

func (t *T)M(arg0 SomeType1) {
    // some computations
}

var Obj *T

我有一些与C语言相关的源代码

// SomeType1C is equivalent to SomeType1.
typedef void (*CallbackFunc)(SomeType1C);

// callback will be called !after! register_callback function returns.
void register_callback(CallbackFunc callback); 

我想在C语言中将Obj.M作为register_callbackcallback使用。

在使用winapi的MS Windows上,我会传递类似于C.CallbackFunc(unsafe.Pointer(syscall.NewCallback(Obj.M)))这样的东西给register_callback(不确定是否完全正确,但至少能够工作)。但是在非Windows系统中没有NewCallback

PS:

  1. 我确认在初始化T之后注册了回调,并在移除T之前移除了回调。

  2. 我可能有多个T实例,并且其中一些可能同时用于回调的“源”(因此T不是某种单例)。

  3. GoLang's Wiki中的Function pointer callbacks使用gateway function,但我不知道如何使用它来适当地使用结构体的方法。

1个回答

2

基本思路:

使用导出的回调函数作为C和Go之间的代理:

//export callback
func callback(data0 SomeType1C, data1 Data){ // data1 - data passed to register_callback_with_data
    obj := convertDataToObj(data1)       
    obj.M(data0)
}

然后像这样注册它:

register_callback_with_data(callback, convertObjToData(obj));

有三种方法:错误(容易),受限(中等)和正确(困难)。
错误(容易)方法:
将指针传递到Go结构体中,然后在C中使用(与原始答案相同)。这是完全错误的,因为Go运行时可以移动结构体。通常,此操作是透明的(所有Go指针将自动更新)。但是指向该结构体的C内存中的指针不会被更新,当尝试使用它时,程序可能会崩溃/UB/...。 不要使用这种方式
受限(中等)方式:
类似于上一个方法,但使用在C内存中分配的Go结构体:
Obj = (*T)(C.calloc(C.size_t(unsafe.Sizeof(T{}))))

在这种情况下,由于Obj在C内存中,因此Go运行时无法移动它。但是,如果Obj具有指向Go内存的指针(带有*变量的字段、映射、切片、通道、函数指针等),那么这也可能导致崩溃/UB/... 这是因为:
1. 如果没有(其他)指向相同变量(内存)的Go指针,则Go运行时认为该内存空闲并可以重用, 2. 或者,如果存在指向同一变量(内存)的其他Go指针,则Go可以将此变量移动到内存中。
因此,仅当结构体没有指向Go内存的指针时使用此方法。通常,这意味着结构体仅包含原始字段(int、float、bool)。
正确(且困难)的方法:
为类型T的每个对象分配一个ID(例如整数类型),并将此ID传递给C。在导出的回调中,您应将ID转换回对象。这是无限制的正确方式,因此始终可以使用此方式。但是,这种方式需要维护一些数组/切片/映射以在对象和ID之间进行转换。此外,此转换可能需要某些同步以实现线程安全(因此请参见sync.Mutex和sync.RWMutex)。
原始答案:不是最佳答案,也有限制,但没有其他建议。在我的情况下,我可以将附加数据传递给register_callback。这些数据将在每次调用时传递回callback。因此,我将unsafe.Pointer(Obj)作为数据传递,并使用网关函数:
//export callback
func callback(data SomeType1C, additionalData unsafe.Pointer){
    obj := (*T)(additionalData) // Get original Obj (pointer to instance of T)
    dataGo := *(*SomeType1)(unsafe.Pointer(&data)) // Cast data from C to Go type
    obj.M(dataGo)
}

然后像这样注册它:

register_callback_with_data(callback, unsafe.Pointer(Obj));

备注:但是我还是想知道如何在一般情况下更好地做到这一点(不需要额外的数据)。


很棒的答案。https://github.com/mattn/go-pointer可以帮助我们以“正确而困难”的方式维护mutex保护的map[unsafe.Pointer]interface{}。这种方法也在这里展示:https://github.com/golang/go/wiki/cgo#function-variables另一个不错的阅读是https://golang.org/cmd/cgo/#hdr-Passing_pointers - mattes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接