En el nuevo lenguaje Go , ¿cómo llamo al código C ++? En otras palabras, ¿cómo puedo envolver mis clases de C ++ y usarlas en Go?
En el nuevo lenguaje Go , ¿cómo llamo al código C ++? En otras palabras, ¿cómo puedo envolver mis clases de C ++ y usarlas en Go?
Respuestas:
Actualización: logré vincular una pequeña clase de prueba C ++ con Go
Si ajusta su código C ++ con una interfaz C, debería poder llamar a su biblioteca con cgo (consulte el ejemplo de gmp in $GOROOT/misc/cgo/gmp
).
No estoy seguro de si la idea de una clase en C ++ es realmente expresable en Go, ya que no tiene herencia.
Aquí hay un ejemplo:
Tengo una clase de C ++ definida como:
// 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 quiero usar en Go. Usaré la interfaz C
// foo.h
#ifdef __cplusplus
extern "C" {
#endif
typedef void* Foo;
Foo FooInit(void);
void FooFree(Foo);
void FooBar(Foo);
#ifdef __cplusplus
}
#endif
(Utilizo una void*
estructura en lugar de una C para que el compilador conozca el tamaño de Foo)
La implementación es:
//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();
}
Con todo eso hecho, el archivo Go es:
// 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));
}
El makefile que solía compilar 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)
Intenta probarlo con:
// foo_test.go
package foo
import "testing"
func TestFoo(t *testing.T){
foo := New();
foo.Bar();
foo.Free();
}
Deberá instalar la biblioteca compartida con make install y luego ejecutar make test. La salida esperada es:
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
debería funcionar sin el archivo MAKE
Parece que actualmente SWIG es la mejor solución para esto:
http://www.swig.org/Doc2.0/Go.html
Es compatible con la herencia e incluso permite la subclase de la clase C ++ con Go struct, de modo que cuando se invocan métodos anulados en el código C ++, se activa el código Go.
La sección sobre C ++ en Preguntas frecuentes sobre Go se actualiza y ahora menciona SWIG y ya no dice " porque Go es basura recolectada, no será prudente hacerlo, al menos ingenuamente ".
Todavía no puedes ver lo que leí en las preguntas frecuentes :
¿Los programas Go se vinculan con los programas C / C ++?
Hay dos implementaciones del compilador Go, gc (el programa 6g y amigos) y gccgo. Gc usa una convención de llamada y un enlazador diferentes y, por lo tanto, solo se puede vincular con programas en C que usan la misma convención. Existe un compilador de C pero no un compilador de C ++. Gccgo es un front-end de GCC que, con cuidado, puede vincularse con programas C o C ++ compilados con GCC.
El programa cgo proporciona el mecanismo para una "interfaz de función externa" para permitir la llamada segura de las bibliotecas C desde el código Go. SWIG extiende esta capacidad a las bibliotecas de C ++.
A partir de go1.2 +, cgo incorpora y compila automáticamente el código C ++:
He creado el siguiente ejemplo basado en la respuesta de Scott Wales . Lo probé en la go
versión de macOS High Sierra 10.13.3 go1.10 darwin/amd64
.
(1) Código para library.hpp
la API de C ++ que pretendemos llamar.
#pragma once
class Foo {
public:
Foo(int value);
~Foo();
int value() const;
private:
int m_value;
};
(2) Código para library.cpp
la implementación de 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) Código para library-bridge.h
el puente necesario para exponer una C
API implementada C++
para que go
pueda 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) Código para library-bridge.cpp
la implementación del puente.
#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) Finalmente, library.go
el programa go llama a la API de 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())
}
Usando el siguiente 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
Puedo ejecutar el programa de ejemplo de la siguiente manera:
$ 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
Los comentarios anteriores import "C"
en el go
programa NO SON OPCIONALES . Debe colocarlos exactamente como se muestra para que cgo
sepa qué encabezado y biblioteca cargar, en este caso:
// #cgo LDFLAGS: -L. -llibrary
// #include "library-bridge.h"
import "C"
Parece que es una de las primeras preguntas sobre Golang. Y al mismo tiempo responde que nunca se actualizará. Durante estos tres o cuatro años, se han publicado demasiadas bibliotecas y publicaciones de blog nuevas. Debajo están los pocos enlaces que me parecieron útiles.
Llamar a código C ++ desde ir con SWIG
Se habla de interoperabilidad entre C y Go cuando se usa el compilador gcc Go, gccgo. Sin embargo, existen limitaciones tanto para la interoperabilidad como para el conjunto de características implementadas de Go cuando se usa gccgo (por ejemplo, goroutines limitados, no recolección de basura).
El problema aquí es que una implementación compatible no necesita colocar sus clases en un archivo de compilación .cpp. Si el compilador puede optimizar la existencia de una clase, siempre que el programa se comporte de la misma manera sin ella, entonces puede omitirse del ejecutable de salida.
C tiene una interfaz binaria estandarizada. Por lo tanto, podrá saber que sus funciones se exportan. Pero C ++ no tiene ese estándar detrás.
Es curioso cuántos problemas más amplios ha desenterrado este anuncio. Dan Lyke tuvo una discusión muy entretenida y reflexiva en su sitio web, Flutterby, sobre el desarrollo de estándares entre procesos como una forma de iniciar nuevos lenguajes (y otras ramificaciones, pero esa es la que está relacionada aquí).
Esto se puede lograr usando el comando cgo.
En esencia 'Si la importación de "C" está precedida inmediatamente por un comentario, ese comentario, llamado preámbulo, se usa como encabezado al compilar las partes C del paquete. Por ejemplo: '
fuente: https://golang.org/cmd/cgo/
// #include <stdio.h>
// #include <errno.h>
import "C"