Dans le nouveau langage Go , comment appeler le code C++? En d'autres termes, comment puis-je envelopper mes classes C++ et les utiliser dans Go?
Mise à jour: J'ai réussi à lier une petite classe de test C++ avec Go
Si vous encapsulez 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 sais pas si l'idée d'une classe en C++ est 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 veux utiliser dans Go. Je vais utiliser 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 un 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 ce qui est 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'avais l'habitude de compiler é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 aurez besoin d'installer la bibliothèque partagée avec make install, puis d'exécuter make test. La sortie attendue 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
Il semble que SWIG soit 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 remplacées sont appelées dans du code C++, le code Go est déclenché.
La section sur C++ dans la FAQ Go est mise à jour et mentionne maintenant SWIG et ne dit plus " car Go est mal ordonné, il sera mal avisé de le faire, du moins naïvement. ".
Vous ne pouvez pas encore tout à fait de 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 une interface GCC qui peut, avec précaution, être liée à 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 l’appel sécurisé des bibliothèques C à partir du code Go. SWIG étend cette capacité aux bibliothèques C++.
À partir de go1.2 +, cgo incorpore et compile automatiquement le code C++:
On dirait que c'est l'une des premières questions posées sur Golang. Et le même temps répond de ne jamais mettre à jour. Pendant ces trois ou quatre années, trop de nouvelles bibliothèques et de blogs ont été publiés. Vous trouverez ci-dessous quelques liens sur ce qui m'a paru utile.
Appel du code C++ depuis Go with SWIG
J'ai créé l'exemple suivant basé sur réponse de Scott Wales . Je l'ai testé sous macOS High Sierra 10.13.3 sous go
version go1.10 darwin/AMD64
.
(1) Code pour library.hpp
, l’API C++ que nous cherchons à 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.h
le pont nécessaire pour exposer une API C
implémentée dans C++
afin que go
puisse 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.cpp
, la mise en place 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.go
, le 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())
}
Utiliser le 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 le programme d'exemple 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 programme go
sont NON FACULTATIF. Vous devez les mettre exactement comme indiqué pour que cgo
sache quel en-tête et quelle bibliothèque charger, dans ce cas:
// #cgo LDFLAGS: -L. -llibrary
// #include "library-bridge.h"
import "C"
On parle de interopérabilité entre C et Go lors de l'utilisation du compilateur gcc Go, gccgo. Il existe toutefois des limites à l'interopérabilité et à l'ensemble de fonctionnalités implémentées de Go lors de l'utilisation de gccgo (par exemple, goroutines limitées, pas de garbage collection).
Vous marchez sur un territoire inconnu ici. Ici est l'exemple de Go permettant d'appeler du code C, vous pouvez peut-être faire quelque chose comme ça après avoir lu le nom C++ mangling et conventions d'appel, et beaucoup d'essais et d'erreurs.
Si vous avez encore envie d'essayer, bonne chance.
Le problème ici est qu'une implémentation conforme n'a pas besoin de mettre vos classes dans un fichier .cpp de compilation. Si le compilateur peut optimiser l'existence d'une classe, tant que le programme se comporte de la même manière sans elle, il peut être omis de l'exécutable en sortie.
C a une interface binaire standardisée. Par conséquent, vous saurez que vos fonctions sont exportées. Mais C++ n’a aucune norme de ce type derrière elle.
C'est drôle de voir combien de questions plus larges cette annonce a été soulevée. Sur son site Web, Flutterby, Dan Lyke a eu une discussion très amusante et réfléchie sur le développement de Interprocess Standards comme moyen d’amorcer de nouvelles langues (et d’autres ramifications, mais c’est celle qui est pertinente ici).
Vous devrez peut-être ajouter -lc++
au LDFlags
pour que Golang/CGo reconnaisse le besoin de la bibliothèque standard.