Funciones de llamada desde C


Estoy tratando de crear un objeto estático escrito en la interfaz Go to con un programa en C (digamos, un módulo del núcleo o algo así).

He encontrado documentación sobre cómo llamar a funciones de C desde Go, pero no he encontrado mucho sobre cómo ir al revés. Lo que he encontrado es que es posible, pero complicado.

Esto es lo que encontré:

Entrada de blog sobre callbacks entre C y Go

Cgo documentation

Lista de correo de Golang post

¿alguien tiene experiencia con esto? En resumen, estoy tratando de crear un módulo PAM escrito completamente en Go.

Author: Alexander, 2011-05-25

4 answers

Puedes llamar al código Go desde C. sin embargo, es una proposición confusa.

El proceso se describe en la publicación del blog a la que ha vinculado. Pero puedo ver cómo eso no es muy útil. Aquí hay un fragmento corto sin bits innecesarios. Debería aclarar un poco las cosas.

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

El orden en que todo es llamado es el siguiente: {[15]]}

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

La clave para recordar aquí es que una función de devolución de llamada debe marcarse con el comentario //export en el lado Go y como extern en el lado C. Esto significa que cualquier devolución de llamada que desee utilizar debe estar definida dentro de su paquete.

Para permitir que un usuario de su paquete proporcione una función de devolución de llamada personalizada, utilizamos el mismo enfoque que el anterior, pero suministramos el controlador personalizado del usuario (que es solo una función Go normal) como un parámetro que se pasa al lado C como void*. Luego es recibido por el callbackhandler en nuestro paquete y llamado.

Vamos a usar un ejemplo más avanzado Que estoy actualmente trabajando con. En este caso, tenemos una función C que realiza una tarea bastante pesada: Lee una lista de archivos de un dispositivo USB. Esto puede llevar un tiempo, por lo que queremos que nuestra aplicación sea notificada de su progreso. Podemos hacer esto pasando un puntero de función que definimos en nuestro programa. Simplemente muestra algo de información de progreso al usuario cada vez que se llama. Dado que tiene una firma bien conocida, podemos asignarle su propio tipo:

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

Este controlador toma información de progreso (número actual de los archivos recibidos y el número total de archivos) junto con un valor de interfaz{} que puede contener cualquier cosa que el usuario necesite que contenga.

Ahora necesitamos escribir la C y Go plumbing para permitirnos usar este manejador. Afortunadamente la función C que deseo llamar desde la biblioteca nos permite pasar una estructura userdata de tipo void*. Esto significa que puede contener lo que queramos, sin hacer preguntas y lo devolveremos al mundo de Go tal cual. Para que todo esto funcione, no llamamos a la biblioteca función de Ir directamente, pero creamos una envoltura C para ella que llamaremos goGetFiles(). Es esta envoltura la que realmente suministra nuestra devolución de llamada Go a la biblioteca C, junto con un objeto 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"

Tenga en cuenta que la función goGetFiles() no toma ningún puntero de función para devoluciones de llamada como parámetros. En su lugar, la devolución de llamada que nuestro usuario ha suministrado se empaqueta en una estructura personalizada que contiene tanto ese controlador como el propio valor userdata del usuario. Pasamos esto a goGetFiles() como los datos de usuario parámetro.

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

Eso es todo para nuestros enlaces C. El código del usuario ahora es muy sencillo:

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
}

Todo esto parece mucho más complicado de lo que es. El orden de llamada no ha cambiado a diferencia de nuestro ejemplo anterior, pero obtenemos dos llamadas adicionales al final de la cadena:

El orden es El siguiente:

foo.GetFiles(....) ->
  C.goGetFiles(...) ->
    C.somelib_get_files(..) ->
      C.goProgressCB(...) ->
        foo.goProgressCB(...) ->
           main.myProgress(...)
 113
Author: jimt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-19 11:51:20

No es una proposición confusa si usa gccgo. Esto funciona aquí:

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
 51
Author: Alexander,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-02 07:45:55

La respuesta ha cambiado con el lanzamiento de Go 1.5

Esta pregunta que hice hace algún tiempo aborda el problema de nuevo a la luz de las capacidades agregadas de 1.5

Usando código Go en un proyecto C existente

 10
Author: dreadiscool,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:20

En lo que a mí respecta no es posible:

Nota: no puede definir ninguna función C en el preámbulo si está utilizando exportación.

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

 2
Author: Rafael Ribeiro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-11 13:36:38