web-dev-qa-db-ja.com

Go []バイトをC * charに変換します

Binary.Write()関数を使用してデータをパックするbyte.Bufferがあります。次に、このバイト配列を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]))

繰り返しますが、これは、より厳密なcgoポインタールールを導入したGo1.6で機能する必要があります。

ありがとうございました。

11
dangeroushobo

最初のアプローチを使用する場合は、関数呼び出し引数の外側にスライスを作成し、一時的に割り当てられたスライスヘッダーまたは引数の外部構造を回避する必要があるため、cgoチェックでは表示されません。 Goに格納されているポインタとして。

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

C.CStringメソッドは、データがCバッファーにコピーされるため、より安全になります。そのため、Goメモリーへのポインターはなく、bytes.Bufferの後ろのスライスが変更されたり消えたりする可能性はありません。スコープの。最初のバイトだけでなく、文字列全体を変換する必要があります。このメソッドは、2回割り当ててコピーする必要がありますが、データの量が少ない場合は、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バッファーを自分でmallocし、そのバッファーに1つのコピーを作成する3番目のオプションがあります。

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されたポインターを解放することを忘れないでください。

13
JimB

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に渡します。

0
alex