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 //export
comentario en el lado Ir y extern
en 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(...)