Verwendung von C ++ in Go

173

Wie rufe ich in der neuen Go- Sprache C ++ - Code auf? Mit anderen Worten, wie kann ich meine C ++ - Klassen umbrechen und in Go verwenden?

Frank
quelle
1
In dem Tech Talk wurde SWIG sehr kurz erwähnt, so etwas wie "... bis wir den Schluck fertig haben ..."
StackedCrooked
1
@Matt: Wahrscheinlich möchte er eine vorhandene C ++ - Bibliothek verwenden, ohne sie nach C oder Go portieren zu müssen. Ich wollte das Gleiche.
Graeme Perrow
Ich kann mir keine anständige Bibliothek vorstellen, die für C ++ und nicht für C verfügbar ist. Ich würde gerne wissen, was Sie vorhaben.
Matt Joiner
13
@Matt: Ein Beispiel ist die Boost-Bibliothek, und es gibt Tausende anderer nützlicher C ++ - Bibliotheken. Aber vielleicht füttere ich hier nur einen Troll ...
Frank
@Matt: In meinem Fall wollte ich eine Go-Schnittstelle zu unserer vorhandenen Client-Bibliothek erstellen, aber die Bibliothek besteht hauptsächlich aus C ++. Das Portieren nach C oder Go ist einfach keine Option.
Graeme Perrow

Antworten:

154

Update: Es ist mir gelungen, eine kleine Test-C ++ - Klasse mit Go zu verknüpfen

Wenn Sie Ihren C ++ - Code mit einer C-Schnittstelle umschließen, sollten Sie in der Lage sein, Ihre Bibliothek mit cgo aufzurufen (siehe das Beispiel von gmp in $GOROOT/misc/cgo/gmp).

Ich bin mir nicht sicher, ob die Idee einer Klasse in C ++ in Go wirklich ausgedrückt werden kann, da sie keine Vererbung hat.

Hier ist ein Beispiel:

Ich habe eine C ++ - Klasse definiert als:

// foo.hpp
class cxxFoo {
public:
  int a;
  cxxFoo(int _a):a(_a){};
  ~cxxFoo(){};
  void Bar();
};

// foo.cpp
#include <iostream>
#include "foo.hpp"
void
cxxFoo::Bar(void){
  std::cout<<this->a<<std::endl;
}

was ich in Go verwenden möchte. Ich werde die C-Schnittstelle verwenden

// foo.h
#ifdef __cplusplus
extern "C" {
#endif
  typedef void* Foo;
  Foo FooInit(void);
  void FooFree(Foo);
  void FooBar(Foo);
#ifdef __cplusplus
}
#endif

(Ich verwende eine void*anstelle einer C-Struktur, damit der Compiler die Größe von Foo kennt.)

Die Implementierung ist:

//cfoo.cpp
#include "foo.hpp"
#include "foo.h"
Foo FooInit()
{
  cxxFoo * ret = new cxxFoo(1);
  return (void*)ret;
}
void FooFree(Foo f)
{
  cxxFoo * foo = (cxxFoo*)f;
  delete foo;
}
void FooBar(Foo f)
{
  cxxFoo * foo = (cxxFoo*)f;
  foo->Bar();
}

Nach all dem lautet die Go-Datei:

// foo.go
package foo
// #include "foo.h"
import "C"
import "unsafe"
type GoFoo struct {
     foo C.Foo;
}
func New()(GoFoo){
     var ret GoFoo;
     ret.foo = C.FooInit();
     return ret;
}
func (f GoFoo)Free(){
     C.FooFree(unsafe.Pointer(f.foo));
}
func (f GoFoo)Bar(){
     C.FooBar(unsafe.Pointer(f.foo));
}

Das Makefile, mit dem ich das kompiliert habe, war:

