如何将 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:
我确定回调在 T
初始化后注册并在 T
删除之前删除。
我可能有多个 T
的实例,其中一些可能同时用于回调的 'source' (所以 T
不是某种单音).
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/... 这是因为:
- 如果没有(其他)Go 指针指向同一个变量(内存),那么 Go 运行时认为这块内存是空闲的,可以重复使用,
- 或者,如果有其他 Go 指针指向同一个变量(内存),那么 Go 可以在内存中移动这个变量。
因此,仅当结构没有指向 Go 内存的指针时才使用这种方式。通常这意味着结构仅包含原始字段(整数、浮点数、布尔值)。
正确(困难)的方法:
为每个 T
类型的对象分配 id(例如整数类型)并将此 id 传递给 C。在导出的回调中,您应该将 id 转换回对象。这是正确的方式,没有限制,所以这种方式可以一直使用。但是这种方式需要维护一些array/slice/map来在对象和id之间进行转换。此外,此转换可能需要一些线程安全的同步(因此请参阅 sync.Mutex
和 sync.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
)。
在 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:
我确定回调在
T
初始化后注册并在T
删除之前删除。我可能有多个
T
的实例,其中一些可能同时用于回调的 'source' (所以T
不是某种单音).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/... 这是因为:
- 如果没有(其他)Go 指针指向同一个变量(内存),那么 Go 运行时认为这块内存是空闲的,可以重复使用,
- 或者,如果有其他 Go 指针指向同一个变量(内存),那么 Go 可以在内存中移动这个变量。
因此,仅当结构没有指向 Go 内存的指针时才使用这种方式。通常这意味着结构仅包含原始字段(整数、浮点数、布尔值)。
正确(困难)的方法:
为每个 T
类型的对象分配 id(例如整数类型)并将此 id 传递给 C。在导出的回调中,您应该将 id 转换回对象。这是正确的方式,没有限制,所以这种方式可以一直使用。但是这种方式需要维护一些array/slice/map来在对象和id之间进行转换。此外,此转换可能需要一些线程安全的同步(因此请参阅 sync.Mutex
和 sync.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
)。