Appeler les fonctions Go depuis C


150

J'essaye de créer un objet statique écrit dans Go to interface avec un programme C (disons, un module de noyau ou quelque chose).

J'ai trouvé de la documentation sur l'appel des fonctions C à partir de Go, mais je n'ai pas trouvé grand-chose sur la façon de procéder dans l'autre sens. Ce que j'ai trouvé, c'est que c'est possible, mais compliqué.

Voici ce que j'ai trouvé:

Article de blog sur les rappels entre C et Go

Documentation CGO

Message de la liste de diffusion Golang

Est-ce que quelqu'un a de l'expérience avec ça? Bref, j'essaye de créer un module PAM entièrement écrit en Go.


11
Vous ne pouvez pas, du moins à partir de threads qui n'ont pas été créés à partir de Go. J'ai fait rage à ce sujet à plusieurs reprises et j'ai cessé de développer dans Go jusqu'à ce que cela soit corrigé.
Matt Joiner

J'ai entendu dire que c'était possible. N'y a-t-il pas de solution?
beatgammit

Go utilise une convention d'appel différente et des piles segmentées. Vous pourrez peut-être lier le code Go compilé avec gccgo avec du code C, mais je n'ai pas essayé cela car je n'ai pas obtenu de gccgo pour construire sur mon système.
mkb

J'essaye en utilisant SWIG maintenant, et j'espère ... Je n'ai encore rien réussi à travailler ... = '(J'ai posté sur la liste de diffusion. J'espère que quelqu'un a pitié de moi.
beatgammit

2
Vous pouvez appeler le code Go à partir de C, mais pour le moment, vous ne pouvez pas intégrer le runtime Go dans une application C, ce qui est une différence importante, mais subtile.
tylerl

Réponses:


126

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 //exportcommentaire sur le côté Go et comme externsur 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(...)

Oui, je me rends compte que tout cela peut nous exploser au visage lorsque des fils séparés entrent en jeu. Plus précisément ceux qui ne sont pas créés par Go. Malheureusement, c'est ainsi que les choses se passent à ce stade.
jimt

17
C'est une très bonne réponse et approfondie. Cela ne répond pas directement à la question, mais c'est parce qu'il n'y a pas de réponse. Selon plusieurs sources, le point d'entrée doit être Go, et ne peut pas être C. Je marque cela comme correct parce que cela a vraiment clarifié les choses pour moi. Merci!
beatgammit

@jimt Comment cela s'intègre-t-il au garbage collector? Plus précisément, quand l'instance privée progressRequest est-elle collectée, le cas échéant? (Nouveau pour Go, et donc pour unsafe.Pointer). Aussi, qu'en est-il des API comme SQLite3 qui acceptent un vide * userdata, mais aussi une fonction optionnelle "deleter" pour userdata? Est-ce que cela peut être utilisé pour interagir avec le GC, pour lui dire "maintenant c'est OK de récupérer ces données utilisateur, à condition que le côté Go ne les référence plus?".
ddevienne

6
Depuis Go 1.5, il y a un meilleur support pour appeler Go à partir de C. Voir cette question si vous cherchez une réponse qui démontre une technique plus simple: stackoverflow.com/questions/32215509/...
Gabriel Southern

2
Depuis Go 1.6, cette approche ne fonctionne pas, elle rompt le "code C ne peut pas conserver une copie d'un pointeur Go après le retour de l'appel." règle et émet une erreur "panic: runtime error: cgo argument has Go pointer to Go pointer" error lors de l'exécution
kaspersky

56

Ce n'est pas une proposition déroutante si vous utilisez gccgo. Cela fonctionne ici:

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

quand j'ai le code go faire qch avec la chaîne go package main func Add(a, b string) int { return a + b }j'obtiens l'erreur "undefined _go_string_plus"
TruongSinh

2
TruongSinh, vous voudrez probablement utiliser cgoet à la goplace de gccgo. Voir golang.org/cmd/cgo . Cela dit, il est tout à fait possible d'utiliser le type "chaîne" dans le fichier .go et de modifier votre fichier .c pour inclure une __go_string_plusfonction. Cela fonctionne: ix.io/dZB
Alexander


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.