Sie können Go-Code von C aus aufrufen. Dies ist jedoch verwirrend.
Der Prozess wird in dem Blog-Beitrag beschrieben, auf den Sie verlinkt haben. Aber ich kann sehen, dass das nicht sehr hilfreich ist. Hier ist ein kurzer Ausschnitt ohne unnötige Bits. Es sollte die Dinge etwas klarer machen.
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)) )
}
Die Reihenfolge, in der alles aufgerufen wird, ist wie folgt:
foo.MyAdd(a, b) ->
C.doAdd(a, b) ->
C.goCallbackHandler(a, b) ->
foo.goCallbackHandler(a, b)
Der Schlüssel, an den Sie sich erinnern sollten, ist, dass eine Rückruffunktion mit dem //export
Kommentar auf der Go-Seite und wie extern
auf der C-Seite gekennzeichnet sein muss. Dies bedeutet, dass jeder Rückruf, den Sie verwenden möchten, in Ihrem Paket definiert werden muss.
Damit ein Benutzer Ihres Pakets eine benutzerdefinierte Rückruffunktion bereitstellen kann, verwenden wir genau den gleichen Ansatz wie oben, stellen jedoch den benutzerdefinierten Handler des Benutzers (der nur eine reguläre Go-Funktion ist) als Parameter bereit, der an C übergeben wird Seite als void*
. Es wird dann vom Callbackhandler in unserem Paket empfangen und aufgerufen.
Lassen Sie uns ein fortgeschritteneres Beispiel verwenden, mit dem ich gerade arbeite. In diesem Fall haben wir eine C-Funktion, die eine ziemlich schwere Aufgabe ausführt: Sie liest eine Liste von Dateien von einem USB-Gerät. Dies kann eine Weile dauern, daher möchten wir, dass unsere App über den Fortschritt informiert wird. Wir können dies tun, indem wir einen Funktionszeiger übergeben, den wir in unserem Programm definiert haben. Es zeigt dem Benutzer einfach einige Fortschrittsinformationen an, wenn er aufgerufen wird. Da es eine bekannte Signatur hat, können wir ihm einen eigenen Typ zuweisen:
type ProgressHandler func(current, total uint64, userdata interface{}) int
Dieser Handler verwendet einige Fortschrittsinformationen (aktuelle Anzahl der empfangenen Dateien und Gesamtzahl der Dateien) sowie einen Wert für die Schnittstelle {}, der alles enthalten kann, was der Benutzer benötigt.
Jetzt müssen wir die C and Go-Installation schreiben, damit wir diesen Handler verwenden können. Glücklicherweise ermöglicht uns die C-Funktion, die ich aus der Bibliothek aufrufen möchte, die Übergabe einer Benutzerdatenstruktur vom Typ void*
. Dies bedeutet, dass es alles aufnehmen kann, was wir wollen, ohne dass Fragen gestellt werden, und wir werden es so wie es ist wieder in die Go-Welt zurückbringen. Damit dies alles funktioniert, rufen wir die Bibliotheksfunktion nicht direkt von Go aus auf, sondern erstellen einen C-Wrapper dafür, den wir benennen werden goGetFiles()
. Dieser Wrapper liefert unseren Go-Rückruf zusammen mit einem Benutzerdatenobjekt an die C-Bibliothek.
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"
Beachten Sie, dass die goGetFiles()
Funktion keine Funktionszeiger für Rückrufe als Parameter verwendet. Stattdessen wird der von unserem Benutzer bereitgestellte Rückruf in eine benutzerdefinierte Struktur gepackt, die sowohl diesen Handler als auch den eigenen Benutzerdatenwert des Benutzers enthält. Wir übergeben dies goGetFiles()
als Benutzerdatenparameter.
// 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 ))
}
Das war's für unsere C-Bindungen. Der Code des Benutzers ist jetzt sehr einfach:
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
}
Das alles sieht viel komplizierter aus als es ist. Die Anrufreihenfolge hat sich im Gegensatz zu unserem vorherigen Beispiel nicht geändert, aber wir erhalten zwei zusätzliche Anrufe am Ende der Kette:
Die Reihenfolge ist wie folgt:
foo.GetFiles(....) ->
C.goGetFiles(...) ->
C.somelib_get_files(..) ->
C.goProgressCB(...) ->
foo.goProgressCB(...) ->
main.myProgress(...)