Funkcje Call Go z C

150

Próbuję utworzyć obiekt statyczny napisany w Idź do interfejsu z programem C (powiedzmy, moduł jądra lub coś w tym rodzaju).

Znalazłem dokumentację dotyczącą wywoływania funkcji C z Go, ale nie znalazłem zbyt wiele na temat tego, jak przejść w drugą stronę. Odkryłem, że jest to możliwe, ale skomplikowane.

Oto co znalazłem:

Wpis na blogu dotyczący wywołań zwrotnych między C i Go

Dokumentacja Cgo

Post na liście mailingowej Golang

Czy ktoś ma z tym doświadczenie? Krótko mówiąc, próbuję stworzyć moduł PAM napisany w całości w Go.

beatgammit
źródło
11
Nie możesz, przynajmniej z wątków, które nie zostały utworzone w Go. Wściekałem się na ten temat wiele razy i przestałem rozwijać się w Go, dopóki nie zostanie to naprawione.
Matt Joiner
Słyszałem, że jest to możliwe. Czy nie ma rozwiązania?
beatgammit
Go używa innej konwencji wywoływania i stosów segmentowanych. Możesz być w stanie połączyć kod Go skompilowany za pomocą gccgo z kodem C, ale nie próbowałem tego, ponieważ nie dostałem gccgo do kompilacji w moim systemie.
mkb
Próbuję go teraz za pomocą SWIG i mam nadzieję ... Jednak jeszcze nic nie udało mi się uruchomić ... = '(opublikowałem na liście mailingowej. Mam nadzieję, że ktoś się dla mnie lituje.
beatgammit
2
Możesz wywołać kod Go z C, ale w tej chwili nie możesz osadzić środowiska wykonawczego Go w aplikacji C, co jest ważną, ale subtelną różnicą.
tylerl

Odpowiedzi:

126

Możesz wywołać kod Go z C. jest to jednak myląca propozycja.

Proces jest opisany w poście na blogu, do którego prowadzi łącze. Ale widzę, że nie jest to zbyt pomocne. Oto krótki fragment bez zbędnych bitów. Powinno to trochę wyjaśnić.

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

Kolejność, w jakiej wszystko się nazywa, jest następująca:

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

Kluczem do zapamiętania jest tutaj to, że funkcja zwrotna musi być oznaczona //exportkomentarzem po stronie Go i externpo stronie C. Oznacza to, że każde wywołanie zwrotne, którego chcesz użyć, musi być zdefiniowane w pakiecie.

Aby umożliwić użytkownikowi twojego pakietu dostarczenie niestandardowej funkcji zwrotnej, używamy dokładnie tego samego podejścia, co powyżej, ale dostarczamy niestandardową procedurę obsługi użytkownika (która jest zwykłą funkcją Go) jako parametr, który jest przekazywany do C strona jak void*. Następnie jest odbierany przez callbackhandler w naszym pakiecie i wywoływany.

Skorzystajmy z bardziej zaawansowanego przykładu, z którym obecnie pracuję. W tym przypadku mamy funkcję C, która wykonuje dość ciężkie zadanie: odczytuje listę plików z urządzenia USB. Może to chwilę potrwać, dlatego chcemy, aby nasza aplikacja była powiadamiana o postępach. Możemy to zrobić, przekazując wskaźnik funkcji, który zdefiniowaliśmy w naszym programie. Po prostu wyświetla użytkownikowi informacje o postępie, gdy zostanie wywołany. Ponieważ ma dobrze znaną sygnaturę, możemy przypisać jej własny typ:

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

Ten program obsługi pobiera informacje o postępie (aktualną liczbę otrzymanych plików i całkowitą liczbę plików) wraz z wartością interfejsu {}, która może przechowywać wszystko, czego potrzebuje użytkownik.

