web-dev-qa-db-fra.com

Fonctions d'appel aller de C

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

J'ai trouvé de la documentation sur l'appel de fonctions C depuis 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

publication de la liste de diffusion Golang

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

141
beatgammit

Vous pouvez appeler le code Go à partir de C. C’est une proposition déroutante.

Le processus est décrit dans l'article de blog auquel vous êtes lié. Mais je peux voir à quel point cela n'est pas très utile. Voici un court extrait sans bits inutiles. 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 avec le commentaire //export Du côté Go et comme extern du 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 paquet 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 n'est qu'une fonction Go normale) sous la forme d'un paramètre transmis au C côté comme void*. Il est ensuite reçu par le callbackhandler dans notre paquet 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 ses progrès. 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é. Comme il a une signature connue, on peut lui attribuer son propre type:

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

Ce gestionnaire prend des informations sur la progression (nombre actuel de fichiers reçus et nombre total de fichiers) ainsi qu'une valeur d'interface {} pouvant contenir tout ce que l'utilisateur a besoin de détenir.

Maintenant, nous devons é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 transmettre une structure userdata de type void*. Cela signifie qu'il peut contenir ce que nous voulons, sans poser de question et nous le renverrons dans le monde de Go tel quel. Pour que tout cela fonctionne, nous n'appelons pas directement la fonction de bibliothèque de Go, mais nous créons un wrapper en C que nous nommerons goGetFiles(). C'est ce wrapper qui fournit en réalité notre rappel Go à la bibliothèque C, ainsi qu'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 fonction goGetFiles() ne prend pas de pointeur de fonction pour les rappels en tant que paramètres. Au lieu de cela, le rappel fourni par notre utilisateur est emballé dans une structure personnalisée qui contient à la fois ce gestionnaire et la valeur de la donnée utilisateur de l'utilisateur. Nous passons ceci dans goGetFiles() en 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 ))
}

Voilà pour nos fixations en C. Le code de l'utilisateur est maintenant 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 a l'air beaucoup plus compliqué qu'il ne l'est. L'ordre des appels n'a pas changé contrairement à notre exemple précédent, mais nous recevons deux appels supplémentaires à la fin de la chaîne:

La commande est la suivante:

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

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

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

La réponse a changé avec la sortie de Go 1.5

Cette SO question que j’ai posée il ya quelque temps résout le problème à la lumière des nouvelles fonctionnalités de la 1.5)

tilisation du code Go dans un projet C existant

10
dreadiscool

En ce qui me concerne, ce n'est pas possible:

Remarque: vous ne pouvez définir aucune fonction C dans le préambule si vous utilisez des exportations.

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

2
Rafael Ribeiro