Функции Call Go от C


150

Я пытаюсь создать статический объект, написанный на Go, для взаимодействия с программой на C (скажем, модулем ядра или чем-то еще).

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

Вот что я нашел:

Сообщение в блоге о колбэках между C и Go

Cgo документация

Почта списка рассылки Голанга

У кого-нибудь есть опыт с этим? Короче говоря, я пытаюсь создать модуль PAM, полностью написанный на Go.


11
Вы не можете, по крайней мере, из потоков, которые не были созданы из Go. Я бушевал об этом много раз и перестал развиваться в Go, пока это не исправлено.
Matt Joiner

Я слышал, что это возможно. Нет ли решения?
beatgammit

Go использует другое соглашение о вызовах и сегментированные стеки. Возможно, вы сможете связать код Go, скомпилированный с помощью gccgo, с кодом C, но я не пробовал этого, так как не получил gccgo для сборки в моей системе.
MKB

Сейчас я пытаюсь сделать это с помощью SWIG, и я надеюсь ... Я еще не получил ничего, чтобы работать ... = '(Я разместил в списке рассылки. Надеюсь, кто-то помилует меня.
beatgammit

2
Вы можете вызывать код Go из C, но в настоящее время вы не можете встроить среду выполнения Go в приложение C, что является важным, но тонким отличием.
Тайлер

Ответы:


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, которую я хочу вызвать из библиотеки, позволяет нам передавать структуру типа userdata 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 ))
}

Вот и все для наших C-привязок. Код пользователя теперь очень прост:

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. К сожалению, так обстоят дела на данный момент.
Джим

17
Это действительно хороший и исчерпывающий ответ. Он не дает прямого ответа на вопрос, но это потому, что нет ответа. Согласно нескольким источникам, точка входа должна быть Go, и не может быть C. Я отмечаю это как правильное, потому что это действительно прояснило для меня. Спасибо!
beatgammit

@jimt Как это интегрируется с сборщиком мусора? В частности, когда собирается частный экземпляр progressRequest? (Новый для Go, и, следовательно, небезопасно. Указатель). Кроме того, как насчет API, такого как SQLite3, которое принимает пользовательские данные void *, но также и дополнительную функцию «удаления» пользовательских данных? Можно ли это использовать для взаимодействия с GC, чтобы сказать ему: «Теперь можно вернуть эти пользовательские данные, если сторона Go больше не ссылается на них?».
ddevienne

6
Начиная с Go 1.5, улучшена поддержка вызова Go из C. Посмотрите этот вопрос, если вы ищете ответ, который демонстрирует более простую технику: stackoverflow.com/questions/32215509/…
Gabriel Southern

2
Начиная с Go 1.6, этот подход не работает, он ломает «код C может не сохранить копию указателя Go после возврата вызова». выдает правило «паника: ошибка во время выполнения: у аргумента cgo есть указатель Go на указатель Go» во время выполнения
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);
}

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

когда у меня есть код go sth со строкой, 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 . Когда это сказано, вполне возможно использовать тип «string» в файле .go и изменить ваш файл .c, чтобы включить __go_string_plusфункцию. Это работает: ix.io/dZB
Александр


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.