Teraz musimy napisać instalację wodno-kanalizacyjną C i Go, aby umożliwić nam użycie tego programu obsługi. Na szczęście funkcja C, którą chcę wywołać z biblioteki, pozwala nam przekazać strukturę typu userdata void*. Oznacza to, że może pomieścić wszystko, co chcemy, bez zadawania pytań i wrócimy do świata Go tak, jak jest. Aby to wszystko działało, nie wywołujemy funkcji biblioteki bezpośrednio z Go, ale tworzymy dla niej opakowanie C, które nazwiemy goGetFiles(). To właśnie ta otoka faktycznie dostarcza wywołanie zwrotne Go do biblioteki C, wraz z obiektem 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"

Zauważ, że goGetFiles()funkcja nie przyjmuje żadnych wskaźników funkcji dla wywołań zwrotnych jako parametrów. Zamiast tego wywołanie zwrotne dostarczone przez naszego użytkownika jest pakowane w niestandardową strukturę, która przechowuje zarówno tę procedurę obsługi, jak i własną wartość userdata użytkownika. Przekazujemy to goGetFiles()jako parametr 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 ))
}

To wszystko w przypadku naszych wiązań C. Kod użytkownika jest teraz bardzo prosty:

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
}

To wszystko wygląda na dużo bardziej skomplikowane niż w rzeczywistości. Kolejność wywołań nie zmieniła się w przeciwieństwie do poprzedniego przykładu, ale otrzymujemy dwa dodatkowe wywołania na końcu łańcucha:

Kolejność jest następująca:

foo.GetFiles(....) ->
  C.goGetFiles(...) ->
    C.somelib_get_files(..) ->
      C.goProgressCB(...) ->
        foo.goProgressCB(...) ->
           main.myProgress(...)
jimt
źródło
Tak, zdaję sobie sprawę, że wszystko to może wybuchnąć nam na twarzach, gdy w grę wchodzą oddzielne wątki. W szczególności te, które nie zostały stworzone przez Go. Niestety w tym momencie tak właśnie jest.
jimt
17
To naprawdę dobra i dokładna odpowiedź. Nie odpowiada bezpośrednio na pytanie, ale to dlatego, że nie ma odpowiedzi. Według kilku źródeł punktem wejścia musi być Go i nie może to być C. Oznaczam to jako poprawne, ponieważ to naprawdę wyjaśniło mi sprawę. Dzięki!
beatgammit
@jimt Jak to się integruje z garbage collector? W szczególności, kiedy jest gromadzona prywatna instancja progressRequest, jeśli w ogóle? (Nowość w Go, a tym samym na niebezpiecznej.Pointer). A co z API, takim jak SQLite3, które pobierają void * userdata, ale także opcjonalną funkcję „deleter” dla userdata? Czy można tego użyć do interakcji z GC, aby powiedzieć mu: „teraz można odzyskać te dane użytkownika, pod warunkiem, że strona Go nie odwołuje się już do nich?”.
ddevienne
6
Od wersji Go 1.5 dostępna jest lepsza obsługa dzwonienia do Go z C. Zobacz to pytanie, jeśli szukasz odpowiedzi, która pokazuje prostszą technikę: stackoverflow.com/questions/32215509/ ...
Gabriel Southern
2
Od wersji Go 1.6 to podejście nie działa, przerywa „Kod C może nie zachować kopii wskaźnika Go po powrocie wywołania”. reguła i wyświetla błąd „panic: runtime error: cgo argument has Go pointer to Go pointer” błąd w czasie wykonywania
kaspersky
56

Nie jest to myląca propozycja, jeśli używasz gccgo. Działa to tutaj:

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
Alexander
źródło
kiedy mam go kod zrób coś ze stringiem go package main func Add(a, b string) int { return a + b }, pojawia się błąd „undefined _go_string_plus”
TruongSinh
2
TruongSinh, prawdopodobnie chcesz użyć cgoi gozamiast gccgo. Zobacz golang.org/cmd/cgo . Kiedy to powiedziane, jest w pełni możliwe użycie typu „string” w pliku .go i zmiana pliku .c tak, aby zawierał __go_string_plusfunkcję. To działa: ix.io/dZB
Alexander
3

Jeśli o mnie chodzi, nie jest to możliwe:

Uwaga: nie możesz zdefiniować żadnych funkcji C w preambule, jeśli używasz eksportu.

źródło: https://github.com/golang/go/wiki/cgo

Rafael Ribeiro
źródło