Możesz wywołać kod Go z C. jest to jednak myląca propozycja.
Proces jest opisany w poście na blogu, do którego prowadzi łącze. Ale widzę, że nie jest to zbyt pomocne. Oto krótki fragment bez zbędnych bitów. Powinno to trochę wyjaśnić.
package foo
// extern int goCallbackHandler(int, int);
//
// static int doAdd(int a, int b) {
// return goCallbackHandler(a, b);
// }
import "C"
//export goCallbackHandler
func goCallbackHandler(a, b C.int) C.int {
return a + b
}
// This is the public function, callable from outside this package.
// It forwards the parameters to C.doAdd(), which in turn forwards
// them back to goCallbackHandler(). This one performs the addition
// and yields the result.
func MyAdd(a, b int) int {
return int( C.doAdd( C.int(a), C.int(b)) )
}
Kolejność, w jakiej wszystko się nazywa, jest następująca:
foo.MyAdd(a, b) ->
C.doAdd(a, b) ->
C.goCallbackHandler(a, b) ->
foo.goCallbackHandler(a, b)
Kluczem do zapamiętania jest tutaj to, że funkcja zwrotna musi być oznaczona //export
komentarzem po stronie Go i extern
po stronie C. Oznacza to, że każde wywołanie zwrotne, którego chcesz użyć, musi być zdefiniowane w pakiecie.
Aby umożliwić użytkownikowi twojego pakietu dostarczenie niestandardowej funkcji zwrotnej, używamy dokładnie tego samego podejścia, co powyżej, ale dostarczamy niestandardową procedurę obsługi użytkownika (która jest zwykłą funkcją Go) jako parametr, który jest przekazywany do C strona jak void*
. Następnie jest odbierany przez callbackhandler w naszym pakiecie i wywoływany.
Skorzystajmy z bardziej zaawansowanego przykładu, z którym obecnie pracuję. W tym przypadku mamy funkcję C, która wykonuje dość ciężkie zadanie: odczytuje listę plików z urządzenia USB. Może to chwilę potrwać, dlatego chcemy, aby nasza aplikacja była powiadamiana o postępach. Możemy to zrobić, przekazując wskaźnik funkcji, który zdefiniowaliśmy w naszym programie. Po prostu wyświetla użytkownikowi informacje o postępie, gdy zostanie wywołany. Ponieważ ma dobrze znaną sygnaturę, możemy przypisać jej własny typ:
type ProgressHandler func(current, total uint64, userdata interface{}) int
Ten program obsługi pobiera informacje o postępie (aktualną liczbę otrzymanych plików i całkowitą liczbę plików) wraz z wartością interfejsu {}, która może przechowywać wszystko, czego potrzebuje użytkownik.
Teraz musimy napisać instalację wodno-kanalizacyjną C i Go, aby umożliwić nam użycie tego programu obsługi. Na szczęście funkcja C, którą chcę wywołać z biblioteki, pozwala nam przekazać strukturę typu userdata void*
. Oznacza to, że może pomieścić wszystko, co chcemy, bez zadawania pytań i wrócimy do świata Go tak, jak jest. Aby to wszystko działało, nie wywołujemy funkcji biblioteki bezpośrednio z Go, ale tworzymy dla niej opakowanie C, które nazwiemy goGetFiles()
. To właśnie ta otoka faktycznie dostarcza wywołanie zwrotne Go do biblioteki C, wraz z obiektem userdata.
package foo
// #include <somelib.h>
// extern int goProgressCB(uint64_t current, uint64_t total, void* userdata);
//
// static int goGetFiles(some_t* handle, void* userdata) {
// return somelib_get_files(handle, goProgressCB, userdata);
// }
import "C"
import "unsafe"
Zauważ, że goGetFiles()
funkcja nie przyjmuje żadnych wskaźników funkcji dla wywołań zwrotnych jako parametrów. Zamiast tego wywołanie zwrotne dostarczone przez naszego użytkownika jest pakowane w niestandardową strukturę, która przechowuje zarówno tę procedurę obsługi, jak i własną wartość userdata użytkownika. Przekazujemy to goGetFiles()
jako parametr userdata.
// This defines the signature of our user's progress handler,
type ProgressHandler func(current, total uint64, userdata interface{}) int
// This is an internal type which will pack the users callback function and userdata.
// It is an instance of this type that we will actually be sending to the C code.
type progressRequest struct {
f ProgressHandler // The user's function pointer
d interface{} // The user's userdata.
}
//export goProgressCB
func goProgressCB(current, total C.uint64_t, userdata unsafe.Pointer) C.int {
// This is the function called from the C world by our expensive
// C.somelib_get_files() function. The userdata value contains an instance
// of *progressRequest, We unpack it and use it's values to call the
// actual function that our user supplied.
req := (*progressRequest)(userdata)
// Call req.f with our parameters and the user's own userdata value.
return C.int( req.f( uint64(current), uint64(total), req.d ) )
}
// This is our public function, which is called by the user and
// takes a handle to something our C lib needs, a function pointer
// and optionally some user defined data structure. Whatever it may be.
func GetFiles(h *Handle, pf ProgressFunc, userdata interface{}) int {
// Instead of calling the external C library directly, we call our C wrapper.
// We pass it the handle and an instance of progressRequest.
req := unsafe.Pointer(&progressequest{ pf, userdata })
return int(C.goGetFiles( (*C.some_t)(h), req ))
}
To wszystko w przypadku naszych wiązań C. Kod użytkownika jest teraz bardzo prosty:
package main
import (
"foo"
"fmt"
)
func main() {
handle := SomeInitStuff()
// We call GetFiles. Pass it our progress handler and some
// arbitrary userdata (could just as well be nil).
ret := foo.GetFiles( handle, myProgress, "Callbacks rock!" )
....
}
// This is our progress handler. Do something useful like display.
// progress percentage.
func myProgress(current, total uint64, userdata interface{}) int {
fc := float64(current)
ft := float64(total) * 0.01
// print how far along we are.
// eg: 500 / 1000 (50.00%)
// For good measure, prefix it with our userdata value, which
// we supplied as "Callbacks rock!".
fmt.Printf("%s: %d / %d (%3.2f%%)\n", userdata.(string), current, total, fc / ft)
return 0
}
To wszystko wygląda na dużo bardziej skomplikowane niż w rzeczywistości. Kolejność wywołań nie zmieniła się w przeciwieństwie do poprzedniego przykładu, ale otrzymujemy dwa dodatkowe wywołania na końcu łańcucha:
Kolejność jest następująca:
foo.GetFiles(....) ->
C.goGetFiles(...) ->
C.somelib_get_files(..) ->
C.goProgressCB(...) ->
foo.goProgressCB(...) ->
main.myProgress(...)