如何从传递给 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 char
或 char[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.char
s;
- 可以覆盖的
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 部分。 (我在另一个系统上构建并运行它。)
我很难准确描述这个问题。让我展示下面的代码:
我在 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代码是错误的
如
编写正确的 C 代码本身就已经很困难了;将它连接到 Go 运行time 可能会更加困难。您现有的 C 代码只有一个 return 值,但您可能希望它有两个 return 值:一个整数和一个指向一系列 char
以 '[=19=]'
字节结尾的值,即 C 字符串。然而,C 中的字符串是出了名的棘手。
您的局部变量 a
包含一个合适的字符串:
char a[]="abc";
此处 a
的类型为 array 4 of char
或 char[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 >= 4C.char
s; - 可以覆盖的
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 部分。 (我在另一个系统上构建并运行它。)