英文:
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 .h
all works fine, but for libraries like <iostream>
, <map>
, <string>
etc, I got the following error:
fatal error: iostream: No such file or directory
4 | #include <iostream>
| ^~~~~~~~~~
Below my code:
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()
}
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 - <<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.
答案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
,具体来说是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);
}
}
然后,我们可以使用以下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
会将其转换为空字符串。然而,真正的代码应该尽可能避免这种愚蠢的分配和释放序列:我们可能知道字符串的长度,或者有一个不需要这种愚蠢的malloc
的static
字符串,例如。
英文:
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 "C"
line in one of your Go files must contain only C code above it. That is:
/*
#include <stdlib.h>
extern char *cstyle_plus();
*/
is OK, but:
/*
#include <stdlib.h>
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 "C"
around it.
Hence, our plus.cpp
file (or whatever you would like to call it) might read this way:
#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);
}
}
We can then invoke this from Go using this 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)
}
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论