Comment utiliser C ++ dans Go


173

Dans le nouveau langage Go , comment appeler du code C ++? En d'autres termes, comment puis-je encapsuler mes classes C ++ et les utiliser dans Go?


1
Dans la discussion technique, SWIG a été très brièvement mentionné, quelque chose comme "... jusqu'à ce que nous ayons terminé la gorgée ..."
StackedCrooked

1
@Matt: Il souhaite probablement utiliser une bibliothèque C ++ existante sans avoir à la porter vers C ou Go. Je voulais la même chose.
Graeme Perrow

Je ne peux pas penser à une seule bibliothèque décente disponible pour C ++ et non pour C. J'adorerais savoir ce que vous avez en tête.
Matt Joiner

13
@Matt: Un exemple est la bibliothèque Boost, et il existe des milliers d'autres bibliothèques C ++ utiles. Mais peut-être que je nourris juste un troll ici ...
Frank

@Matt: dans mon cas, je voulais faire une interface Go à notre bibliothèque client existante mais la bibliothèque est principalement C ++. Le porter vers C ou Go n'est tout simplement pas une option.
Graeme Perrow

Réponses:


154

Mise à jour: j'ai réussi à lier une petite classe de test C ++ avec Go

Si vous enveloppez votre code C ++ avec une interface C, vous devriez pouvoir appeler votre bibliothèque avec cgo (voir l'exemple de gmp dans $GOROOT/misc/cgo/gmp).

Je ne suis pas sûr que l'idée d'une classe en C ++ soit vraiment exprimable dans Go, car elle n'a pas d'héritage.

Voici un exemple:

J'ai une classe C ++ définie comme:

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

que je souhaite utiliser dans Go. J'utiliserai l'interface C

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

(J'utilise a void*au lieu d'une structure C pour que le compilateur connaisse la taille de Foo)

La mise en œuvre est:

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

avec tout cela fait, le fichier Go est:

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

Le makefile que j'ai utilisé pour compiler ceci était:

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

Essayez de le tester avec:

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

Vous devrez installer la bibliothèque partagée avec make install, puis exécuter make test. Le résultat attendu est:

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
Soyez prudent, je n'ai aucune idée de ce qui pourrait arriver à la mémoire si vous l'envoyez entre les deux langues.
Scott Wales

11
Je dois dire que cet exemple me rappelle pourquoi je veux écrire du Go pur. Regardez à quel point le côté C ++ est plus grand et plus laid. Ick.
Jeff Allen

@ScottWales, une chance que vous ayez mis cela dans un dépôt sur Github ou quoi que ce soit? J'aimerais voir un exemple fonctionnel
netpoetica

7
@Arne: Vous ne rejetez pas une réponse parce que ce n'est pas la meilleure. Vous votez contre une réponse parce qu'elle n'est pas utile. Tant que cela fonctionne, cette réponse est toujours utile même s'il existe de meilleures solutions.
Graeme Perrow

Bonne nouvelle, Go va compiler cpp maintenant donc le makefile n'est plus nécessaire. Les wrappers unsafe.Pointer ne fonctionnaient pas pour moi. Une légère modification compilée pour moi: play.golang.org/p/hKuKV51cRp go test devrait fonctionner sans le makefile
Drew le

47

Semble que SWIG est actuellement la meilleure solution pour cela:

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

Il prend en charge l'héritage et permet même de sous-classer la classe C ++ avec la structure Go.Ainsi, lorsque des méthodes surchargées sont appelées dans du code C ++, le code Go est déclenché.

La section sur le C ++ dans la FAQ de Go est mise à jour et mentionne maintenant SWIG et ne dit plus « parce que Go est ramassé, il ne sera pas sage de le faire, du moins naïvement ».


9
J'aurais aimé qu'il y ait un moyen d'augmenter cela. Les autres réponses sont dépassées. Plus SWIG a versionné swig.org/Doc3.0/Go.html
dragonx

34

Vous ne pouvez pas encore tout à fait d'après ce que j'ai lu dans la FAQ :

Les programmes Go sont-ils liés aux programmes C / C ++?

Il existe deux implémentations du compilateur Go, gc (le programme 6g et ses amis) et gccgo. Gc utilise une convention d'appel et un éditeur de liens différents et ne peut donc être lié qu'à des programmes C utilisant la même convention. Il existe un tel compilateur C mais pas de compilateur C ++. Gccgo est un frontal GCC qui peut, avec précaution, être lié à des programmes C ou C ++ compilés par GCC.

Le programme cgo fournit le mécanisme d'une «interface de fonction étrangère» pour permettre un appel sécurisé des bibliothèques C à partir du code Go. SWIG étend cette capacité aux bibliothèques C ++.



13

J'ai créé l'exemple suivant basé sur la réponse de Scott Wales . Je l'ai testé dans la goversion exécutable de macOS High Sierra 10.13.3 go1.10 darwin/amd64.

(1) Code pour library.hppl'API C ++ que nous souhaitons appeler.

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

(2) Code pour library.cpp, l'implémentation 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) Code pour library-bridge.hle pont nécessaire pour exposer une CAPI implémentée dans C++afin de gopouvoir l'utiliser.

#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 pour library-bridge.cppla mise en œuvre du pont.

#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) Enfin, library.gole programme go appelant 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())
}

