wake-up-neo.com

Rufen Sie die Go-Funktionen von C aus auf

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 aus Go heraus gefunden, aber ich habe nicht viel darüber herausgefunden, 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.

141
beatgammit

Sie können Go-Code von C aus aufrufen. Dies ist jedoch eine verwirrende Aussage.

Der Vorgang wird in dem Blog-Beitrag beschrieben, mit dem Sie verlinkt sind. Aber ich kann sehen, dass das nicht sehr hilfreich ist. Hier ist ein kurzer Ausschnitt ohne unnötige Teile. 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 auf der Go-Seite mit dem Kommentar //export Und auf der C-Seite mit extern 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 (bei dem es sich lediglich um eine reguläre Go-Funktion handelt) als Parameter zur Verfügung, der an das C übergeben wird Seite als void*. Es wird dann vom Callbackhandler in unserem Paket empfangen und angerufen.

Lassen Sie uns ein erweitertes 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, wann immer 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 benötigt einige Fortschrittsinformationen (aktuelle Anzahl der empfangenen Dateien und Gesamtanzahl der Dateien) sowie einen Schnittstellenwert {}, der alles aufnehmen kann, was der Benutzer benötigt.

Jetzt müssen wir das C and Go-Installationsprogramm schreiben, damit wir diesen Handler verwenden können. Zum Glück können wir mit der C-Funktion, die ich aus der Bibliothek aufrufen möchte, eine Benutzerdatenstruktur vom Typ void* Übergeben. Dies bedeutet, dass es alles aufnehmen kann, was wir wollen, ohne dass Fragen gestellt werden, und wir werden es in die Go-Welt zurückbringen, wie es ist. Damit dies alles funktioniert, rufen wir die Bibliotheksfunktion nicht direkt von Go aus auf, sondern erstellen einen C-Wrapper dafür, den wir goGetFiles() nennen. Es ist dieser Wrapper, der zusammen mit einem Benutzerdatenobjekt unseren Go-Rückruf 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"

Beachten Sie, dass die Funktion goGetFiles() keine Funktionszeiger für Rückrufe als Parameter akzeptiert. 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 an 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 Benutzercode 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 Vergleich 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(...)
121
jimt

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
54
Alexander

Die Antwort hat sich mit der Veröffentlichung von Go 1.5 geändert

Diese SO Frage, die ich vor einiger Zeit gestellt habe, behebt das Problem erneut angesichts der 1.5 hinzugefügten Funktionen

Verwenden von Go-Code in einem vorhandenen C-Projekt

10
dreadiscool

Für mich ist es nicht möglich:

Hinweis: Wenn Sie Exporte verwenden, können Sie in der Präambel keine C-Funktionen definieren.

quelle: https://github.com/golang/go/wiki/cgo

2
Rafael Ribeiro