Cómo usar C ++ en Go

173

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?

Franco
fuente
1
En la charla tecnológica, SWIG se mencionó brevemente, algo así como "... hasta que terminemos el trago ..."
StackedCrooked
1
@Matt: Probablemente quiera usar una biblioteca C ++ existente sin tener que portarla a C o Go. Yo quería lo mismo.
Graeme Perrow
No puedo pensar en una sola biblioteca decente disponible para C ++ y no para C. Me encantaría saber lo que tienes en mente.
Matt Joiner
13
@Matt: Un ejemplo es la biblioteca Boost, y hay miles de otras bibliotecas C ++ útiles. Pero tal vez solo estoy alimentando a un troll aquí ...
Frank
@Matt: en mi caso, quería crear una interfaz Go para nuestra biblioteca cliente existente, pero la biblioteca es principalmente C ++. Portarlo a C o Go simplemente no es una opción.
Graeme Perrow

Respuestas:

154

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
Scott Wales
fuente
1
Tenga cuidado con esto, no tengo idea de lo que podría pasarle a la memoria si la envía entre los dos idiomas.
Scott Wales
11
Debo decir que este ejemplo me recuerda por qué quiero escribir Go puro. Mira cuánto más grande y feo es el lado de C ++. Ick
Jeff Allen
@ScottWales, ¿hay alguna posibilidad de que haya puesto esto en un repositorio en Github o algo así? Me encantaría ver un ejemplo de trabajo
netpoetica
77
@Arne: No rechazas una respuesta porque no es la mejor. Vota una respuesta en contra porque no es útil. Mientras funcione, esta respuesta sigue siendo útil, incluso si hay mejores soluciones.
Graeme Perrow
Buenas noticias, Go compilará cpp ahora para que el archivo MAKE ya no sea necesario. Los envoltorios inseguros de puntero no me funcionaron. Una ligera modificación compilada para mí: play.golang.org/p/hKuKV51cRp go test debería funcionar sin el archivo MAKE
Drew
47

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 ".

kolen
fuente
9
Desearía que hubiera una manera de aumentar esto. Las otras respuestas están desactualizadas. Además SWIG ha versionado swig.org/Doc3.0/Go.html
dragonx
34

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 ++.

Dirk Eddelbuettel
fuente
13

He creado el siguiente ejemplo basado en la respuesta de Scott Wales . Lo probé en la goversión de macOS High Sierra 10.13.3 go1.10 darwin/amd64.

(1) Código para library.hppla 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.cppla 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 CAPI implementada C++para que gopueda 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.cppla 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.goel 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 goprograma NO SON OPCIONALES . Debe colocarlos exactamente como se muestra para que cgosepa qué encabezado y biblioteca cargar, en este caso:

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

Enlace al repositorio de GitHub con el ejemplo completo .

Escualo
fuente
¡Gracias, esto fue de gran ayuda!
Robert Cowham
3

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

fbrereto
fuente
2
1. Cree un lenguaje sin facilidades para la gestión manual de la memoria. 2. ¿Elimine la recolección de basura? ¿Soy el único que se rasca la cabeza ante esto?
György Andrasek
2

Estás caminando en territorio desconocido aquí. Aquí está el ejemplo de Go para llamar al código C, tal vez pueda hacer algo así después de leer sobre la convención de nombres y llamadas de C ++ , y muchas pruebas y errores.

Si todavía tienes ganas de probarlo, buena suerte.

György Andrasek
fuente
1

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.

Billy ONeal
fuente
1

Es posible que deba agregar -lc++a LDFlagsGolang / CGo para reconocer la necesidad de la biblioteca estándar.

ggobieski
fuente
0

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í).

Don Wakefield
fuente
0

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"
Devendra Mukharaiya
fuente