为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
鸿蒙传说 2022-08-01 10:30:05
我很难准确地描述这个问题。让我展示下面的代码:我在C中有一个函数:int foo(char *deviceID){    char a[]="abc";    deviceID=a;    return 1;}显然,我在这个函数中传递了要更改的char*deviceID参数,只是忽略返回整数,我想要的是获取设备ID的值;在围棋中:func getID() string{    var b []byte    C.foo((*C.char)(unsafe.Pointer(&b)))    return string(b)}但似乎我什么也没得到。任何人都可以帮我找出问题吗?
查看完整描述

2 回答

?
ibeautiful

TA贡献1993条经验 获得超5个赞

您的 C 代码有误

正如GSerg在评论中指出的那样,并在Thanh Do的答案中所示,您的C代码不正确(通常会编译和运行,但没有有用的效果)。


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


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


char a[]="abc";

这里有类型或(在C中,即在Go中最接近的匹配将是),但此变量的生存期仅在函数本身结束之前。具体而言,数组具有块范围和自动持续时间。1 这意味着一旦函数本身返回,四个字节就会保持 evanesce。aarray 4 of charchar[4][4]bytea'a', 'b', 'c', '\0'


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


int foo(char **deviceID) {

    static char a[] = "abc";

    *deviceID = a;

    return 1;

}

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


    char *name;

    ret ok;


    ret = foo(&name);

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


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


    char buf[4];

    ret ok;


    ret = foo(buf);

但你应该问:我们如何知道让buf有四个字节的空间?答案要么是“我们没有”——我们只是幸运地把它做得足够大——要么是“因为我们可以看到这个函数总是只写四个字节”。但是如果我们使用第二个答案,那么,我们可以看到该函数总是写入四个字节(然后总是返回1)。那么,我们为什么要费心调用函数呢?我们可以这样写:foofoo'a', 'b', 'c', '\0'foo


    char buf[4] = "foo";

    int ret = 1;

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


int foo(char *buffer, size_t len) {

    char a[] = "abc";


    if (strlen(a) + 1 > len) {

        return 0; /* failure */

    }

    strcpy(buffer, a);

    return 1; /* success */

}

函数现在依赖于其调用方来正确发送这两个值,但至少它可以正确且安全地使用,这与早期版本的 不同。foofoo


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 中的切片值实际上是一个三元素组,如果你愿意的话,它是一种类型。请参阅反射。SliceHeader 以获取更多详细信息。该构造生成指向此切片标头的指针。struct&b


根据你如何修复你的函数——它是使用 ,还是通过取一个 type 的值来设置一个 type 值,甚至像 Allen ZHU 的答案那样使用——你绝对不想传递给 C 代码。仅仅用掩饰这个错误来包装。foostrcpychar *char **malloc&b&bunsafe.Pointer


相反,假设您选择有 take 和 .然后我们要做的是传递给C函数:C.foochar *size_t


类型的值,指向 n 个>= 4 s 中的第一个;*C.charC.char

可以覆盖的数目,包括终止的“\0”。C.char

我们应该保存返回值,并确保它是魔术常数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部分。(我在另一个系统上构建并运行了它。


查看完整回答
反对 回复 2022-08-01
?
森林海

TA贡献2011条经验 获得超2个赞

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


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


当程序返回时,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] = '\0';

    }

    return 1;

}


查看完整回答
反对 回复 2022-08-01
  • 2 回答
  • 0 关注
  • 113 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号