Call Go-Funktionen von C.


150

Ich versuche, ein statisches Objekt zu erstellen, das in Go to interface mit einem C-Programm (z. B. einem Kernelmodul oder etwas anderem) geschrieben wurde.

Ich habe Dokumentation zum Aufrufen von C-Funktionen von Go aus gefunden, aber ich habe nicht viel darüber gefunden, wie man in die andere Richtung geht. Was ich gefunden habe ist, dass es möglich, aber kompliziert ist.

Folgendes habe ich gefunden:

Blogbeitrag über Rückrufe zwischen C und Go

Cgo-Dokumentation

Golang Mailingliste Beitrag

Hat jemand Erfahrung damit? Kurz gesagt, ich versuche ein PAM-Modul zu erstellen, das vollständig in Go geschrieben ist.


11
Sie können nicht, zumindest nicht aus Threads, die nicht aus Go erstellt wurden. Ich habe darüber viele Male gewütet und die Entwicklung in Go eingestellt, bis dies behoben ist.
Matt Joiner

Ich habe gehört, dass es möglich ist. Gibt es keine Lösung?
Beatgammit

Go verwendet eine andere Aufrufkonvention und segmentierte Stapel. 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 dazu gebracht habe, auf meinem System aufzubauen.
mkb

Ich versuche es jetzt mit SWIG und bin hoffnungsvoll ... Ich habe aber noch nichts zum Arbeiten gebracht ... = '(Ich habe auf der Mailingliste gepostet. Hoffentlich hat jemand
Mitleid mit

2
Sie können Go-Code von C aus aufrufen , aber im Moment können Sie die Go-Laufzeit nicht in eine C-App einbetten , was ein wichtiger, aber subtiler Unterschied ist.
Tylerl

Antworten:


126

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 //exportKommentar auf der Go-Seite und wie externauf 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(...)

Ja, mir ist klar, dass all dies in unseren Gesichtern explodieren kann, wenn separate Fäden ins Spiel kommen. Insbesondere diejenigen, die nicht von Go erstellt wurden. Leider ist das an diesem Punkt so.
Jimt

17
Dies ist eine wirklich gute und gründliche Antwort. Die Frage wird nicht direkt beantwortet, aber das liegt daran, dass es keine Antwort gibt. Nach mehreren Quellen muss der Einstiegspunkt Go sein und kann nicht C sein. Ich markiere dies als richtig, weil dies wirklich Dinge für mich geklärt hat. Vielen Dank!
Beatgammit

@jimt Wie integriert sich das in den Garbage Collector? Wann wird die private progressRequest-Instanz erfasst, wenn überhaupt? (Neu zu gehen und damit zu unsicher. Zeiger). Und was ist mit APIs wie SQLite3, die void * userdata, aber auch eine optionale "Deleter" -Funktion für Userdata verwenden? Kann dies verwendet werden, um mit dem GC zu interagieren und ihm mitzuteilen, dass es jetzt in Ordnung ist, diese Benutzerdaten zurückzufordern, vorausgesetzt, die Go-Seite verweist nicht mehr darauf?
ddevienne

6
Ab Go 1.5 gibt es eine bessere Unterstützung für das Aufrufen von Go von C. Sehen Sie sich diese Frage an, wenn Sie nach einer Antwort suchen, die eine einfachere Technik demonstriert: stackoverflow.com/questions/32215509/…
Gabriel Southern

2
Ab Go 1.6 funktioniert dieser Ansatz nicht mehr. Er bricht den "C-Code behält möglicherweise keine Kopie eines Go-Zeigers, nachdem der Aufruf zurückgegeben wurde." Regel und gibt einen "Panik: Laufzeitfehler: cgo Argument hat Go Zeiger auf Go Zeiger" Fehler zur Laufzeit
Kaspersky

56

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

Wenn ich Code mit Zeichenfolge machen muss, go package main func Add(a, b string) int { return a + b }erhalte ich den Fehler "undefined _go_string_plus"
TruongSinh

2
TruongSinh möchten Sie wahrscheinlich verwenden cgound gostatt gccgo. Siehe golang.org/cmd/cgo . Wenn dies gesagt ist, ist es vollständig möglich, den Typ "string" in der .go-Datei zu verwenden und Ihre .c-Datei so zu ändern, dass sie eine __go_string_plusFunktion enthält. Dies funktioniert: ix.io/dZB
Alexander


Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.