passing function pointer to the C code using cgo

后端 未结 2 1242
囚心锁ツ
囚心锁ツ 2021-02-06 04:04

Starting from Go v1.6 cgo changed the rules of passing pointers to the C code golang/go#12416. The example of invoking a dynamic Go callback from C code from the wiki doesn\'t

相关标签:
2条回答
  • 2021-02-06 04:52

    It depends exactly what you need to do with the callback function - but a trick that might work is to not pass the Go function, but a pointer to a struct with the function on it that you want.

    For example:

    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))
    }
    

    Another option might be to use a global variable as you had in your example, and then forgo passing anything useful in the pointer to C.

    Like this:

    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)
    }
    

    It possibly depends on what your C callback needs to do.

    If neither of these work it might be possible to do some tricks with channels to send the value you need (and keep it in scope).

    0 讨论(0)
  • 2021-02-06 05:01

    Starting from Go 1.6 cgo has new rules.

    Go code may pass a Go pointer to C provided that the Go memory to which it points does not contain any Go pointers.

    [source]

    These rules are checked during the runtime, and if violated program crashes. At the moment it is possible to disable checks using GODEBUG=cgocheck=0 environment variable. But in the future, that might stop working.

    So it is not possible anymore to pass a pointer to C code, if the memory to which it is pointing stores a Go function/method pointer. There are several ways to overcome this limitations, but I guess in most of them you should store a synchronized data structure which represents the correspondence between a certain id and the actual pointer. This way you can pass an id to the C code, not a pointer.

    The code solving this problem might look like this:

    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)
    }
    

    This code comes from the (updated) wiki page.

    0 讨论(0)
提交回复
热议问题