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

如何使用 CGo 为标准库调用 C++ 变量

如何使用 CGo 为标准库调用 C++ 变量

Go
慕斯王 2023-01-03 10:13:55
我正在尝试使用 cgo 从 C++ 代码中获取变量值。对于以.hall 结尾的库工作正常,但对于,等库<iostream>,我收到以下错误:<map><string>fatal error: iostream: No such file or directory    4 | #include <iostream>      |          ^~~~~~~~~~在我的代码下面:package main/*#cgo LDFLAGS: -lc++#include <iostream>std::string plus() {    return "Hello World!\n";}*/import "C"import "fmt"func main() {    a := Plus_go()    fmt.Println(a)}func Plus_go() string {    return C.plus()}我添加了这个标志,因为我在https://stackoverflow.com/a/41615301/15024997#cgo LDFLAGS: -lc++的 stackoverflow 上的一个答案中看到了这个建议。我正在使用 VS Code(不是 VS Studio)、Windows 10、Go 1.18(最新版本)。我运行了以下命令go tool cgo -debug-gcc mycode.go来跟踪编译器的执行和输出:$ gcc -E -dM -xc -m64 - <<EOF#line 1 "cgo-builtin-prolog"#include <stddef.h> /* for ptrdiff_t and size_t below *//* Define intgo when compiling with GCC.  */typedef ptrdiff_t intgo;#define GO_CGO_GOSTRING_TYPEDEFtypedef struct { const char *p; intgo n; } _GoString_;typedef struct { char *p; intgo n; intgo c; } _GoBytes_;_GoString_ GoString(char *p);_GoString_ GoStringN(char *p, int l);_GoBytes_ GoBytes(void *p, int n);char *CString(_GoString_);void *CBytes(_GoBytes_);void *_CMalloc(size_t);__attribute__ ((unused))static size_t _GoStringLen(_GoString_ s) { return (size_t)s.n; }__attribute__ ((unused))static const char *_GoStringPtr(_GoString_ s) { return s.p; }#line 3 "C:\\Users\\Home\\OneDrive\\Desktop\\DevicesC++\\devices.go"#include <iostream>std::string plus() {    return "Hello World!\n";}#line 1 "cgo-generated-wrapper"EOFC:\Users\Home\OneDrive\Desktop\DevicesC++\devices.go:5:10: fatal error: iostream: No such file or directory    5 | #include <iostream>      |          ^~~~~~~~~~compilation terminated.C:\Users\Home\OneDrive\Desktop\DevicesC++\devices.go:5:10: fatal error: iostream: No such file or directory    5 | #include <iostream>      |          ^~~~~~~~~~compilation terminated.
查看完整描述

1 回答

?
侃侃无极

TA贡献2051条经验 获得超10个赞

CGo 允许您将 Go 代码链接到实现 C 风格外部函数接口的代码。这并不意味着您可以将任意语言代码固定到位。


让我们从第一个问题开始,即import "C"您的一个 Go 文件中的行必须仅包含其上方的 C 代码。那是:


/*

#include <stdlib.h>

extern char *cstyle_plus();

*/

可以,但是:


/*

#include <stdlib.h>

extern std::string *plus();

*/

不是,您也不能#include在这里使用任何 C++ 标头。为了稍微简化事情,这里的注释实际上被剪掉并提供给C 编译器。如果它不是有效的 C,它就不会编译。


如果你想包含 C++ 代码,你可以,但你必须将它放在一个或多个单独的文件中(从技术上讲,C 或 C++ 术语中的“翻译单元”)。然后 CGo 将该文件编译为目标代码。


然而,下一个问题是目标代码必须符合CGo 实现的 C外部函数接口。这意味着您的 C++ 代码必须返回 C 类型(和/或接收此类类型作为参数)。由于std::string不是 C 字符串,您实际上不能直接返回它。


它不是很有效(并且存在一些解决此问题的尝试),但处理此问题的常用方法是让 C 函数返回 C 风格的“char *”或“const char *”字符串。如果字符串本身具有非静态持续时间——就像你的那样——你必须malloc在这里使用,特别是C malloc(std::malloc可能是不可互操作的)。


函数本身也必须可以从C 代码调用。这意味着我们需要使用extern "C"它。


因此,我们的plus.cpp文件(或任何你想称呼它的东西)可能会这样读:


#include <stdlib.h>

#include <iostream>


std::string plus() {

        return "Hello World!\n";

}


extern "C" {

char *cstyle_plus() {

        // Ideally we'd use strdup here, but Windows calls it _strdup

        char *ret = static_cast<char *>(malloc(plus().length() + 1));

        if (ret != NULL) {

                strcpy(ret, plus().c_str());

        }

        return static_cast<char *>(ret);

}

}

然后我们可以使用这个从 Go 中调用它main.go:


package main


/*

#include <stdlib.h>

extern char *cstyle_plus();

*/

import "C"

import (

        "fmt"

        "unsafe"

)


func Plus_go() string {

        s := C.cstyle_plus()

        defer C.free(unsafe.Pointer(s))

        return C.GoString(s)

}


func main() {

        a := Plus_go()

        fmt.Println(a)

}

添加一个 trivialgo.mod和 building,生成的代码运行;双换行是因为C字符串里面有一个换行符,加fmt.Println了一个换行符:


$ go build

$ ./cgo_cpp

Hello World!


这段代码有点草率:应该malloc失败,它返回 NULL,并将C.GoString其变成一个空字符串。然而,真正的代码应该尽量避免这种愚蠢的分配和释放序列:例如,我们可能知道字符串长度,或者有一个static不需要这种愚蠢的字符串malloc。


查看完整回答
反对 回复 2023-01-03
  • 1 回答
  • 0 关注
  • 216 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信