如何将 GoLang 结构方法作为 C 回调传递

How to pass GoLang's struct's method as C callback

在 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 用作 callback 用于 register_callback

在 MS Windows for winapi 上,我为此将 C.CallbackFunc(unsafe.Pointer(syscall.NewCallback(Obj.M))) 之类的 smth 传递给 register_callback(不确定它是否完全正确,但至少这是有效的)。但是对于非Windows系统,哪里没有NewCallback

PS:

  1. 我确定回调在 T 初始化后注册并在 T 删除之前删除。

  2. 我可能有多个 T 的实例,其中一些可能同时用于回调的 'source' (所以 T 不是某种单音).

  3. Function pointer callbacks 在 GoLang 的 wiki 中使用 gateway function,但我看不出如何将它与 struct 的方法充分结合使用。

基本想法:

使用导出的回调作为 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));

哪里有 3 种方法:错误(和简单)、有限(中等)和正确(困难)。

错误(且简单)的方式:

将指向 Go 结构的指针传递给 C(与原始答案一样)。这是完全错误的,因为 Go 运行时可以在内存中移动结构。通常这个操作是透明的(所有 Go 指针都会自动更新)。但是 C 内存中指向此结构的指针将 不会 更新,程序可能会 crash/UB/... 当尝试使用它时。 不要使用这种方式

有限(中等)方式:

与之前类似,但在 C 内存中分配了 Go 结构:

Obj = (*T)(C.calloc(C.size_t(unsafe.Sizeof(T{}))))

在这种情况下,Obj 不能被 Go 运行时移动,因为它在 C 内存中。但是现在如果 Obj 有指向 Go 内存的指针(带 * 变量的字段、映射、切片、通道、函数指针,...)那么这也可能导致 crash/UB/... 这是因为:

  1. 如果没有(其他)Go 指针指向同一个变量(内存),那么 Go 运行时认为这块内存是空闲的,可以重复使用,
  2. 或者,如果有其他 Go 指针指向同一个变量(内存),那么 Go 可以在内存中移动这个变量。

因此,仅当结构没有指向 Go 内存的指针时才使用这种方式。通常这意味着结构仅包含原始字段(整数、浮点数、布尔值)。

正确(困难)的方法:

为每个 T 类型的对象分配 id(例如整数类型)并将此 id 传递给 C。在导出的回调中,您应该将 id 转换回对象。这是正确的方式,没有限制,所以这种方式可以一直使用。但是这种方式需要维护一些array/slice/map来在对象和id之间进行转换。此外,此转换可能需要一些线程安全的同步(因此请参阅 sync.Mutexsync.RWMutex)。

原回答:

不是最佳答案并且有限制,但没有其他建议。在我的例子中,我可以将额外的数据传递给 register_callback。此数据将在每次调用时传回 callback。所以我将 unsafe.Pointer(Obj) 作为数据传递并使用 gateway function:

//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));

PS:但仍然想知道在一般情况下如何更好地做到这一点(没有 additional data)。