drdl18946
2018-02-02 16:37
浏览 366
已采纳

如何将GoLang的struct方法作为C回调传递

In Go source I have

type T struct {
    // some data
}

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

var Obj *T

In C sources I have

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

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

I would like to use Obj.M as callback for register_callback in C.

On MS Windows for winapi I pass smth like C.CallbackFunc(unsafe.Pointer(syscall.NewCallback(Obj.M))) to register_callback for this (not sure is it fully correct, but at least this works). But where is no NewCallback for non-Windows systems.

PS:

  1. I'm sure that callback is registered after T is initialised and removed before T is removed.

  2. I may have multiple instances of T and some of them may be used to callback's 'source' at same time (so T is not some kind of singltone).

  3. Function pointer callbacks in GoLang's wiki uses gateway function, but I don't see how to adequate use it with struct's method.

图片转代码服务由CSDN问答提供 功能建议

在Go源代码中,我有

 类型T结构{\  n //一些数据
} 
 
func(t * T)M(arg0 SomeType1){
 //一些计算
} 
 
var Obj * T 
   \  n 
 

在C源代码中我有

  // SomeType1C等同于SomeType1。
typedef void(* CallbackFunc)(SomeType1C); 
 
 // 回调将被称为!之后!  
register_callback(CallbackFunc callback);  
   
 
 

我想将 Obj.M 用作 register_callback callback 在C中。

在Winapi的MS Windows上,我将 C.CallbackFunc(unsafe.Pointer(syscall.NewCallback(Obj.M)))传递给< 为此,请执行代码> register_callback (不确定是否完全正确,但至少可以正常运行)。 但是在非Windows系统上没有 NewCallback

PS:

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

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

  3. 函数指针回调在GoLang的Wiki中使用 gateway函数,但是我看不出如何在struct的方法中充分使用它 。

  • 写回答
  • 好问题 提建议
  • 关注问题
  • 收藏
  • 邀请回答

1条回答 默认 最新

  • doumi7861 2018-02-13 11:58
    已采纳

    Base idea:

    Use exported callback as a proxy between C and Go:

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

    and register it like this:

    register_callback_with_data(callback, convertObjToData(obj));
    

    Where are 3 ways: wrong (and easy), limited (medium) and right (hard).

    Wrong (and easy) way:

    Pass pointer to Go struct into C (as in original answer). This is totally wrong because Go runtime can move struct in memory. Usually this operation is transparent (all Go pointers will be updated automatically). But pointers in C memory to this struct will not be updated and program may crash/UB/... when tries to use it. Do not use this way.

    Limited (medium) way:

    Similar to previous, but with Go struct allocated in C memory:

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

    In this case Obj can not be moved by Go runtime because it is in C memory. But now if Obj has pointers to Go memory (fields with *-variables, maps, slices, channels, function-pointers, ...) then this also may cause crash/UB/... This is because:

    1. if there are no (other) Go pointers to the same variable (memory), then Go runtime thinks that this memory is free and can be reused,
    2. or, if there is other Go pointer to same variable (memory), then Go can move this variable in memory.

    So, use this way only if struct has no pointers to Go memory. Usually this means that struct contains only primitive fields (ints, floats, bool).

    Right (and hard) way:

    Assign id (of integer type for example) for each object of type T and pass this id into C. In exported callback you should convert id back to object. This is right way with no limitation, so this way may be used always. But this way requires to maintain some array/slice/map to convert between objects and ids. Moreover, this convertation may require some synchronization for thread-safe (so see sync.Mutex and sync.RWMutex).

    Original answer:

    Not best answer and has restrictions, but no other suggested. In my case I can pass additional data to register_callback. This data will be passed back to callback on each call. So I pass unsafe.Pointer(Obj) as data and use 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)
    }
    

    and register it like this:

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

    PS: but still want to know how to do this better in general case (without additional data).

    评论
    解决 无用
    打赏 举报

相关推荐 更多相似问题