Chiama Vai funzioni da C


150

Sto cercando di creare un oggetto statico scritto in Vai all'interfaccia con un programma C (diciamo, un modulo del kernel o qualcosa del genere).

Ho trovato la documentazione su come chiamare le funzioni C da Go, ma non ho trovato molto su come andare dall'altra parte. Quello che ho scoperto è che è possibile, ma complicato.

Ecco cosa ho trovato:

Post di blog sui callback tra C e Go

Documentazione Cgo

Post della mailing list Golang

Qualcuno ha esperienza con questo? In breve, sto cercando di creare un modulo PAM scritto interamente in Go.


11
Non puoi, almeno dai thread che non sono stati creati da Go. Ho imperversato su questo numerose volte e ho smesso di svilupparmi in Go fino a quando questo non è stato risolto.
Matt Joiner,

Ho sentito che è possibile. Non c'è soluzione?
beatgammit,

Go utilizza una convenzione di chiamata diversa e stack segmentati. Potresti essere in grado di collegare il codice Go compilato con gccgo con il codice C, ma non ho provato questo dato che non ho ottenuto gccgo per costruire sul mio sistema.
MK

Lo sto provando con SWIG ora, e spero ... Non ho ancora ottenuto nulla per funzionare ... = '(Ho pubblicato sulla mailing list. Spero che qualcuno abbia pietà di me.
Beatgammit

2
Puoi chiamare il codice Go da C, ma al momento non puoi incorporare il runtime Go in un'app C, che è una differenza importante, ma sottile.
Tylerl,

Risposte:


126

Puoi chiamare il codice Go da C. è comunque una proposta confusa.

Il processo è descritto nel post sul blog a cui sei collegato. Ma posso vedere come ciò non sia molto utile. Ecco un breve frammento senza bit inutili. Dovrebbe rendere le cose un po 'più chiare.

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

L'ordine in cui tutto viene chiamato è il seguente:

foo.MyAdd(a, b) ->
  C.doAdd(a, b) ->
    C.goCallbackHandler(a, b) ->
      foo.goCallbackHandler(a, b)

La chiave da ricordare qui è che una funzione di richiamata deve essere contrassegnata con il //exportcommento sul lato Vai e come externsul lato C. Ciò significa che qualsiasi callback che si desidera utilizzare, deve essere definito all'interno del pacchetto.

Al fine di consentire a un utente del pacchetto di fornire una funzione di callback personalizzata, utilizziamo lo stesso approccio di cui sopra, ma forniamo il gestore personalizzato dell'utente (che è solo una normale funzione Go) come parametro che viene passato alla C lato come void*. Viene quindi ricevuto dal callbackhandler nel nostro pacchetto e chiamato.

Facciamo un esempio più avanzato con cui sto attualmente lavorando. In questo caso, abbiamo una funzione C che svolge un compito piuttosto pesante: legge un elenco di file da un dispositivo USB. Questo può richiedere del tempo, quindi desideriamo che la nostra app venga informata dei suoi progressi. Possiamo farlo passando un puntatore a funzione che abbiamo definito nel nostro programma. Visualizza semplicemente alcune informazioni sull'avanzamento all'utente ogni volta che viene chiamato. Dal momento che ha una firma ben nota, possiamo assegnargli un proprio tipo:

type ProgressHandler func(current, total uint64, userdata interface{}) int

Questo gestore prende alcune informazioni sull'avanzamento (numero corrente di file ricevuti e numero totale di file) insieme a un valore di interfaccia {} che può contenere qualsiasi cosa l'utente ne abbia bisogno.

Ora dobbiamo scrivere l'idraulico C e Go per consentirci di utilizzare questo gestore. Fortunatamente la funzione C che desidero chiamare dalla libreria ci consente di trasmettere una struttura di tipo userdata void*. Ciò significa che può contenere tutto ciò che vogliamo, senza fare domande e lo faremo tornare nel mondo di Go così com'è. Per far funzionare tutto questo, non chiamiamo direttamente la funzione di libreria da Go, ma creiamo un wrapper C per cui chiameremo goGetFiles(). È questo wrapper che in realtà fornisce il nostro callback Go alla libreria C, insieme a un oggetto userdata.

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"

Si noti che la goGetFiles()funzione non accetta alcun puntatore a funzione per i callback come parametri. Invece, il callback che il nostro utente ha fornito è impacchettato in una struttura personalizzata che contiene sia il gestore che il valore dei dati utente dell'utente. Lo passiamo goGetFiles()come parametro userdata.

// 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 ))
}

Questo è tutto per i nostri attacchi C. Il codice dell'utente è ora molto semplice:

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
}

Tutto questo sembra molto più complicato di quello che è. L'ordine di chiamata non è cambiato rispetto al nostro esempio precedente, ma riceviamo due chiamate extra alla fine della catena:

L'ordine è il seguente:

foo.GetFiles(....) ->
  C.goGetFiles(...) ->
    C.somelib_get_files(..) ->
      C.goProgressCB(...) ->
        foo.goProgressCB(...) ->
           main.myProgress(...)

Sì, mi rendo conto che tutto ciò può esplodere in faccia quando entrano in gioco fili separati. In particolare quelli non creati da Go. Purtroppo è così che vanno le cose a questo punto.
jimt

17
Questa è una risposta davvero buona e approfondita. Non risponde direttamente alla domanda, ma è perché non esiste una risposta. Secondo diverse fonti, il punto di ingresso deve essere Go e non può essere C. Sto contrassegnando questo come corretto perché questo ha davvero chiarito le cose per me. Grazie!
Beatgammit,

@jimt Come si integra con il Garbage Collector? In particolare, quando viene raccolta l'istanza progressRequest privata, se mai? (Nuovo da andare, quindi non sicuro. Puntatore). Inoltre, che dire di API come SQLite3 che accettano un void * userdata, ma anche una funzione "deleter" opzionale per userdata? Può essere usato per interagire con il GC, per dirlo "ora è corretto recuperare i dati dell'utente, a condizione che il lato Go non lo faccia più riferimento?".
ddevienne,

6
A partire da Go 1.5 c'è un supporto migliore per chiamare Go da C. Vedi questa domanda se stai cercando una risposta che dimostra una tecnica più semplice: stackoverflow.com/questions/32215509/…
Gabriel Southern,

2
A partire da Go 1.6 questo approccio non funziona, interrompe il "codice C potrebbe non conservare una copia di un puntatore Go dopo il ritorno della chiamata". regola ed emette un errore "panic: runtime error: cgo topic has Go pointer to Go pointer" in fase di esecuzione
kaspersky

56

Non è una proposta confusa se usi gccgo. Questo funziona qui:

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

quando ho go code do sth con stringa go package main func Add(a, b string) int { return a + b }ricevo l'errore "undefined _go_string_plus"
TruongSinh

2
TruongSinh, probabilmente vuoi usare cgoe goinvece di gccgo. Vedi golang.org/cmd/cgo . Detto questo, è completamente possibile utilizzare il tipo "stringa" nel file .go e modificare il file .c per includere una __go_string_plusfunzione. Funziona: ix.io/dZB
Alexander,


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.