如何从传递给 C 函数的参数(char*)中获取字符串?

How to get string from argument(char*) passed to C function?

我很难准确描述这个问题。让我展示下面的代码:

我在 C 中有一个函数:

int foo(char *deviceID){
    char a[]="abc";
    deviceID=a;
    return 1;

}

显然我在这个函数中传递了要更改的 char*deviceID 参数,只是忽略了 return 整数,我 想要的 是获取 设备ID;

:

func getID() string{
    var b []byte
    C.foo((*C.char)(unsafe.Pointer(&b)))
    return string(b)
}

但是我好像什么都没有。谁能帮我找出问题所在?

char *deviceID => 这是指向调用数组(客户端代码)的第一个字符的指针。可以说deviceID存储的是调用(客户端代码)数组的起始地址。 假设数组是B。所以它存储的是B[0]

的地址

deviceID=a => deviceID存储[a]数组的起始地址。

程序returns时,deviceID指向第一个字符。可以说deviceID又存储了B[0]的起始地址

没有变化。

在C中,我们可以使用strcpy将内容复制到地址中。

int foo(char *deviceID){
    char a[]="abc";
    strcpy(deviceID, a);
    return 1;
}

int foo(char *deviceID){
    char a[]="abc";
    
    for (int i = 0; i < 4; i++){
        a[i] = a[i];  //note: copy the last character such as a[3] = '[=11=]';
    }
    return 1;
}

你的C代码是错误的

and shown in ,你的C代码不正确(一般会编译和运行,但没有任何作用)。

编写正确的 C 代码本身就已经很困难了;将它连接到 Go 运行time 可能会更加困难。您现有的 C 代码只有一个 return 值,但您可能希望它有两个 return 值:一个整数和一个指向一系列 char'[=19=]' 字节结尾的值,即 C 字符串。然而,C 中的字符串是出了名的棘手。

您的局部变量 a 包含一个合适的字符串:

char a[]="abc";

此处 a 的类型为 array 4 of charchar[4](在 C 中,也就是说——在 Go 中,最接近的匹配是 [4]byte)但是 [=112=这个变量的 ]lifetime 只是直到函数本身结束。具体来说,数组 a 具有 块作用域 自动持续时间 1 这意味着函数本身 returns.

后保存 'a', 'b', 'c', '[=26=]' evanesce 的四个字节

有许多不同的方法可以解决您的 C 代码中的这个问题。哪一个合适取决于实际问题:在您重现问题的这个玩具示例中,最简单的方法是给 vraiable a static duration。结果将如下所示:

int foo(char **deviceID) {
    static char a[] = "abc";
    *deviceID = a;
    return 1;
}

请注意,此函数现在采用 指向指针 的指针。从更多的C代码中,它可能被这样调用:

    char *name;
    ret ok;

    ret = foo(&name);

char * 类型的调用者指针 name 已被填充(覆盖)为 char * 类型的适当值,即指向字符的指针,指向有效字符串,其生命周期是整个程序的生命周期(“静态持续时间”)。

另一种方法—— 中说明的方法是使用 strcpy这样做很危险,因为现在由调用者分配足够的存储空间来容纳整个字符串。现在有必要选择一些最大长度 for foo 填写(或添加一个参数来减轻危险,我们稍后会做)。我们的调用 C 代码片段现在可能是:

    char buf[4];
    ret ok;

    ret = foo(buf);

但您应该问:我们怎么知道 buf 有四个字节的空间? 答案是“我们不知道”——我们只是幸运的是让它足够大——或者“因为我们可以看到函数 foo 总是恰好写入四个字节”。但是如果我们使用第二个答案,那么,我们可以看到函数 foo 总是写入四个字节 'a', 'b', 'c', '[=26=]'(然后总是 returns 1)。那么我们为什么要费心调用函数 foo 呢?我们可以这样写:

    char buf[4] = "foo";
    int ret = 1;

并完全省略函数调用。因此,一个真实世界的例子可能会采用 两个 参数:指向要填充的缓冲区的指针 大小 该缓冲区的字节数。如果我们需要比可用空间更多的空间,在我们的 foo 函数中,我们会 return 失败——调用者现在必须注意这一点——或者 t运行 命名,或者两者兼而有之:

int foo(char *buffer, size_t len) {
    char a[] = "abc";

    if (strlen(a) + 1 > len) {
        return 0; /* failure */
    }
    strcpy(buffer, a);
    return 1; /* success */
}

函数 foo 现在取决于它的调用者是否正确发送两个值,但至少它 可以 通常被正确但安全地使用—不同于早期版本的foo.


1这些术语是C语言特有的。虽然 Go 声明也有作用域,但该术语的使用有点不同,因为 Go 中的底层存储是垃圾收集的。 C 中某些数据的持续时间可以以一种在 Go 中根本不会发生的方式绑定到变量的范围。


你的 Go 代码有误

不管你用你的 C 代码做什么,你现有的 Go 代码也有问题:

func getID() string {
    var b []byte
    C.foo((*C.char)(unsafe.Pointer(&b)))
    return string(b)
}

此处b的类型是[]byte或“字节片”。它的初始值为零(转换为字节片类型)。

Go 中的切片值实际上是一个三元素组,如果您愿意,可以说是一种 struct 类型。有关更多详细信息,请参阅 reflect.SliceHeader。构造 &b 生成指向此切片的指针 header.

取决于您修复 foo 函数的方式——它是使用 strcpy,还是通过采用 char ** 类型的值来设置 char * 类型的值,甚至像在 Allen ZHU's answer 中那样使用 malloc——你肯定 不想 &b 传递给 C 代码。用 unsafe.Pointer 包装 &b 只是掩盖了这个错误。

假设您选择让 C.foo 接受 char *size_t。那么我们要做的就是传递给C函数:

  • 一个*C.char类型的值,指向第一个n >= 4 C.chars;
  • 可以覆盖的C.char个数,包括终止符'\0'。

我们应该保存 return 值并确保它是魔法常数 1(最好也去掉魔法常数,但你可以稍后再做)。

这是我们修改后的 getID 函数:

func getID() string {
        b := make([]C.char, 4)
        ret := C.foo(&b[0], C.size_t(len(b)))
        if ret != 1 {
                panic(fmt.Sprintf("C.foo failed (ret=%d)", int(ret)))
        }
        return C.GoString(&b[0])
}

我把 a complete sample program 放到了 Go Playground 中,但是不允许在那里构建 C 部分。 (我在另一个系统上构建并运行它。)