Come usare C ++ in Go


173

Nella nuova lingua Go , come posso chiamare il codice C ++? In altre parole, come posso avvolgere le mie classi C ++ e usarle in Go?


1
Nel discorso tecnico SWIG è stato menzionato molto brevemente, qualcosa del tipo "fino a quando non avremo finito il sorso ..."
StackedCrooked

1
@Matt: Probabilmente vuole usare una libreria C ++ esistente senza doverla portare su C o Go. Volevo la stessa cosa.
Graeme Perrow,

Non riesco a pensare a un'unica libreria decente disponibile per C ++ e non per C. Mi piacerebbe sapere cosa hai in mente.
Matt Joiner,

13
@Matt: un esempio è la libreria Boost e ci sono migliaia di altre utili librerie C ++. Ma forse sto solo dando da mangiare a un troll qui ...
Frank,

@Matt: nel mio caso, volevo creare un'interfaccia Go per la nostra libreria client esistente, ma la libreria è principalmente C ++. Il porting su C o Go non è semplicemente un'opzione.
Graeme Perrow,

Risposte:


154

Aggiornamento: sono riuscito a collegare una piccola classe C ++ di prova con Go

Se avvolgi il tuo codice C ++ con un'interfaccia C, dovresti essere in grado di chiamare la tua libreria con cgo (vedi l'esempio di gmp in $GOROOT/misc/cgo/gmp).

Non sono sicuro che l'idea di una classe in C ++ sia davvero espressibile in Go, in quanto non ha eredità.

Ecco un esempio:

Ho una classe C ++ definita come:

// 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;
}

che voglio usare in Go. Userò l'interfaccia C.

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

(Uso una struttura void*anziché una C in modo che il compilatore conosca le dimensioni di Foo)

L'implementazione è:

//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();
}

fatto tutto ciò, il file Go è:

// 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));
}

Il makefile che ho usato per compilare questo era:

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

Prova a provarlo con:

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

Dovrai installare la libreria condivisa con make install, quindi esegui make test. L'output previsto è:

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
Fai attenzione, non ho idea di cosa potrebbe accadere alla memoria se la invii tra le due lingue.
Scott Wales,

11
Devo dire che questo esempio mi ricorda perché voglio scrivere pure Go. Guarda quanto è più grande e brutto il lato C ++. Ick.
Jeff Allen,

@ScottWales hai qualche possibilità che tu possa averlo inserito in un repository su Github o altro? Mi piacerebbe vedere un esempio funzionante
netpoetica,

7
@Arne: non devi sottovalutare una risposta perché non è la migliore. Declassate una risposta perché non è utile. Finché funziona, questa risposta è ancora utile anche se ci sono soluzioni migliori.
Graeme Perrow,

Buone notizie, Go compilerà cpp ora in modo che il makefile non sia più necessario. I wrapper non sicuri.Pointer non hanno funzionato per me. Una leggera modifica compilata per me: play.golang.org/p/hKuKV51cRp go test dovrebbe funzionare senza il makefile
Drew

47

Sembra che attualmente SWIG sia la soluzione migliore per questo:

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

Supporta l'ereditarietà e consente persino di sottoclassare la classe C ++ con Go struct, quindi quando i metodi sovrascritti vengono chiamati nel codice C ++, il codice Go viene attivato.

La sezione relativa al C ++ nelle Domande frequenti su Go viene aggiornata e ora menziona SWIG e non dice più " poiché Go è stato spazzato via, non è saggio farlo, almeno ingenuamente ".


9
Vorrei che ci fosse un modo per risolverlo. Le altre risposte sono obsolete. Inoltre SWIG ha aggiornato la versione di swig.org/Doc3.0/Go.html
dragonx,

34

Non puoi ancora abbastanza da quello che ho letto nelle FAQ :

I programmi Go si collegano ai programmi C / C ++?

Esistono due implementazioni del compilatore Go, gc (il programma 6g e amici) e gccgo. Gc utilizza una convenzione di chiamata e un linker diversi e pertanto può essere collegato solo ai programmi C utilizzando la stessa convenzione. Esiste un tale compilatore C ma nessun compilatore C ++. Gccgo è un front-end GCC che può, con cura, essere collegato con i programmi C o C ++ compilati da GCC.

Il programma cgo fornisce il meccanismo per una "interfaccia di funzione esterna" per consentire la chiamata sicura delle librerie C dal codice Go. SWIG estende questa capacità alle librerie C ++.



13

Ho creato il seguente esempio basato sulla risposta di Scott Wales . L'ho provato in macOS High Sierra 10.13.3 goversione corrente go1.10 darwin/amd64.

(1) Codice per library.hpp, l'API C ++ che intendiamo chiamare.

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

(2) Codice per library.cppl'implementazione C ++.

#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) Codice per library-bridge.hil bridge necessario per esporre Cun'API implementata in C++modo che gopossa usarla.

#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) Codice per library-bridge.cpp, l'implementazione del ponte.

#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) Infine, library.goil programma go chiama l'API C ++.

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())
}

Utilizzando il seguente 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

Posso eseguire il programma di esempio come segue:

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

Importante

I commenti sopra riportati import "C"nel goprogramma NON SONO FACOLTATIVI . Devi metterli esattamente come mostrato in modo che cgosappia quale intestazione e libreria caricare, in questo caso:

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

Link al repository GitHub con l'esempio completo .


Grazie - è stato molto utile!
Robert Cowham,


3

Si parla di interoperabilità tra C e Go quando si utilizza il compilatore gcc Go, gccgo. Ci sono limiti sia all'interoperabilità che al set di funzionalità implementate di Go quando si utilizza gccgo (ad es. Goroutine limitate, nessuna raccolta dei rifiuti).


2
1. Creare una lingua senza servizi per la gestione manuale della memoria, 2. Rimuovere la garbage collection? Sono l'unico a grattarmi la testa?
György Andrasek,

2

Stai camminando su un territorio inesplorato qui. Ecco l'esempio Go per chiamare il codice C, forse puoi fare qualcosa del genere dopo aver letto la modifica del nome C ++ convenzioni di chiamata e di e un sacco di tentativi ed errori.

Se hai ancora voglia di provarlo, buona fortuna.


1

Il problema qui è che un'implementazione conforme non ha bisogno di mettere le tue classi in un file .cpp compilato. Se il compilatore può ottimizzare l'esistenza di una classe, purché il programma si comporti allo stesso modo senza di essa, allora può essere omesso dall'eseguibile di output.

C ha un'interfaccia binaria standardizzata. Pertanto, sarai in grado di sapere che le tue funzioni vengono esportate. Ma C ++ non ha tali standard dietro.


1

Potrebbe essere necessario aggiungere -lc++a LDFlagsper Golang / CGo per riconoscere la necessità della libreria standard.


0

Divertente quante questioni più ampie questo annuncio è stato risolto. Dan Lyke ha avuto una discussione molto divertente e ponderata sul suo sito web, Flutterby, sullo sviluppo degli Standard Interprocesso come un modo per avviare nuove lingue (e altre ramificazioni, ma è quello che è germano qui).


0

Questo può essere ottenuto usando il comando cgo.

In sostanza 'Se l'importazione di "C" è immediatamente preceduta da un commento, quel commento, chiamato preambolo, viene usato come intestazione durante la compilazione delle parti C del pacchetto. Ad esempio: '
fonte: https://golang.org/cmd/cgo/

// #include <stdio.h>
// #include <errno.h>
import "C"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.