Как обрабатывать char * из упакованной структуры в cgo? - PullRequest
0 голосов
/ 07 августа 2020

Поскольку Go не поддерживает упакованную структуру, я обнаружил, что в этой отличной статье все объясняется с примерами работы с упакованной структурой в go. https://medium.com/@liamkelly17 / working-with-pack- c -structs-in- cgo -224a0a3b708b

Проблема в том, что я пытаюсь использовать char * вместо [ 10] char это не работает. Я не уверен, как это преобразование работает с [10] char, а не с char *. Вот пример кода, взятого из статьи выше и измененного с помощью char *.

package main

/*
#include "stdio.h"
#pragma pack(1)
typedef struct{
    unsigned char a;
    char b;
    int c;
    unsigned int d;
    char *e; // changed from char[10] to char *
}packed;

void PrintPacked(packed p){
    printf("\nFrom C\na:%d\nb:%d\nc:%d\nd:%d\ne:%s\n", p.a, p.b, p.c, p.d, p.e);
}

*/
import "C"
import (
    "bytes"
    "encoding/binary"
)

//GoPack is the go version of the c packed structure
type GoPack struct {
    a uint8
    b int8
    c int32
    d uint32
    e [10]uint8
}

//Pack Produces a packed version of the go struct
func (g *GoPack) Pack(out *C.packed) {
    buf := &bytes.Buffer{}
    binary.Write(buf, binary.LittleEndian, g)
    *out = *(*C.packed)(C.CBytes(buf.Bytes()))
}

func main() {
    pack := &GoPack{1, 2, 3, 4, [10]byte{}}
    copy(pack.e[:], "TEST123")
    cpack := C.packed{} //just to allocate the memory, still under GC control
    pack.Pack(&cpack)
    C.PrintPacked(cpack)
}

Я работаю с cgo в первый раз, так что поправьте меня, если я ошибаюсь в любой момент.

1 Ответ

1 голос
/ 07 августа 2020

Вы записываете десять (ноль) байтов из GoPack.e в packed.e, который имеет тип char *. Это не сработает, потому что указатели будут иметь размер 4 или 8 байтов в зависимости от вашей системы, поэтому даже если байты представляют действительный указатель, вы переполняете объем выделенной памяти.

Если вы хотите создать допустимая структура с допустимым полем packed.e, вам нужно выделить 10 байтов памяти в куче C, скопировать байты в нее, а затем указать packed.e на эту выделенную память. (Вам также потребуется освободить эту память, когда вы освободите соответствующую структуру packed). Вы не можете сделать это напрямую с помощью binary.Write.

Вы можете принять это как отправную точку:

buf := &bytes.Buffer{}
binary.Write(buf, binary.LittleEndian, g.a)
binary.Write(buf, binary.LittleEndian, g.b)
binary.Write(buf, binary.LittleEndian, g.c)
binary.Write(buf, binary.LittleEndian, g.d)
binary.Write(buf, binary.LittleEndian, uintptr(C.CBytes(g.e))
*out = *(*C.packed)(C.CBytes(buf.Bytes()))

Функция C.CBytes(b) выделяет len(b) байтов в C heap, и копирует в нее байты из b, возвращая unsafe.Pointer.

Обратите внимание, что я скопировал вашу строку *out = *(*C.packed)... из вашего кода. Фактически это вызывает утечку памяти и ненужную копию. Вероятно, было бы лучше использовать модуль записи, который записывает байты непосредственно в память, на которую указывает out.

Возможно, это?

const N = 10000 // should be sizeof(*out) or larger
buf := bytes.NewBuffer((*[N]byte)(unsafe.Pointer(out))[:])

Это делает bytes.Buffer, который напрямую записывает в структуру out без прохождения какой-либо промежуточной памяти. Обратите внимание, что из-за небезопасных махинаций это уязвимо для переполнения буфера, если вы напишете больше байтов данных, чем указано в out.

Слова предупреждения: все это довольно неприятно, и те же проблемы, что и в C, и вам нужно будет проверить правила указателя cgo, чтобы убедиться, что вы не уязвимы для взаимодействий со сборкой мусора. Совет: учитывая, что вы говорите, что «не имеете большого опыта работы с указателями и распределением памяти», вам, вероятно, следует избегать написания или включения такого кода, потому что проблемы, которые он может вызвать, ужасны и могут быть не сразу очевидны *. 1033 *

...