将Go语言中的 []byte 转换为C语言中的 *char

16

我有一个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指针规则。

谢谢。

2个回答

19

如果您想使用第一种方法,需要在函数调用参数之外创建切片,并避免在参数中使用临时分配的切片头或外部结构体,以便检查不会将其视为存储在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()))

但是在使用这两种方法时,不要忘记释放已分配的指针。


1
将 []byte 转换为 c 字符串可能不是一个好主意。因为 []byte 中的 '\0' 会终止 c 字符串,并且 c 字符串的长度可能不等于原始 []byte 的长度。 - bronze man
@bronzeman:显然,但是所讨论的函数将缓冲区长度作为参数,并不期望一个以空字符结尾的字符串。如果需要,C.CString会添加空字节,但我们通过传递确切的字符串长度来跳过它。 - JimB

-1

你的程序崩溃了,因为在go1.6中传递指针的规则发生了变化(详见https://tip.golang.org/doc/go1.6#cgo)。

我不知道为什么你的程序会崩溃,所以我创建了一个Go问题https://github.com/golang/go/issues/14546

但是无论如何,在问题得到解答之前,我不会直接使用bytes.Buffer的内部位来传递cgo。 bytes.Buffer的实现可能会在未来发生变化,你的程序将开始出现神秘的错误。我建议你将需要的数据复制到适当的结构中,并使用该结构来传递给cgo。


OP没有使用bytes.Buffer的任何内部位。我解释了原因,你的问题的副本也有解释。使用buffer返回的切片没有任何问题。 - JimB

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接