передача указателя функции на код C с помощью cgo

Начиная с Go v1.6 cgo изменил правила передачи указателей на код C golang/go#12416< /а>. Пример вызова динамического обратного вызова Go из кода C из вики больше не работает.

package main

import (
    "fmt"
    "unsafe"
)

/*
   extern void go_callback_int(void* 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(void* pfoo) {
       go_callback_int(pfoo, 5);
       }
*/
import "C"

//export go_callback_int
func go_callback_int(pfoo unsafe.Pointer, p1 C.int) {
    foo := *(*func(C.int))(pfoo)
    foo(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 Example() {
    C.CallMyFunction(unsafe.Pointer(&MyCallbackFunc))
}

func main() {
    Example()
}

Вывод выглядит следующим образом:

panic: runtime error: cgo argument has Go pointer to Go pointer

Как правильно это сделать сегодня? Желательно без хаков вроде сокрытия указателя из языка путем преобразования его в uintptr_t.


person Iakov Davydov    schedule 11.05.2016    source источник


Ответы (2)


Начиная с Go 1.6 в cgo появились новые правила.

Код Go может передать указатель Go на C при условии, что память Go, на которую он указывает, не содержит указателей Go.

[источник]

Эти правила проверяются во время выполнения, и при нарушении программа вылетает. На данный момент можно отключить проверки с помощью переменной окружения GODEBUG=cgocheck=0. Но в будущем это может перестать работать.

Таким образом, больше невозможно передать указатель на код C, если в памяти, на которую он указывает, хранится указатель на функцию/метод Go. Есть несколько способов обойти это ограничение, но я думаю, что в большинстве из них вы должны хранить синхронизированную структуру данных, которая представляет соответствие между определенным идентификатором и фактическим указателем. Таким образом, вы можете передать коду C идентификатор, а не указатель.

Код, решающий эту проблему, может выглядеть так:

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

Этот код взят с (обновленной) вики-страницы.

person Iakov Davydov    schedule 05.06.2016

Это зависит именно от того, что вам нужно делать с функцией обратного вызова, но трюк, который может сработать, заключается в том, чтобы не передавать функцию Go, а указатель на структуру с функцией, которую вы хотите.

Например:

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

Другой вариант может состоять в том, чтобы использовать глобальную переменную, как в вашем примере, а затем отказаться от передачи чего-либо полезного в указатель на C.

Как это:

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

Возможно, это зависит от того, что должен делать ваш обратный вызов C.

Если ни один из них не работает, возможно, можно будет проделать некоторые трюки с каналами, чтобы отправить нужное вам значение (и сохранить его в пределах области действия).

person Ash Berlin-Taylor    schedule 22.05.2016
comment
Насколько я понимаю, то, что вы предлагаете (не передавать функцию Go, а указатель на структуру с функцией, которую вы хотите) не будет работать, поскольку новые правила cgo гласят: Код Go может передать указатель Go на C при условии, что память Go, на которую он указывает, не содержит указателей Go. Кроме того, для обеспечения соблюдения правила используются проверки во время выполнения. - person Iakov Davydov; 05.06.2016
comment
@IakovDavydov Я попробовал первый пример, и у меня это сработало, может быть, теперь разрешите это? - person Ank i zle; 23.05.2020