// makefile
TARG=foo
CGOFILES=foo.go
include $(GOROOT)/src/Make.$(GOARCH)
include $(GOROOT)/src/Make.pkg
foo.o:foo.cpp
    g++ $(_CGO_CFLAGS_$(GOARCH)) -fPIC -O2 -o $@ -c $(CGO_CFLAGS) $<
cfoo.o:cfoo.cpp
    g++ $(_CGO_CFLAGS_$(GOARCH)) -fPIC -O2 -o $@ -c $(CGO_CFLAGS) $<
CGO_LDFLAGS+=-lstdc++
$(elem)_foo.so: foo.cgo4.o foo.o cfoo.o
    gcc $(_CGO_CFLAGS_$(GOARCH)) $(_CGO_LDFLAGS_$(GOOS)) -o $@ $^ $(CGO_LDFLAGS)

Versuchen Sie es mit:

// foo_test.go
package foo
import "testing"
func TestFoo(t *testing.T){
    foo := New();
    foo.Bar();
    foo.Free();
}

Sie müssen die gemeinsam genutzte Bibliothek mit make install installieren und dann make test ausführen. Die erwartete Ausgabe ist:

gotest
rm -f _test/foo.a _gotest_.6
6g -o _gotest_.6 foo.cgo1.go foo.cgo2.go foo_test.go
rm -f _test/foo.a
gopack grc _test/foo.a _gotest_.6  foo.cgo3.6
1
PASS
Scott Wales
quelle
1
Seien Sie vorsichtig damit, ich habe keine Ahnung, was mit dem Speicher passieren könnte, wenn Sie ihn zwischen den beiden Sprachen senden.
Scott Wales
11
Ich muss sagen, dieses Beispiel erinnert mich daran, warum ich reines Go schreiben möchte. Schauen Sie, wie viel größer und hässlicher die C ++ - Seite ist. Ick.
Jeff Allen
@ScottWales Gibt es eine Chance, dass du dies in ein Repo auf Github oder so gesetzt hast? Ich würde gerne ein funktionierendes Beispiel sehen
netpoetica
7
@Arne: Sie stimmen eine Antwort nicht ab, weil sie nicht die beste ist. Sie stimmen eine Antwort ab, weil sie nicht hilfreich ist. Solange es funktioniert, ist diese Antwort auch dann hilfreich, wenn es bessere Lösungen gibt.
Graeme Perrow
Gute Nachrichten, Go wird jetzt cpp kompilieren, sodass das Makefile nicht mehr benötigt wird. Die unsicheren Zeiger-Wrapper haben bei mir nicht funktioniert. Eine kleine Modifikation für mich zusammengestellt: play.golang.org/p/hKuKV51cRp go test sollte ohne das Makefile funktionieren
Drew
47

Derzeit scheint SWIG die beste Lösung dafür zu sein:

http://www.swig.org/Doc2.0/Go.html

Es unterstützt die Vererbung und ermöglicht sogar die Unterklasse von C ++ - Klassen mit Go-Struktur. Wenn überschriebene Methoden in C ++ - Code aufgerufen werden, wird Go-Code ausgelöst.

Der Abschnitt über C ++ in den Go-FAQ wird aktualisiert und erwähnt nun SWIG und sagt nicht mehr " weil Go Müll gesammelt hat, ist es unklug, dies zumindest naiv zu tun ".

kolen
quelle
9
Ich wünschte, es gäbe eine Möglichkeit, dies zu verbessern. Die anderen Antworten sind veraltet. Plus SWIG hat swig.org/Doc3.0/Go.html
dragonx
34

Sie können noch nicht ganz von dem, was ich in den FAQ gelesen habe :

Verknüpfen Go-Programme mit C / C ++ - Programmen?

Es gibt zwei Go-Compiler-Implementierungen, gc (das 6g-Programm und Freunde) und gccgo. Gc verwendet eine andere Aufrufkonvention und einen anderen Linker und kann daher nur mit C-Programmen verknüpft werden, die dieselbe Konvention verwenden. Es gibt einen solchen C-Compiler, aber keinen C ++ - Compiler. Gccgo ist ein GCC-Frontend, das mit Sorgfalt mit GCC-kompilierten C- oder C ++ - Programmen verknüpft werden kann.

