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?


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

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


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.



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 .


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


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.


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.


1

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


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


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"
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.