Преобразование Go[]byte в C *char

У меня есть 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

Функция С:

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

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

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

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

Спасибо.


person dangeroushobo    schedule 27.02.2016    source источник


Ответы (2)


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

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

Если 2 копии данных неприемлемы в этом решении, есть третий вариант, когда вы сами выделяете буфер 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()))

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

person JimB    schedule 27.02.2016
comment
преобразование []byte в строку c может быть не очень хорошей идеей. Поскольку '\0' в [] байте завершает строку c, а длина строки c может не равняться длине исходного [] байта. - person bronze man; 27.09.2016
comment
@bronzeman: очевидно, но рассматриваемая функция принимает длину буфера в качестве аргумента и не ожидает строки с нулевым завершением. C.CString добавляет нулевой байт, если он необходим, но мы пропускаем его, передавая точную длину строки. - person JimB; 27.09.2016

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

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

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

person alex    schedule 28.02.2016
comment
OP не использует внутренние биты байтов. Буфер. Я объяснил причину, как и дубликат вашей проблемы. Нет ничего плохого в использовании фрагмента, возвращаемого буфером. - person JimB; 28.02.2016