Vous pouvez appeler le code Go à partir de C. c'est une proposition déroutante cependant.
Le processus est décrit dans l'article de blog auquel vous avez lié. Mais je peux voir à quel point cela n'est pas très utile. Voici un court extrait sans aucun élément inutile. Cela devrait rendre les choses un peu plus claires.
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'ordre dans lequel tout est appelé est le suivant:
foo.MyAdd(a, b) ->
C.doAdd(a, b) ->
C.goCallbackHandler(a, b) ->
foo.goCallbackHandler(a, b)
La clé à retenir ici est qu'une fonction de rappel doit être marquée du //export
commentaire sur le côté Go et comme extern
sur le côté C. Cela signifie que tout rappel que vous souhaitez utiliser doit être défini dans votre package.
Afin de permettre à un utilisateur de votre package de fournir une fonction de rappel personnalisée, nous utilisons exactement la même approche que ci-dessus, mais nous fournissons le gestionnaire personnalisé de l'utilisateur (qui est juste une fonction Go régulière) en tant que paramètre qui est passé au C côté comme void*
. Il est ensuite reçu par le callbackhandler dans notre package et appelé.
Utilisons un exemple plus avancé avec lequel je travaille actuellement. Dans ce cas, nous avons une fonction C qui effectue une tâche assez lourde: elle lit une liste de fichiers à partir d'un périphérique USB. Cela peut prendre un certain temps, nous voulons donc que notre application soit informée de sa progression. Nous pouvons le faire en passant un pointeur de fonction que nous avons défini dans notre programme. Il affiche simplement des informations de progression à l'utilisateur chaque fois qu'il est appelé. Puisqu'il a une signature bien connue, nous pouvons lui attribuer son propre type:
type ProgressHandler func(current, total uint64, userdata interface{}) int
Ce gestionnaire prend des informations de progression (nombre actuel de fichiers reçus et nombre total de fichiers) avec une valeur d'interface {} qui peut contenir tout ce que l'utilisateur a besoin de conserver.
Nous devons maintenant écrire la plomberie C and Go pour nous permettre d'utiliser ce gestionnaire. Heureusement, la fonction C que je souhaite appeler depuis la bibliothèque nous permet de passer une structure userdata de type void*
. Cela signifie qu'il peut contenir tout ce que nous voulons qu'il contienne, aucune question posée et nous le ramènerons dans le monde de Go tel quel. Pour que tout cela fonctionne, nous n'appelons pas directement la fonction de bibliothèque depuis Go, mais nous créons un wrapper C pour cela que nous nommerons goGetFiles()
. C'est ce wrapper qui fournit en fait notre rappel Go à la bibliothèque C, avec un objet 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"
Notez que la goGetFiles()
fonction ne prend aucun pointeur de fonction pour les rappels comme paramètres. Au lieu de cela, le rappel que notre utilisateur a fourni est emballé dans une structure personnalisée qui contient à la fois ce gestionnaire et la propre valeur userdata de l'utilisateur. Nous le transmettons en goGetFiles()
tant que paramètre 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 ))
}
C'est tout pour nos fixations C. Le code de l'utilisateur est désormais très simple:
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
}
Tout cela semble beaucoup plus compliqué qu'il ne l'est. L'ordre des appels n'a pas changé par rapport à notre exemple précédent, mais nous recevons deux appels supplémentaires à la fin de la chaîne:
L'ordre est le suivant:
foo.GetFiles(....) ->
C.goGetFiles(...) ->
C.somelib_get_files(..) ->
C.goProgressCB(...) ->
foo.goProgressCB(...) ->
main.myProgress(...)