Функції виклику Go від C


150

Я намагаюся створити статичний об'єкт, написаний на Go, щоб взаємодіяти з програмою C (скажімо, модулем ядра чи іншим).

Я знайшов документацію щодо виклику функцій C від Go, але не знайшов багато того, як пройти іншим шляхом. Я знайшов, що це можливо, але складно.

Ось що я знайшов:

Повідомлення в блозі про зворотні дзвінки між C і Go

Документація ЦГО

Повідомлення списку розсилки Golang

Хтось має з цим досвід? Коротше кажучи, я намагаюся створити модуль PAM, повністю написаний на Go.


11
Ви не можете, принаймні, з тем, які не були створені з Go. Я неодноразово лютував з цього приводу і переставав розвиватися в Go, поки це не виправлено.
Метт Столяр

Я чув, що це можливо. Немає рішення?
beatgammit

Go використовує інший режим викликів та сегментовані стеки. Можливо, ви зможете зв’язати код Go, зібраний з gccgo, з кодом C, але я цього не пробував, оскільки не отримав gccgo для створення своєї системи.
mkb

Я зараз пробую це за допомогою SWIG, і я сподіваюся ... У мене ще нічого не вийшло працювати, хоча ... = '(Я розмістив у списку розсилки. Сподіваюся, хтось змилується над мною.
beatgammit

2
Ви можете зателефонувати з коду Go з C, але на даний момент ви не можете вбудувати програму Go у додаток C, що є важливою, але тонкою, різницею.
tylerl

Відповіді:


126

Ви можете зателефонувати на код Go від C. Це, однак, є заплутаною пропозицією.

Процес описаний у публікації блогу, до якого ви пов’язані. Але я бачу, як це не дуже корисно. Ось короткий фрагмент без зайвих шматочків. Це повинно зробити речі яснішими.

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

Порядок, у якому все називається, такий:

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

Тут важливо пам’ятати, що функція зворотного дзвінка повинна бути позначена //exportкоментарем на стороні Go та як externна стороні C. Це означає, що будь-який зворотний дзвінок, який ви хочете використовувати, повинен бути визначений у вашому пакеті.

Для того, щоб дозволити користувачеві вашого пакета надавати власну функцію зворотного виклику, ми використовуємо точно такий же підхід, як і вище, але ми постачаємо користувальницький користувальницький обробник (що є лише звичайною функцією Go) як параметр, який передається на C сторона як void*. Потім він отримує зворотний дзвінок в нашому пакеті і дзвонить.

Давайте скористаємося більш просунутим прикладом, з яким я зараз працюю. У цьому випадку у нас є функція C, яка виконує досить важке завдання: вона читає список файлів з USB-пристрою. Це може зайняти деякий час, тому ми хочемо, щоб наш додаток був сповіщений про його хід. Ми можемо це зробити, передавши вказівник функції, який ми визначили в нашій програмі. Він просто відображає певну інформацію про прогрес користувачеві кожного разу, коли йому дзвонять. Оскільки він має добре відомий підпис, ми можемо призначити його власний тип:

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

Цей обробник отримує деяку інформацію про прогрес (поточну кількість отриманих файлів та загальну кількість файлів), а також значення {} інтерфейсу, який може вмістити все, що потрібно для користування.

Тепер нам потрібно написати сантехніку C і Go, щоб ми могли використовувати цей обробник. На щастя, функція C, яку я хочу зателефонувати з бібліотеки, дозволяє нам передати структуру типів даних користувачів void*. Це означає, що він може вмістити все, що ми хочемо, і не задаватиметься жодних питань, і ми повернемо його назад у світ Go так, як є. Для того, щоб все це працювало, ми не викликаємо функцію бібліотеки від Go безпосередньо, а створюємо для неї обгортку C, яку ми назвамо goGetFiles(). Саме ця обгортка фактично постачає наш зворотний виклик Go у бібліотеку C разом із об’єктом даних користувача.

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"

Зауважте, що goGetFiles()функція не приймає жодних покажчиків функцій для зворотних викликів як параметрів. Натомість зворотний виклик, який надав наш користувач, упаковується у власну структуру, яка містить як цей обробник, так і власне значення даних користувача. Ми передаємо це goGetFiles()як параметр 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 ))
}

Це все для наших зв'язків на С. Код користувача зараз дуже прямий:

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
}

Це все виглядає набагато складніше, ніж це є. Порядок дзвінків не змінився на відміну від попереднього прикладу, але ми отримуємо два додаткових дзвінка в кінці ланцюга:

Порядок такий:

foo.GetFiles(....) ->
  C.goGetFiles(...) ->
    C.somelib_get_files(..) ->
      C.goProgressCB(...) ->
        foo.goProgressCB(...) ->
           main.myProgress(...)

Так, я розумію, що все це може вибухнути на наших обличчях, коли в гру вступають окремі нитки. Зокрема ті, які не створені Go. На жаль, саме так зараз і йдеться.
jimt

17
Це дійсно гарна відповідь та ретельна. Це не відповідає безпосередньо на питання, але це тому, що відповіді немає. За даними декількох джерел, точкою входу має бути «Перейти», а не може бути «Я». Я відзначаю це як правильне, оскільки це дійсно очищено для мене. Дякую!
битгаміт

@jimt Як це поєднується зі смітником? Зокрема, коли збирається приватний екземпляр progressRequest, якщо він є? (Нове для Go, і, таким чином, для небезпечного. Покажчик). Крім того, що робити з API, як SQLite3, які приймають недійсні * userdata, а також додатковою функцією "deleter" для даних користувача? Чи можна це використовувати для взаємодії з GC, щоб сказати йому, що "зараз добре повернути ці дані користувача, за умови, що сторона Go вже не посилається на це?".
ddevienne

6
На Go 1.5 є краща підтримка для дзвінка Перейти з C. Дивіться на це питання, якщо ви шукаєте відповідь, яка демонструє більш просту техніку: stackoverflow.com/questions/32215509/…
Габріель Південний

2
На Go 1.6 цей підхід не працює, він порушує "код C може не зберігати копію вказівника Go після повернення дзвінка". правило і видає помилку "паніка: помилка часу виконання: аргумент cgo має вказівник" Перейти на вказівник на ", - під час виконання
kaspersky

56

Це не заплутане судження, якщо ви використовуєте gccgo. Це працює тут:

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

Makefilefile

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

коли у мене є код код робити що-небудь із рядком, go package main func Add(a, b string) int { return a + b }я отримую помилку "undefined _go_string_plus"
TruongSinh

2
TruongSinh, напевно, ви хочете використовувати cgoі goзамість цього gccgo. Див. Golang.org/cmd/cgo . Коли це сказано, цілком можливо використовувати тип "рядок" у файлі .go та змінити файл .c, щоб включити __go_string_plusфункцію. Це працює: ix.io/dZB
Олександр


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.