Das Programm cgo bietet den Mechanismus für eine „Fremdfunktionsschnittstelle“, um das sichere Aufrufen von C-Bibliotheken aus Go-Code zu ermöglichen. SWIG erweitert diese Funktion auf C ++ - Bibliotheken.

Dirk Eddelbuettel
quelle
13

Ich habe das folgende Beispiel basierend auf der Antwort von Scott Wales erstellt . Ich habe es in der laufenden goVersion von macOS High Sierra 10.13.3 getestet go1.10 darwin/amd64.

(1) Code für library.hppdie C ++ - API, die wir aufrufen möchten.

#pragma once
class Foo {
 public:
  Foo(int value);
  ~Foo();
  int value() const;    
 private:
  int m_value;
};

(2) Code für library.cppdie C ++ - Implementierung.

#include "library.hpp"
#include <iostream>

Foo::Foo(int value) : m_value(value) {
  std::cout << "[c++] Foo::Foo(" << m_value << ")" << std::endl;
}

Foo::~Foo() { std::cout << "[c++] Foo::~Foo(" << m_value << ")" << std::endl; }

int Foo::value() const {
  std::cout << "[c++] Foo::value() is " << m_value << std::endl;
  return m_value;
}

(3) Code für library-bridge.hdie Bridge, der erforderlich ist, um eine Cimplementierte API verfügbar zu machen, C++damit diese goverwendet werden kann.

#pragma once
#ifdef __cplusplus
extern "C" {
#endif

void* LIB_NewFoo(int value);
void LIB_DestroyFoo(void* foo);
int LIB_FooValue(void* foo);

#ifdef __cplusplus
}  // extern "C"
#endif

(4) Code für library-bridge.cppdie Umsetzung der Brücke.

#include <iostream>

#include "library-bridge.h"
#include "library.hpp"

void* LIB_NewFoo(int value) {
  std::cout << "[c++ bridge] LIB_NewFoo(" << value << ")" << std::endl;
  auto foo = new Foo(value);
  std::cout << "[c++ bridge] LIB_NewFoo(" << value << ") will return pointer "
            << foo << std::endl;
  return foo;
}

// Utility function local to the bridge's implementation
Foo* AsFoo(void* foo) { return reinterpret_cast<Foo*>(foo); }

void LIB_DestroyFoo(void* foo) {
  std::cout << "[c++ bridge] LIB_DestroyFoo(" << foo << ")" << std::endl;
  AsFoo(foo)->~Foo();
}

int LIB_FooValue(void* foo) {
  std::cout << "[c++ bridge] LIB_FooValue(" << foo << ")" << std::endl;
  return AsFoo(foo)->value();
}

(5) Schließlich library.godas go-Programm, das die C ++ - API aufruft.

package main

// #cgo LDFLAGS: -L. -llibrary
// #include "library-bridge.h"
import "C"
import "unsafe"
import "fmt"

type Foo struct {
    ptr unsafe.Pointer
}

func NewFoo(value int) Foo {
    var foo Foo
    foo.ptr = C.LIB_NewFoo(C.int(value))
    return foo
}

func (foo Foo) Free() {
    C.LIB_DestroyFoo(foo.ptr)
}

func (foo Foo) value() int {
    return int(C.LIB_FooValue(foo.ptr))
}

func main() {
    foo := NewFoo(42)
    defer foo.Free() // The Go analog to C++'s RAII
    fmt.Println("[go]", foo.value())
}

Verwenden Sie das folgende Makefile

liblibrary.so: library.cpp library-bridge.cpp
    clang++ -o liblibrary.so library.cpp library-bridge.cpp \
    -std=c++17 -O3 -Wall -Wextra -fPIC -shared

Ich kann das Beispielprogramm wie folgt ausführen:

$ make
clang++ -o liblibrary.so library.cpp library-bridge.cpp \
    -std=c++17 -O3 -Wall -Wextra -fPIC -shared
$ go run library.go
[c++ bridge] LIB_NewFoo(42)
[c++] Foo::Foo(42)
[c++ bridge] LIB_NewFoo(42) will return pointer 0x42002e0
[c++ bridge] LIB_FooValue(0x42002e0)
[c++] Foo::value() is 42
[go] 42
[c++ bridge] LIB_DestroyFoo(0x42002e0)
[c++] Foo::~Foo(42)

Wichtig

Die obigen Kommentare import "C"im goProgramm sind NICHT OPTIONAL . Sie müssen sie genau wie gezeigt platzieren, damit Sie cgowissen, welcher Header und welche Bibliothek geladen werden sollen. In diesem Fall:

// #cgo LDFLAGS: -L. -llibrary
// #include "library-bridge.h"
import "C"

Link zum GitHub-Repo mit dem vollständigen Beispiel .

Escualo
quelle
Danke - das war sehr hilfreich!
Robert Cowham
3

Bei Verwendung des gcc Go-Compilers gccgo wird über die Interoperabilität zwischen C und Go gesprochen. Bei der Verwendung von gccgo gibt es jedoch Einschränkungen sowohl für die Interoperabilität als auch für den implementierten Funktionsumfang von Go (z. B. eingeschränkte Goroutinen, keine Speicherbereinigung).

fbrereto
quelle
2
1. Erstellen Sie eine Sprache ohne Funktionen für die manuelle Speicherverwaltung. 2. Entfernen Sie die Speicherbereinigung. Bin ich der einzige, der sich dabei am Kopf kratzt?
György Andrasek
2

Sie betreten hier Neuland. Hier ist das Go-Beispiel für das Aufrufen von C-Code. Vielleicht können Sie so etwas tun, nachdem Sie sich mit den Konventionen zum Verwalten und Aufrufen von C ++ - Namen und vielen Versuchen und Irrtümern befasst haben.

Wenn Sie es immer noch versuchen möchten, viel Glück.

György Andrasek
quelle
1

Das Problem hierbei ist, dass eine kompatible Implementierung Ihre Klassen nicht in eine kompilierte CPP-Datei einfügen muss. Wenn der Compiler die Existenz einer Klasse optimieren kann, solange sich das Programm ohne sie genauso verhält, kann sie in der ausführbaren Ausgabedatei weggelassen werden.

C hat eine standardisierte binäre Schnittstelle. Daher können Sie wissen, dass Ihre Funktionen exportiert werden. Aber C ++ hat keinen solchen Standard dahinter.

Billy ONeal
quelle
1

Möglicherweise müssen Sie -lc++das LDFlagsfür Golang / CGo hinzufügen, um die Notwendigkeit der Standardbibliothek zu erkennen.

ggobieski
quelle
0

Komisch, wie viele umfassendere Themen diese Ankündigung ausgebaggert hat. Dan Lyke hatte auf seiner Website Flutterby eine sehr unterhaltsame und nachdenkliche Diskussion über die Entwicklung von Interprozessstandards , um neue Sprachen zu booten (und andere Konsequenzen, aber das ist diejenige, die hier von Bedeutung ist).

Don Wakefield
quelle
0

Dies kann mit dem Befehl cgo erreicht werden.

Im Wesentlichen 'Wenn dem Import von "C" unmittelbar ein Kommentar vorausgeht, wird dieser Kommentar, der als Präambel bezeichnet wird, als Header beim Kompilieren der C-Teile des Pakets verwendet. Zum Beispiel: '
Quelle: https://golang.org/cmd/cgo/

// #include <stdio.h>
// #include <errno.h>
import "C"
Devendra Mukharaiya
quelle