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



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

我在C中有一个函数:

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

显然,我在这个函数中传递了要更改的char*deviceID参数,只需忽略返回的整数,我想要的是获得deviceID的值

转到

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

但我好像什么都没有。有人能帮我找出问题吗?

您的C代码错误

正如GSerg在一条评论中所指出的,正如Thanh Do的回答所示,你的C代码是不正确的(通常会编译和运行,但没有任何有用的效果)。

编写正确的C代码本身就足够困难;将其连接到Go运行时可能会更加困难。现有的C代码只有一个返回值,但您可能希望它返回两个值:一个整数和一个指向以''字节结尾的一系列char值中第一个值的char指针,即C字符串。然而,C语言中的字符串是出了名的棘手。

本地变量a包含一个合适的字符串:

char a[]="abc";

这里a的类型为array 4 of charchar[4](在C中,也就是说,在Go中,最接近的匹配将是[4]byte),但该变量的生存期仅到函数本身结束。具体而言,阵列a具有块范围自动持续时间1这意味着一旦函数本身返回,保持'a', 'b', 'c', ''的四个字节就消失。

有许多不同的方法可以在C代码中解决这个问题。哪一个合适取决于真正的问题:在这个你已经复制了问题的玩具示例中,最简单的方法是给出可调用的a静态持续时间。结果如下:

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

请注意,此函数现在使用指针指向指针。从更多的C代码中,它可能被称为:

char *name;
ret ok;
ret = foo(&name);

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

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

char buf[4];
ret ok;
ret = foo(buf);

但你应该问:我们怎么知道让buf有四个字节的空间答案是";我们不&quot--我们只是幸运地把它做得足够大——或者说";因为我们可以看到函数CCD_ 17总是正好写入四个字节";。但如果我们使用第二个答案,那么,我们可以看到函数foo总是写入四个字节'a', 'b', 'c', ''(然后总是返回1)。那么,我们为什么要调用函数foo呢?我们可以写:

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

并完全省略函数调用。因此,一个真实世界的例子可能会采用两个参数:一个指向中要填充的缓冲区的指针,以及该缓冲区的大小(以字节为单位)。如果我们需要比可用空间更多的空间,在我们的foo函数中,我们将返回failure(调用者现在必须注意)或截断名称,或者两者兼有:

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)
}

这里CCD_ 24的类型是CCD_;字节的切片";。它的初始值为零(转换为字节类型的切片)。

Go中的切片值实际上是一个三元素组,如果愿意的话,它是一种struct类型。有关更多详细信息,请参见reflect.SliceHeader。构造&b产生一个指向此切片标头的指针。

根据您如何修复foo函数——无论它是使用strcpy,还是通过采用char **类型的值来设置char *类型的值,甚至像Allen ZHU的回答中那样使用malloc——您肯定不想将&b传递给C代码。用unsafe.Pointer包装&b只是掩盖了这个错误。

假设您选择让C.foochar *size_t。然后我们要做的是传递给C函数:

  • 类型为*C.char的值,指向n>=中的第一个4个CCD_ 41s
  • 可以重写的CCD_ 42的数目,包括终止的"\0">

我们应该保存返回值,并确保它是魔术常数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])
}

我在Go Playground中放入了一个完整的示例程序,但不允许在那里构建C部分。(我在另一个系统上构建并运行了它。)

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

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

当程序返回时,deviceID指向第一个字符。我们可以说设备ID再次存储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] = '';
}
return 1;
}

最新更新