0

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 <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 lets Go packages call C code"_ - but the headers you try to include are C++ headers. – Ted Lyngmo Jul 05 '22 at 18:50
  • CGo also supports C++ according this page https://pkg.go.dev/cmd/cgo, if I understood well – Daniel D'champs Jul 05 '22 at 18:55
  • It mentions it at least so, perhaps... Can you see which compiler it uses? Try to make `Cgo` print out the full compilation command of the C or C++ compiler it uses. – Ted Lyngmo Jul 05 '22 at 19:01
  • I tried to show it on the output but I do not know how to do that, nevertheless I search the documentation and seems it use gcc according the page https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/cmd/cgo/gcc.go – Daniel D'champs Jul 05 '22 at 19:20
  • 1
    Hmm, I don't know [tag:go] but I assume you do. Wouldn't it be possible to modify `gcc.go` to spit out the commands it's executing? That's the approach I'd take if it doesn't have options to do so built-in. What does the `cgo` option `-debug-gcc` do? – Ted Lyngmo Jul 05 '22 at 20:03
  • `gcc -E -dM -xc -m64` ... It's running the preprocessor only and `-xc` tells it to do it in C mode so the include paths for the C++ headers will be missing. – Ted Lyngmo Jul 05 '22 at 23:28
  • I insert this another flag `//#cgo CXXFLAGS: -x c++` in the code but I am getting the same results. – Daniel D'champs Jul 06 '22 at 00:09
  • 1
    This statement is important: *When the Go tool sees that one or more Go files use the special import "C", it will look for other non-Go files in the directory and compile them as part of the Go package. Any .c, .s, .S or .sx files will be compiled with the C compiler. Any .cc, .cpp, or .cxx files will be compiled with the C++ compiler.* All C++ code must be in an appropriately named file; C++ code cannot appear in the Go comment. – torek Jul 06 '22 at 02:17
  • @torek I have follow your suggestion (see on the post edited), but I also got the same error `iostream: No such file or directory` – Daniel D'champs Jul 06 '22 at 18:48
  • You cannot `#include` the CPP code from the Go comment. No C++ code can appear in the Go comment! You must use separate compilation of the C++ code. You may put C code in the Go comment that appears before `import "C"`, or if you don't need any C code as a shim between Cgo and C++, omit that comment entirely, but you can't put the C++ code into it in any way, and that covers `#include` as well. – torek Jul 06 '22 at 20:00
  • But if I run the go code without any reference to the cpp I will not be able to get any info (in this case the string) from the .cpp. – Daniel D'champs Jul 06 '22 at 20:10
  • That's not the actual problem you'll have here. Instead, you have a whole slew of *different* problems once you fix the cpp file issue. I'll see about writing up a real answer here. – torek Jul 06 '22 at 20:14
  • The `-debug-gcc` output shows that the `gcc` is being used. It does not implicitly set the default C++ library header path or link the C++ library - for that it would have to use `g++` or set the paths and link the library explicitly. Moreover the `-xc` option forces C compilation explicitly. – Clifford Jul 06 '22 at 21:24
  • Clearly this is an X-Y problem. You need interoperability between go and c++, you have made an erroneous attempt at that and are then asking a question about your flawed solution. Ask about interoperability. If all you need is to access a variable in the C++ code, what does have to do with that? Why would you include iostream in any case - none of the code uses it - would make more sense - though that won't work either. Also you cannot "call" a variable - and your code has nothing to do with C++ variables in any event. – Clifford Jul 06 '22 at 21:32

1 Answers1

2

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.

torek
  • 448,244
  • 59
  • 642
  • 775