Funciones de Call Go desde C


150

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

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

Aquí está lo que encontré:

Publicación de blog sobre devoluciones de llamada entre C y Go

Documentación cgo

Publicación de la lista de correo de Golang

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


11
No puede, al menos desde hilos que no fueron creados desde Go. Me enfurecí por esto varias veces y dejé de desarrollarme en Go hasta que se solucionó.
Matt Joiner

Escuché que es posible. ¿No hay solución?
beatgammit

Go utiliza una convención de llamadas diferente y pilas segmentadas. Es posible que pueda vincular el código Go compilado con gccgo con el código C, pero no lo he intentado ya que no he conseguido que gccgo se construya en mi sistema.
mkb

Lo estoy probando con SWIG ahora, y tengo la esperanza ... aunque todavía no tengo nada para trabajar ... = '(publiqué en la lista de correo. Espero que alguien tenga misericordia de mí.
beatgammit

2
Puede llamar al código Go desde C, pero por el momento no puede incrustar el tiempo de ejecución Go en una aplicación C, que es una diferencia importante, pero sutil.
tylerl

Respuestas:


126

Puede llamar al código Go desde C. aunque es una propuesta confusa.

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

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 se llama todo es el siguiente:

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 //exportcomentario en el lado Ir y externen el lado C. Esto significa que cualquier devolución de llamada que desee utilizar debe definirse dentro de su paquete.

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

Usemos un ejemplo más avanzado con el que estoy trabajando actualmente. En este caso, tenemos una función C que realiza una tarea bastante pesada: lee una lista de archivos desde 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 información de progreso al usuario cada vez que se le llama. Como 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 archivos recibidos y número total de archivos) junto con un valor de interfaz {} que puede contener cualquier cosa que el usuario necesite.

Ahora necesitamos escribir la tubería C y Go para permitirnos usar este controlador. Afortunadamente, la función C que deseo llamar desde la biblioteca nos permite pasar una estructura de tipo de datos de usuario void*. Esto significa que puede contener lo que queramos, sin hacer preguntas y lo devolveremos al mundo de Go tal como está. Para que todo esto funcione, no llamamos a la función de biblioteca directamente desde Go, sino que creamos un contenedor de C para el que nombraremos goGetFiles(). Es esta envoltura la que realmente proporciona nuestra devolución de llamada Go a la biblioteca C, junto con un objeto de datos de usuario.

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 goGetFiles()función no toma ningún puntero de función para devoluciones de llamada como parámetros. En cambio, la devolución de llamada que ha proporcionado nuestro usuario está empaquetada en una estructura personalizada que contiene tanto ese controlador como el valor de datos de usuario propio del usuario. Pasamos esto goGetFiles()como el parámetro 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 ))
}

Eso es todo por 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 las llamadas no ha cambiado en comparación con nuestro ejemplo anterior, pero recibimos 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(...)

Sí, me doy cuenta de que todo esto puede explotar en nuestras caras cuando entran en juego hilos separados. Específicamente aquellos no creados por Go. Desafortunadamente así son las cosas en este momento.
jimt

17
Esta es una muy buena respuesta y exhaustiva. No responde directamente a la pregunta, pero eso se debe a que no hay respuesta. Según varias fuentes, el punto de entrada debe ser Ir, y no puede ser C. Estoy marcando esto como correcto porque esto realmente me aclaró las cosas. ¡Gracias!
beatgammit

@jimt ¿Cómo se integra eso con el recolector de basura? Específicamente, ¿cuándo se recopila la instancia privada de progressRequest, si alguna vez? (Nuevo para ir, y por lo tanto inseguro. Puntero). Además, ¿qué pasa con API como SQLite3 que toma un dato de usuario nulo *, pero también una función opcional de "eliminación" para los datos de usuario? ¿Se puede usar para interactuar con el GC y decirle "ahora está bien reclamar esos datos de usuario, siempre que el lado Go ya no lo haga referencia?".
ddevienne

66
A partir de Go 1.5, hay mejor soporte para llamar a Go desde C. Vea esta pregunta si está buscando una respuesta que demuestre una técnica más simple: stackoverflow.com/questions/32215509/…
Gabriel Southern

2
A partir de Go 1.6, este enfoque no funciona, rompe el "código C puede que no conserve una copia de un puntero Go después de que regrese la llamada". regla y emite un error de "pánico: error de tiempo de ejecución: el argumento cgo tiene puntero Ir a puntero Ir" en tiempo de ejecución
kaspersky

56

No es una propuesta 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

cuando tengo un código de ir a hacer algo con la cadena go package main func Add(a, b string) int { return a + b }me sale el error "undefined _go_string_plus"
TruongSinh

2
TruongSinh, probablemente quieras usar cgoy en golugar de gccgo. Ver golang.org/cmd/cgo . Cuando se dice eso, es completamente posible usar el tipo de "cadena" en el archivo .go y cambiar su archivo .c para incluir una __go_string_plusfunción. Esto funciona: ix.io/dZB
Alexander


Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.