Você pode chamar o código Go de C. É uma proposição confusa.
O processo está descrito na postagem do blog ao qual você vinculou. Mas posso ver como isso não ajuda muito. Aqui está um pequeno trecho sem bits desnecessários. Isso deve tornar as coisas um pouco mais claras.
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)) )
}
A ordem em que tudo é chamado é a seguinte:
foo.MyAdd(a, b) ->
C.doAdd(a, b) ->
C.goCallbackHandler(a, b) ->
foo.goCallbackHandler(a, b)
A chave a lembrar aqui é que uma função de retorno de chamada deve ser marcada com o //export
comentário no lado Go e como extern
no lado C. Isso significa que qualquer retorno de chamada que você deseja usar deve ser definido dentro do seu pacote.
Para permitir que um usuário do seu pacote forneça uma função de retorno de chamada personalizada, usamos exatamente a mesma abordagem acima, mas fornecemos o manipulador personalizado do usuário (que é apenas uma função Go normal) como um parâmetro que é passado para o C lado como void*
. Ele é recebido pelo callbackhandler em nosso pacote e chamado.
Vamos usar um exemplo mais avançado com o qual estou trabalhando atualmente. Nesse caso, temos uma função C que executa uma tarefa bastante pesada: lê uma lista de arquivos de um dispositivo USB. Isso pode demorar um pouco, por isso queremos que nosso aplicativo seja notificado sobre seu progresso. Podemos fazer isso passando um ponteiro de função que definimos em nosso programa. Ele simplesmente exibe algumas informações de progresso para o usuário sempre que são chamadas. Como possui uma assinatura conhecida, podemos atribuir a ele seu próprio tipo:
type ProgressHandler func(current, total uint64, userdata interface{}) int
Esse manipulador leva algumas informações de progresso (número atual de arquivos recebidos e número total de arquivos) junto com um valor de interface {} que pode conter tudo o que o usuário precisar.
Agora precisamos escrever o encanamento C e Go para nos permitir usar esse manipulador. Felizmente, a função C que desejo chamar da biblioteca nos permite passar uma estrutura do tipo userdata void*
. Isso significa que ele pode armazenar o que queremos, sem perguntas e vamos devolvê-lo ao mundo Go como está. Para fazer tudo isso funcionar, não chamamos a função de biblioteca de Go diretamente, mas criamos um wrapper C para o qual iremos nomear goGetFiles()
. É esse wrapper que realmente fornece nosso retorno de chamada Go à biblioteca C, junto com um 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"
Observe que a goGetFiles()
função não aceita ponteiros de função para retornos de chamada como parâmetros. Em vez disso, o retorno de chamada que nosso usuário forneceu é empacotado em uma estrutura personalizada que contém o manipulador e o valor dos dados do usuário do usuário. Passamos isso para goGetFiles()
o 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 ))
}
É isso para as nossas ligações C. O código do usuário agora é muito direto:
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
}
Tudo isso parece muito mais complicado do que é. A ordem de chamada não mudou ao contrário do exemplo anterior, mas recebemos duas chamadas extras no final da cadeia:
A ordem é a seguinte:
foo.GetFiles(....) ->
C.goGetFiles(...) ->
C.somelib_get_files(..) ->
C.goProgressCB(...) ->
foo.goProgressCB(...) ->
main.myProgress(...)