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

huangapple go评论73阅读模式
英文:

How Call C++ Variables Using CGo For Standard Libraries

问题

我正在尝试使用cgo从C++代码中获取变量值。对于以.h结尾的库,一切正常,但对于像<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()
}

我添加了#cgo LDFLAGS: -lc++标志,因为我在stackoverflow上的一个答案中看到了这个建议,链接为https://stackoverflow.com/a/41615301/15024997。

我使用的是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_TYPEDEF
typedef 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"
EOF
C:\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.
英文:

I am trying to get a variable value from a c++ code using cgo. For libraries ended in .hall works fine, but for libraries like &lt;iostream&gt;, &lt;map&gt;, &lt;string&gt; etc, I got the following error:

fatal error: iostream: No such file or directory
    4 | #include &lt;iostream&gt;
      |          ^~~~~~~~~~

Below my code:

package main

/*
#cgo LDFLAGS: -lc++
#include &lt;iostream&gt;
std::string plus() {
    return &quot;Hello World!\n&quot;;
}
*/
import &quot;C&quot;
import &quot;fmt&quot;

func main() {

	a := Plus_go()
	fmt.Println(a)

}
func Plus_go() string {
	return C.plus()
}

I added the #cgo LDFLAGS: -lc++ flag because I saw this recommendation on an answer here on stackoverflow at https://stackoverflow.com/a/41615301/15024997.

I am using VS Code (not VS Studio), windows 10, Go 1.18 (lastest version).

I ran the following commands go tool cgo -debug-gcc mycode.go to trace compiler execution and output:

$ gcc -E -dM -xc -m64 - &lt;&lt;EOF

#line 1 &quot;cgo-builtin-prolog&quot;
#include &lt;stddef.h&gt; /* for ptrdiff_t and size_t below */

/* Define intgo when compiling with GCC.  */
typedef ptrdiff_t intgo;

#define GO_CGO_GOSTRING_TYPEDEF
typedef 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 &quot;C:\\Users\\Home\\OneDrive\\Desktop\\DevicesC++\\devices.go&quot;


#include &lt;iostream&gt;
std::string plus() {
    return &quot;Hello World!\n&quot;;
}

#line 1 &quot;cgo-generated-wrapper&quot;
EOF
C:\Users\Home\OneDrive\Desktop\DevicesC++\devices.go:5:10: fatal error: iostream: No such file or directory
    5 | #include &lt;iostream&gt;
      |          ^~~~~~~~~~
compilation terminated.
C:\Users\Home\OneDrive\Desktop\DevicesC++\devices.go:5:10: fatal error: iostream: No such file or directory
    5 | #include &lt;iostream&gt;
      |          ^~~~~~~~~~
compilation terminated.

答案1

得分: 1

CGo允许你将Go代码与实现C风格外部函数接口的代码进行链接。这并不意味着你可以随意插入任意语言的代码。

让我们从第一个问题开始,即你的一个Go文件中的import "C"行上方必须只包含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 Foreign Function Interface。这意味着你的C++代码必须返回C类型(和/或接收这样的类型作为参数)。由于std::string不是C字符串,你无法直接返回它。

这并不是很高效(也有一些尝试解决此问题的方法),但通常处理这个问题的方法是让C函数返回C风格的“char ”或“const char ”字符串。如果字符串本身具有非静态持续时间(就像你的字符串一样),你必须在这里使用malloc,具体来说是Cmallocstd::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);
}
}

然后,我们可以使用以下main.go从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)
}

添加一个简单的go.mod并构建,生成的代码可以运行;双换行是因为C字符串中有一个换行符,而fmt.Println会添加一个换行符:

$ go build
$ ./cgo_cpp
Hello World!

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

英文:

CGo allows you to link your Go code against code that implements the C-style foreign function interfaces. This does not mean that you can just stick arbitrary-language code into place.

Let's start with the first problem, which is that the import &quot;C&quot; line in one of your Go files must contain only C code above it. That is:

/*
#include &lt;stdlib.h&gt;
extern char *cstyle_plus();
*/

is OK, but:

/*
#include &lt;stdlib.h&gt;
extern std::string *plus();
*/

is not, nor may you #include any C++ header here. To oversimplify things a bit, the comment here is in effect snipped out and fed to a C compiler. If it's not valid C, it won't compile.

If you want to include C++ code, you can, but you must put it in a separate file or files (technically speaking, a "translation unit" in C or C++ terminology). CGo will then compile that file to object code.

The next problem, however, is that the object code must conform to the C Foreign Function Interface implemented by CGo. This means your C++ code must return C types (and/or receive such types as arguments). As std::string is not a C string, you literally can't return it directly.

It's not very efficient (and there exist some attempts to work around this), but the usual method for dealing with this is to have C functions return C-style "char *" or "const char *" strings. If the string itself has non-static duration—as yours does—you must use malloc here, specifically the C malloc (std::malloc may be a non-interoperable one).

The function itself must also be callable from C code. This means we'll need to use extern &quot;C&quot; around it.

Hence, our plus.cpp file (or whatever you would like to call it) might read this way:

#include &lt;stdlib.h&gt;
#include &lt;iostream&gt;

std::string plus() {
        return &quot;Hello World!\n&quot;;
}

extern &quot;C&quot; {
char *cstyle_plus() {
        // Ideally we&#39;d use strdup here, but Windows calls it _strdup
        char *ret = static_cast&lt;char *&gt;(malloc(plus().length() + 1));
        if (ret != NULL) {
                strcpy(ret, plus().c_str());
        }
        return static_cast&lt;char *&gt;(ret);
}
}

We can then invoke this from Go using this main.go:

package main

/*
#include &lt;stdlib.h&gt;
extern char *cstyle_plus();
*/
import &quot;C&quot;
import (
        &quot;fmt&quot;
        &quot;unsafe&quot;
)

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

Adding a trivial go.mod and building, the resulting code runs; the double newline is because the C string has a newline in it, and fmt.Println adds a newline:

$ go build
$ ./cgo_cpp
Hello World!

This code is a bit sloppy: should malloc fail, it returns NULL, and C.GoString turns that into an empty string. However, real code should try, as much as possible, to avoid this kind of silly allocation-and-free sequence: we might know the string length, or have a static string that does not require this kind of silly malloc, for instance.

huangapple
  • 本文由 发表于 2022年7月6日 02:19:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/72873908.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定