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.
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.