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 //export
commento sul lato Vai e come extern
sul 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(...)