Rufen Sie Go-Funktionen von C aus auf

Lesezeit: 9 Minuten

Benutzeravatar von beatgammit
beatgammit

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

Benutzeravatar von jimt
jimt

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(...)

  • Ja, mir ist klar, dass uns das alles um die Ohren fliegen kann, wenn separate Threads ins Spiel kommen. Insbesondere diejenigen, die nicht von Go erstellt wurden. Leider ist das im Moment so.

    – Jimt

    27. Mai 2011 um 2:22 Uhr

  • Dies ist eine wirklich gute Antwort und gründlich. Es beantwortet die Frage nicht direkt, aber das liegt daran, dass es keine Antwort gibt. Laut mehreren Quellen muss der Einstiegspunkt Go sein und kann nicht C sein. Ich markiere dies als richtig, weil dies wirklich alles für mich geklärt hat. Vielen Dank!

    – beatgammit

    27. Mai 2011 um 2:45 Uhr

  • @jimt Wie lässt sich das in den Garbage Collector integrieren? Wann wird insbesondere die private progressRequest-Instanz erfasst, wenn überhaupt? (Neu auf Go und damit auf unsicher.Zeiger). Was ist auch mit APIs wie SQLite3, die void*-Benutzerdaten verwenden, aber auch eine optionale „Deleter“-Funktion für Benutzerdaten? Kann das verwendet werden, um mit dem GC zu interagieren, um ihm mitzuteilen, “jetzt ist es in Ordnung, diese Benutzerdaten zurückzufordern, vorausgesetzt, die Go-Seite verweist nicht mehr darauf?”.

    – ddevienne

    2. Juni 2013 um 18:06 Uhr


  • Ab Go 1.5 gibt es eine bessere Unterstützung für den Aufruf 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 Süd

    27. August 2015 um 21:59 Uhr


  • Ab Go 1.6 funktioniert dieser Ansatz nicht, er bricht den “C-Code behält möglicherweise keine Kopie eines Go-Zeigers, nachdem der Aufruf zurückkehrt.” Regel und gibt zur Laufzeit den Fehler „Panic: runtime error: cgo argument has Go pointer to Go pointer“ aus

    – Kaspersky

    24. Juni 2016 um 22:48 Uhr

Alexanders Benutzeravatar
Alexander

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 habe, mache etw mit String go package main func Add(a, b string) int { return a + b } Ich erhalte den Fehler „undefined _go_string_plus“

    – TruongSinh

    28. Juli 2014 um 9:09 Uhr


  • TruongSinh, möchten Sie wahrscheinlich verwenden cgo und go Anstatt von gccgo. Sehen golang.org/cmd/cgo. Wenn das gesagt ist, ist es durchaus möglich, den Typ “String” in der .go-Datei zu verwenden und Ihre .c-Datei so zu ändern, dass sie a enthält __go_string_plus Funktion. Das funktioniert: ix.io/dZB

    – Alexander

    22. August 2014 um 13:25 Uhr

Benutzeravatar von dreadiscool
fürchterlich

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

Aus meiner Sicht ist es nicht möglich:

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

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

1424600cookie-checkRufen Sie Go-Funktionen von C aus auf

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy