Ich versuche, ein statisches Objekt zu erstellen, das in Go geschrieben ist, um mit einem C-Programm (z. B. einem Kernelmodul oder so) zu kommunizieren.
Ich habe Dokumentation zum Aufrufen von C-Funktionen von Go gefunden, aber ich habe nicht viel darüber gefunden, wie man den anderen Weg geht. Was ich gefunden habe, ist, dass es möglich, aber kompliziert ist.
Hier ist, was ich gefunden habe:
Blogbeitrag über Callbacks zwischen C und Go
Cgo-Dokumentation
Post auf der Golang-Mailingliste
Hat jemand Erfahrung damit? Kurz gesagt, ich versuche, ein vollständig in Go geschriebenes PAM-Modul zu erstellen.
Sie können den Go-Code von C aus aufrufen. Es ist jedoch eine verwirrende Aussage.
Der Vorgang ist in dem von Ihnen verlinkten Blogbeitrag beschrieben. Aber ich kann verstehen, dass das nicht sehr hilfreich ist. Hier ist ein kurzer Ausschnitt ohne unnötige Bits. Es sollte die Sache 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)
Wichtig dabei ist, dass eine Callback-Funktion mit gekennzeichnet werden muss //export
Kommentar auf der Go-Seite und als extern
auf der C-Seite. Das bedeutet, dass jeder Rückruf, den Sie verwenden möchten, in Ihrem Paket definiert werden muss.
Um es einem Benutzer Ihres Pakets zu ermöglichen, eine benutzerdefinierte Callback-Funktion bereitzustellen, verwenden wir genau denselben Ansatz wie oben, stellen jedoch den benutzerdefinierten Handler des Benutzers (der nur eine reguläre Go-Funktion ist) als Parameter bereit, der an die C 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 es 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 nimmt einige Fortschrittsinformationen (aktuelle Anzahl der empfangenen Dateien und Gesamtzahl der Dateien) zusammen mit einem Wert der Schnittstelle {}, der alles enthalten kann, was der Benutzer speichern muss.
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 userdata-Struktur vom Typ void*
. Das bedeutet, dass es halten kann, was wir wollen, ohne dass Fragen gestellt werden, und wir werden es so wie es ist wieder in die Go-Welt bringen. Damit all dies funktioniert, rufen wir die Bibliotheksfunktion nicht direkt von Go aus auf, sondern erstellen einen C-Wrapper dafür, den wir benennen werden goGetFiles()
. Es ist dieser Wrapper, der zusammen mit einem userdata-Objekt unseren Go-Callback an die C-Bibliothek liefert.
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"
Notiere dass der goGetFiles()
function nimmt keine Funktionszeiger für Callbacks als Parameter entgegen. 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 in goGetFiles()
als userdata-Parameter.
// 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 sieht alles viel komplizierter aus, als es ist. Die Aufrufreihenfolge hat sich im Gegensatz zu unserem vorherigen Beispiel nicht geändert, aber wir erhalten zwei zusätzliche Aufrufe am Ende der Kette:
Die Reihenfolge ist wie folgt:
foo.GetFiles(....) ->
C.goGetFiles(...) ->
C.somelib_get_files(..) ->
C.goProgressCB(...) ->
foo.goProgressCB(...) ->
main.myProgress(...)
Es ist keine verwirrende Aussage, wenn Sie gccgo verwenden. Das funktioniert hier:
foo.go
package main
func Add(a, b int) int {
return a + b
}
bar.c
#include <stdio.h>
extern int go_add(int, int) __asm__ ("example.main.Add");
int main() {
int x = go_add(2, 3);
printf("Result: %d\n", x);
}
Makefile
all: main
main: foo.o bar.c
gcc foo.o bar.c -o main
foo.o: foo.go
gccgo -c foo.go -o foo.o -fgo-prefix=example
clean:
rm -f main *.o
Die Antwort hat sich mit der Veröffentlichung von Go 1.5 geändert
Diese SO-Frage, die ich vor einiger Zeit gestellt habe, befasst sich erneut mit dem Problem im Hinblick auf die in 1.5 hinzugefügten Funktionen
Verwendung von Go-Code in einem bestehenden C-Projekt
Sie können nicht, zumindest von Threads, die nicht von Go erstellt wurden. Ich habe darüber unzählige Male gewütet und die Entwicklung in Go eingestellt, bis dies behoben ist.
– Matt Tischler
25. Mai 2011 um 16:08 Uhr
Ich habe gehört, dass es möglich ist. Gibt es keine Lösung?
– beatgammit
25. Mai 2011 um 16:56 Uhr
Go verwendet eine andere Aufrufkonvention und segmentierte Stacks. Möglicherweise können Sie mit gccgo kompilierten Go-Code mit C-Code verknüpfen, aber ich habe dies nicht versucht, da ich gccgo nicht zum Erstellen auf meinem System bekommen habe.
– Matt K
25. Mai 2011 um 18:15 Uhr
Ich versuche es jetzt mit SWIG und bin zuversichtlich … Ich habe jedoch noch nichts zum Laufen gebracht … ='( Ich habe auf der Mailingliste gepostet. Hoffentlich hat jemand Mitleid mit mir.
– beatgammit
26. Mai 2011 um 4:48 Uhr
Du kannst Anruf Gehen Sie Code von C, aber im Moment können Sie nicht einbetten die Go-Laufzeit in eine C-App, was ein wichtiger, aber subtiler Unterschied ist.
– tylerl
27. Mai 2011 um 18:39 Uhr