如何在Go中将[][]byte转换为**char

5

我希望将go语言中的[][]byte转换为C语言中的**char。换句话说,我有一个go语言中的字节矩阵,我想将其转换为C语言中的字符双指针。

请假设我必须使用[][]byte作为输入和**char作为输出。

我知道可以通过以下方式从[]byte转换为*char:

((*C.char)(unsafe.Pointer(&data[0])))

但是似乎不可能将此案例扩展到第二维。我尝试了一些相当复杂的东西,其中我将一个[][]byte打包成一个新的[]byte。然后我将该[]byte发送到一个C函数,该函数使用指针算术在正确位置指向新的[]byte以创建**char。

然而,这种转换给我带来了奇怪的行为,我的数据在几次迭代中是正确的,但在函数调用之间似乎被破坏了。

如果有人有任何想法,我会非常感激。

从我看到的回复中,还重要的是要说明我正在处理原始数据而不是字符串。因此,使用go byte类型会使原始数据损坏,如果添加C字符串终止符号。我只是使用C **char,因为char大小为1个字节。话虽如此,感谢您的回复。我能够根据我的需求修改接受的答案。

2个回答

4

未经测试的框架:

func foo(b [][]byte) {
        outer := make([]*C.char, len(b)+1)
        for i, inner := range b {
                outer[i] = C.CString(string(inner))
        }
        C.bar(unsafe.Pointer(&outer[0])) // void bar(**char) {...}
}

编辑:完整示例(经过测试):

package main

/*
#include <stdlib.h>
#include <stdio.h>

void bar(char **a) {
        char *s        ;
        for (;(s = *a++);)
                printf("\"%s\"\n", s);
}
*/
import "C"
import "unsafe"

func foo(b [][]byte) {
        outer := make([]*C.char, len(b)+1)
        for i, inner := range b {
                outer[i] = C.CString(string(inner))
        }
        C.bar((**C.char)(unsafe.Pointer(&outer[0]))) // void bar(**char) {...}
}

func main() {
        foo([][]byte{[]byte("Hello"), []byte("world")})
}

(15:24) jnml@fsc-r550:~/src/tmp/SO/14833531$ go run main.go 
"Hello"
"world"
(15:25) jnml@fsc-r550:~/src/tmp/SO/14833531$ 

你的示例是做什么的?除了代码之外,你能详细说明一下吗? - Kissaki
这个示例将一个Go [][]byte转换为一个以null结尾的C **char,然后在C端打印出来。这是你想问的吗?如果不是,请详细说明一下你的问题。 - zzzz

3

这必须手动完成。您需要分配一个新的**C.char类型,并循环遍历[][]byte切片中的每个元素,将其分配给新列表。这涉及到在每次迭代中通过正确的大小偏移**C.char指针。

这是一个执行此操作的示例程序。

如下面的注释所建议的那样,如果您打算在C语言中使用像printf这样的函数打印char *列表,则请确保输入字符串以NULL结尾。最好使用C.CString()函数进行转换。这假设它们要被视为字符串。否则,您可能还需要提供一种方法来传递每个单独的char *列表的长度到C函数中。

package main

/*
#include <stdlib.h>
#include <stdio.h>

void test(char **list, size_t len)
{
    size_t i;

    for (i = 0; i < len; i++) {
        //printf("%ld: %s\n", i, list[i]);
    }
}
*/
import "C"
import "unsafe"

func main() {
    list := [][]byte{
        []byte("foo"),
        []byte("bar"),
        []byte("baz"),
    }

    test(list)
}

func test(list [][]byte) {
    // Determine the size of a pointer on the current system.
    var b *C.char
    ptrSize := unsafe.Sizeof(b)

    // Allocate the char** list.
    ptr := C.malloc(C.size_t(len(list)) * C.size_t(ptrSize))
    defer C.free(ptr)

    // Assign each byte slice to its appropriate offset.
    for i := 0; i < len(list); i++ {
        element := (**C.char)(unsafe.Pointer(uintptr(ptr) + uintptr(i)*ptrSize))
        *element = (*C.char)(unsafe.Pointer(&list[i][0]))
    }

    // Call our C function.
    C.test((**C.char)(ptr), C.size_t(len(list)))
}

输出结果如下:
$ go run charlist.go 
0: foo
1: bar
2: baz

1
你没有在C字符串中加入空终止符,我认为你的示例虽然能够运行,但很可能是侥幸。 - Nick Craig-Wood
非常正确。行 *element = (*C.char)(unsafe.Pointer(&list[i][0])) 最好改为 *element = C.CString(string(list[i])) - jimt
等等,为什么C结构必须以空值结尾?请不要假设我正在使用字符串。我正在使用原始数据缓冲区。那么字符串函数还需要使用吗? - John Gilmore
1
如果您像@jmt在示例中那样将char *传递给printf,则必须将其以空值结尾。一般情况下并不需要这样做,但是除非每个数据块都是自描述的或具有固定长度,否则我认为您也需要知道其长度。 - Nick Craig-Wood
1
是的,我正在使用固定大小的数据缓冲区,所以我知道长度。我明白你的意思,要将数据打印为字符串,你希望以空字符结尾。我建议,如果帖子被更改,那么空字符终止符最好添加到其他地方,因为通过添加空字符终止符来打印原始数据,会破坏原始数据。 - John Gilmore

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