У меня есть byte.Buffer, в который я упаковываю данные с помощью функции binary.Write (). Затем мне нужно отправить этот массив байтов в функцию C. Используя Go 1.6, я не смог понять это.

buf := new(bytes.Buffer) //create my buffer
....
binary.Write(buf, binary.LittleEndian, data) //write my data to buffer here
addr := (*C.uchar)(unsafe.Pointer(&buf.Bytes()[0])) //convert buffers byte array to a C array
rc := C.the_function(addr, C.int(buf.Len())) //Fails here

Он не работает в строке, вызывающей функцию C, говоря:

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

Функция C:

int the_function(const void *data, int nbytes);

Мне удалось заставить работать следующее, но мне показалось, что преобразование байтового массива в строку было неправильным. Есть лучший способ сделать это? Рискует ли этот метод побочными эффектами для данных?

addr := unsafe.Pointer(C.CString(string(buf.Bytes()[0]))

Опять же, это должно работать в Go 1.6, где введены более строгие правила указателя cgo.

Спасибо.

12
dangeroushobo 27 Фев 2016 в 20:42

2 ответа

Лучший ответ

Если вы хотите использовать свой первый подход, вам нужно создать срез вне аргументов вызова функции и избегать временно выделенного заголовка среза или внешней структуры в аргументах, чтобы проверки cgo не видели его как указатель, хранящийся в Go.

b := buf.Bytes()
rc := C.the_function(unsafe.Pointer(&b[0]), C.int(buf.Len()))

Метод C.CString будет более безопасным, поскольку данные копируются в буфер C, поэтому нет указателя на память Go, и нет никаких шансов, что срез за bytes.Buffer будет изменен или исчезнет объема. Вам нужно преобразовать всю строку, а не только первый байт. Эти методы действительно должны выделять и копировать дважды, однако, если объем данных небольшой, это, вероятно, не проблема по сравнению с накладными расходами самого вызова cgo.

str := buf.String()
p := unsafe.Pointer(C.CString(str))
defer C.free(p)
rc = C.the_function(p, C.int(len(str)))

Если две копии данных неприемлемы в этом решении, есть третий вариант, при котором вы самостоятельно распределяете буфер C и делаете одну копию в этот буфер:

p := C.malloc(C.size_t(len(b)))
defer C.free(p)

// copy the data into the buffer, by converting it to a Go array
cBuf := (*[1 << 30]byte)(p)
copy(cBuf[:], b)
rc = C.the_function(p, C.int(buf.Len()))

Но с обоими этими последними вариантами не забудьте освободить указатель с ошибкой.

14
JimB 2 Мар 2016 в 14:23

Ваша программа вылетает из-за изменения правил передачи указателей в C в go1.6 (см. https: / /tip.golang.org/doc/go1.6#cgo для подробностей).

Я не знаю, почему ваша программа дает сбой, поэтому я создал задачу Go https: // github. com / golang / go / issues / 14546.

Но независимо от ответа на вопрос, я бы не стал использовать внутренние биты bytes.Buffer (как это делаете вы) для непосредственной передачи в cgo. Реализация bytes.Buffer может измениться в будущем, и ваша программа начнет таинственным образом ломаться. Я бы просто скопировал нужные вам данные в любую подходящую структуру и использовал бы их для передачи в cgo.

-1
Duncan Jones 17 Сен 2018 в 14:47