2016-02-27 28 views
5

Ho un byte.Buffer che impacchetta con i dati usando la funzione binary.Write(). Devo quindi inviare questo array di byte a una funzione C. Usando Go 1.6 Non ho avuto successo nel capire questo.Converti Go [] byte in un C * char

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 

Non riesce sulla linea chiamando la funzione C dicendo:

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

La funzione C:

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

sono stato in grado di ottenere la seguente al lavoro, ma si sentiva male convertire l'array di byte in una stringa. C'è un modo migliore per farlo? Questo metodo rischia effetti collaterali ai dati?

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

Anche in questo caso, è necessario lavorare in Go 1.6 che ha introdotto regole di puntatore cgo più rigide.

Grazie.

risposta

7

Se si desidera utilizzare il primo approccio, è necessario creare la sezione all'esterno degli argomenti di chiamata della funzione ed evitare l'intestazione della slice allocata temporaneamente o la struttura esterna negli argomenti, pertanto i controlli cgo non lo vedono come un puntatore memorizzato in Go.

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

Il metodo C.CString sarà più sicuro, in quanto i dati vengono copiati in un buffer C, quindi non c'è nessun puntatore to Go di memoria, e non c'è alcuna possibilità sarà modificata la fetta dietro la bytes.Buffer o andare fuori scopo. Dovrai convertire l'intera stringa, non solo il primo byte. Questo metodo ha bisogno di allocare e copiare due volte, tuttavia se la quantità di dati è piccola non è probabilmente una preoccupazione rispetto al sovraccarico della chiamata stessa.

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

Se le 2 copie dei dati non sono accettabili in questa soluzione, c'è una terza opzione in cui si malloc C tampone da soli, e fare una sola copia in quel buffer:

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

Ma con entrambe queste ultime opzioni, non dimenticare di liberare il puntatore del malloc.

+0

converting [] byte in una stringa c potrebbe non essere una buona idea. Poiché '\ 0' nel byte [] termina la stringa c e la lunghezza della stringa c potrebbe non essere uguale alla lunghezza del byte di origine []. –

+0

@bronzeman: ovviamente, ma la funzione in questione prende la lunghezza del buffer come argomento e non si aspetta una stringa terminata null. 'C.CString' aggiunge il byte null se necessario, ma lo saltiamo passando la lunghezza esatta della stringa. – JimB

0

Il programma si arresta in modo anomalo perché le regole di passaggio dei puntatori in C sono cambiate in go1.6 (vedere https://tip.golang.org/doc/go1.6#cgo per i dettagli).

Non so perché il programma si blocca, quindi ho creato il problema Go https://github.com/golang/go/issues/14546.

Ma indipendentemente dalla risposta al problema, non utilizzerei bit interni di byte.Buffer (come voi) per passare direttamente a cgo. L'implementazione bytes.Buffer può cambiare in futuro e il programma inizierà a rompere misteriosamente. Vorrei solo copiare i dati necessari in qualsiasi struttura sia appropriata e usarli per passare in cgo.

Alex

+0

OP non utilizza alcun bit interno di byte.Buffer. Ho spiegato il motivo, così come il dup del tuo problema. Non c'è niente di sbagliato nell'usare la slice restituita dal buffer – JimB