Utilisation du Makefile suivant

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

Je peux exécuter l'exemple de programme comme suit:

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

Important

Les commentaires ci-dessus import "C"dans le goprogramme ne sont PAS OPTIONNELS . Vous devez les mettre exactement comme indiqué pour cgosavoir quel en-tête et bibliothèque charger, dans ce cas:

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

Lien vers le dépôt GitHub avec l'exemple complet .


Merci, cela m'a beaucoup aidé!
Robert Cowham le


3

On parle d' interopérabilité entre C et Go lors de l'utilisation du compilateur gcc Go, gccgo. Il y a cependant des limitations à la fois à l'interopérabilité et à l'ensemble des fonctionnalités implémentées de Go lors de l'utilisation de gccgo (par exemple, goroutines limitées, pas de garbage collection).


2
1. Créer un langage sans possibilité de gestion manuelle de la mémoire, 2. Supprimer le ramasse-miettes? Suis-je le seul à me gratter la tête?
György Andrasek

2

Vous marchez sur un territoire inconnu ici. Voici l'exemple Go pour appeler du code C, peut-être que vous pouvez faire quelque chose comme ça après avoir lu les conventions de dénomination et d'appel C ++ , ainsi que de nombreux essais et erreurs.

Si vous avez encore envie de l'essayer, bonne chance.


1

Le problème ici est qu'une implémentation conforme n'a pas besoin de placer vos classes dans un fichier de compilation .cpp. Si le compilateur peut optimiser l'existence d'une classe, tant que le programme se comporte de la même manière sans elle, alors il peut être omis de l'exécutable de sortie.

C a une interface binaire normalisée. Vous pourrez ainsi savoir que vos fonctions sont exportées. Mais C ++ n'a pas une telle norme derrière lui.


1

Vous devrez peut-être ajouter -lc++au LDFlagsfor Golang / CGo pour reconnaître le besoin de la bibliothèque standard.


0

C'est drôle le nombre de problèmes plus larges que cette annonce a soulevés. Dan Lyke a eu une discussion très divertissante et réfléchie sur son site Web, Flutterby, sur le développement des normes interprocessus comme un moyen d'amorcer de nouveaux langages (et d'autres ramifications, mais c'est celui qui est pertinent ici).


0

Ceci peut être réalisé en utilisant la commande cgo.

En substance 'Si l'importation de "C" est immédiatement précédée d'un commentaire, ce commentaire, appelé préambule, est utilisé comme en-tête lors de la compilation des parties C du package. Par exemple: '
source: https://golang.org/cmd/cgo/

// #include <stdio.h>
// #include <errno.h>
import "C"
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.