Funções Call Go de C


150

Estou tentando criar um objeto estático escrito em Ir para interagir com um programa C (por exemplo, um módulo do kernel ou algo assim).

Encontrei documentação sobre como chamar funções C do Go, mas não encontrei muito sobre como seguir o outro caminho. O que eu descobri é que é possível, mas complicado.

Aqui está o que eu encontrei:

Postagem em blog sobre retornos de chamada entre C e Go

Documentação Cgo

Postagem na lista de mala direta Golang

Alguém tem experiência com isto? Em resumo, estou tentando criar um módulo PAM totalmente escrito em Go.


11
Você não pode, pelo menos a partir de threads que não foram criados no Go. Eu discuti isso várias vezes e parei de desenvolver no Go até que isso seja corrigido.
Matt Joiner

Ouvi dizer que é possível. Não existe solução?
Beatgammit 25/05

O Go usa uma convenção de chamada diferente e pilhas segmentadas. Você pode vincular o código Go compilado com o gccgo com o código C, mas eu não tentei isso desde que não consegui que o gccgo fosse desenvolvido no meu sistema.
Mkb 25/05

Estou tentando usar o SWIG agora e tenho esperança ... ainda não consegui nada para trabalhar ... = '(Eu postei na lista de discussão. Espero que alguém tenha piedade de mim.
beatgammit

2
Você pode chamar o código Go a partir de C, mas no momento não pode incorporar o tempo de execução Go em um aplicativo C, o que é uma diferença importante, mas sutil.
tylerl

Respostas:


126

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 //exportcomentário no lado Go e como externno 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(...)

Sim, eu percebo que tudo isso pode explodir em nossos rostos quando tópicos separados entram em cena. Especificamente aqueles não criados pelo Go. Infelizmente, é assim que as coisas estão neste momento.
jimt

17
Esta é uma resposta muito boa e completa. Não responde diretamente à pergunta, mas é porque não há resposta. De acordo com várias fontes, o ponto de entrada deve ser Go e não pode ser C. Estou marcando isso como correto, porque isso realmente esclareceu tudo para mim. Obrigado!
Beatgammit 27/05

@ jimt Como isso se integra ao coletor de lixo? Especificamente, quando é que a instância privada progressRequest é coletada? (Novo para ir e, portanto, inseguro. Ponteiro). Além disso, o que acontece com API como SQLite3 que recebe dados * de usuário nulos, mas também uma função "deleter" opcional para dados de usuário? Isso pode ser usado para interagir com o GC, para dizer "agora não há problema em recuperar esses dados do usuário, desde que o lado Go não faça mais referência a ele?".
ddevienne

6
A partir de Go 1.5 não há suporte melhor para chamar Go de C. Veja esta pergunta se você está procurando uma resposta que demonstra uma técnica simples: stackoverflow.com/questions/32215509/...
Gabriel Southern

2
A partir do Go 1.6, essa abordagem não funciona, mas o código C pode não manter uma cópia de um ponteiro Go após o retorno da chamada. regra e emite um "pânico: erro de execução: Argumento CGO tem Go ponteiro para Go ponteiro" Erro em tempo de execução
Kaspersky

56

Não é uma proposta confusa se você usar o gccgo. Isso funciona aqui:

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

quando eu tenho o código do sth com string go package main func Add(a, b string) int { return a + b }recebo o erro "undefined _go_string_plus"
TruongSinh

2
TruongSinh, você provavelmente deseja usar cgoe gonão gccgo. Veja golang.org/cmd/cgo . Quando isso é dito, é totalmente possível usar o tipo "string" no arquivo .go e alterá-lo para incluir uma __go_string_plusfunção. Isso funciona: ix.io/dZB
Alexander


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.