Как заполнить указатель void* C в Go?

Я пытаюсь взаимодействовать с некоторым кодом C из Go. Используя cgo, это было относительно просто, пока я не столкнулся с этим (довольно распространенным) случаем: необходимость передать указатель на структуру, которая сама содержит указатель на некоторые данные. Кажется, я не могу понять, как сделать это из Go, не прибегая к созданию структуры в самом коде C, чего я бы предпочел не делать. Вот фрагмент, который иллюстрирует проблему:

package main

// typedef struct {
//     int   size;
//     void *data;
// } info;
//
// void test(info *infoPtr) {
//     // Do something here...
// }
import "C"

import "unsafe"

func main() {
    var data uint8 = 5
    info := &C.info{size: C.int(unsafe.Sizeof(data)), data: unsafe.Pointer(&data)}
    C.test(info)
}

Хотя это компилируется нормально, попытка запустить его приводит к:

panic: runtime error: cgo argument has Go pointer to Go pointer

В моем случае данные, передаваемые вызову C, не сохраняются после вызова (т. е. рассматриваемый код C копается в структуре, копирует то, что ему нужно, а затем возвращает).


person Richard Wilkes    schedule 25.06.2016    source источник


Ответы (1)


См. раздел "Передача указателей" в cgo документах:

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

А также:

Эти правила проверяются динамически во время выполнения. Проверка управляется настройкой cgocheck переменной окружения GODEBUG. Значение по умолчанию — GODEBUG=cgocheck=1, что реализует достаточно дешевые динамические проверки. Эти проверки можно полностью отключить с помощью GODEBUG=cgocheck=0. Полная проверка обработки указателей за некоторую плату во время выполнения доступна через GODEBUG=cgocheck=2.

Если вы запустите фрагмент, который вы предоставили:

GODEBUG=cgocheck=0 go run snippet.go

Тогда паники нет. Однако правильный путь — использовать C.malloc (или получить «указатель C» откуда-то еще):

package main

// #include <stdlib.h>
// typedef struct {
//     int   size;
//     void *data;
// } info;
//
// void test(info *infoPtr) {
//     // Do something here...
// }
import "C"

import "unsafe"

func main() {
    var data uint8 = 5

    cdata := C.malloc(C.size_t(unsafe.Sizeof(data)))
    *(*C.char)(cdata) = C.char(data)
    defer C.free(cdata)

    info := &C.info{size: C.int(unsafe.Sizeof(data)), data: cdata}
    C.test(info)
}

Это работает, потому что, хотя обычные указатели Go не разрешены, C.malloc возвращает «указатель C»:

Указатель Go означает указатель на память, выделенную Go (например, с помощью оператора & или вызова предопределенной новой функции), а термин указатель C означает указатель на память, выделенную C (например, вызовом C.malloc). Является ли указатель указателем Go или указателем C, это динамическое свойство, определяемое тем, как была выделена память.

Обратите внимание, что вам нужно включить stdlib.h, чтобы использовать C.free.

person gavv    schedule 25.06.2016
comment
Спасибо. Вот что у меня получилось... Я просто надеялся на что-то менее подробное. - person Richard Wilkes; 26.06.2016