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?
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?
Réponses:
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
go test
devrait fonctionner sans le makefile
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 ».
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 ++.
Depuis go1.2 +, cgo intègre et compile automatiquement le code C ++:
J'ai créé l'exemple suivant basé sur la réponse de Scott Wales . Je l'ai testé dans la go
version exécutable de macOS High Sierra 10.13.3 go1.10 darwin/amd64
.
(1) Code pour library.hpp
l'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.h
le pont nécessaire pour exposer une C
API implémentée dans C++
afin de go
pouvoir 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 œ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.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())
}
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 go
programme ne sont PAS OPTIONNELS . Vous devez les mettre exactement comme indiqué pour cgo
savoir quel en-tête et bibliothèque charger, dans ce cas:
// #cgo LDFLAGS: -L. -llibrary
// #include "library-bridge.h"
import "C"
On dirait que c'est l'une des premières questions posées sur Golang. Et en même temps, les réponses ne sont jamais mises à jour. Au cours de ces trois à quatre ans, trop de nouvelles bibliothèques et articles de blog ont été publiés. Voici les quelques liens que j'ai jugés utiles.
Appel de code C ++ depuis Go avec SWIG
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).
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.
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.
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).
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"