Como usar o C ++ no Go

173

No novo idioma Go , como chamo de código C ++? Em outras palavras, como posso agrupar minhas classes C ++ e usá-las no Go?

Frank
fonte
1
Na palestra tecnologia SWIG foi muito brevemente mencionado, algo como "..until temos a gole feito .."
StackedCrooked
1
@ Matt: Provavelmente ele quer usar uma biblioteca C ++ existente sem precisar portá-la para C ou Go. Eu queria a mesma coisa.
Graeme Perrow
Não consigo pensar em uma única biblioteca decente disponível para C ++ e não para C. Eu adoraria saber o que você tem em mente.
Matt Joiner
13
@ Matt: Um exemplo é a biblioteca Boost, e existem milhares de outras bibliotecas C ++ úteis. Mas talvez eu só estou alimentando um troll aqui ...
Frank
@ Matt: no meu caso, eu queria fazer uma interface Go para nossa biblioteca cliente existente, mas a biblioteca é principalmente C ++. Portá-lo para C ou Go simplesmente não é uma opção.
Graeme Perrow

Respostas:

154

Atualizar: Consegui vincular uma pequena classe C ++ de teste ao Go

Se você agrupar seu código C ++ com uma interface C, poderá chamar sua biblioteca com cgo (veja o exemplo de gmp em $GOROOT/misc/cgo/gmp ).

Não tenho certeza se a idéia de uma classe em C ++ é realmente expressável no Go, pois ela não tem herança.

Aqui está um exemplo:

Eu tenho uma classe 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 eu quero usar no Go. Vou usar a interface C

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

(Eu uso uma void*estrutura C em vez de C para que o compilador saiba o tamanho de Foo)

A implementação é:

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

com tudo isso feito, o arquivo Go é:

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

O makefile que eu usei para compilar isso foi:

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

Tente testá-lo com:

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

Você precisará instalar a biblioteca compartilhada com make install e, em seguida, execute make test. A produção esperada é:

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
fonte
1
Tenha cuidado com isso, não tenho idéia do que pode acontecer com a memória se você a enviar entre os dois idiomas.
Scott Wales
11
Eu tenho que dizer, este exemplo me lembra porque eu quero escrever Go puro. Veja como o lado C ++ é maior e mais feio. Ick.
Jeff Allen
@ScottWales alguma chance de você colocar isso em um repositório no Github ou algo assim? Eu adoraria ver um exemplo de trabalho
netpoetica
7
@Arne: Você não diminuiu a resposta porque não é a melhor. Você recusou uma resposta porque não é útil. Enquanto isso funcionar, essa resposta ainda será útil, mesmo que haja soluções melhores.
Graeme Perrow
Boas notícias, o Go irá compilar o cpp agora, para que o makefile não seja mais necessário. Os invólucros inseguros.Pointer não funcionaram para mim. Uma pequena modificação compilada para mim: play.golang.org/p/hKuKV51cRp go test deve funcionar sem o makefile
Drew
47

Parece que atualmente o SWIG é a melhor solução para isso:

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

Ele suporta herança e até permite subclassificar a classe C ++ com a estrutura Go, portanto, quando métodos substituídos são chamados no código C ++, o código Go é acionado.

A seção sobre perguntas frequentes sobre C ++ no Go é atualizada e agora menciona SWIG e não diz mais " porque o Go é coletado no lixo, não é aconselhável fazê-lo, pelo menos ingênuo ".

Kolen
fonte
9
Eu gostaria que houvesse uma maneira de aumentar isso. As outras respostas estão desatualizadas. Além disso SWIG tem versionadas-se swig.org/Doc3.0/Go.html
Dragonx
34

Ainda não é possível pelo que li nas Perguntas frequentes :

Os programas Go são vinculados aos programas C / C ++?

Existem duas implementações do compilador Go, gc (o programa 6g e amigos) e gccgo. O Gc usa uma convenção de chamada e vinculador diferentes e, portanto, só pode ser vinculado a programas C usando a mesma convenção. Existe um compilador C, mas nenhum compilador C ++. O Gccgo é um front-end do GCC que pode, com cuidado, ser vinculado a programas C ou C ++ compilados pelo GCC.

O programa cgo fornece o mecanismo para uma "interface de função externa" para permitir a chamada segura de bibliotecas C a partir do código Go. O SWIG estende esse recurso às bibliotecas C ++.

Dirk Eddelbuettel
fonte
13

Eu criei o exemplo a seguir com base na resposta de Scott Wales . Eu testei no macOS High Sierra 10.13.3 goversão em execuçãogo1.10 darwin/amd64 .

(1) Código para library.hppa API C ++ que pretendemos chamar.

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

(2) Código para library.cppa implementação 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 library-bridge.hda ponte necessário para expor umaC API implementada C++para que ela gopossa ser usada.

#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.cppa implementação da ponte.

#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 o programa go chamando a 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())
}

Usando o seguinte 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

Eu posso executar o programa de exemplo da seguinte maneira:

$ 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

Os comentários acima import "C"no goprograma NÃO SÃO OPCIONAIS . Você deve colocá-los exatamente como mostrado para que cgosaiba qual cabeçalho e biblioteca carregar, neste caso:

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

Link para o repositório GitHub com o exemplo completo .

Escualo
fonte
Obrigado - isso foi muito útil!
Robert Cowham
3

Fala-se de interoperabilidade entre C e Go ao usar o compilador gcc Go, gccgo. Entretanto, existem limitações para a interoperabilidade e o conjunto de recursos implementados do Go ao usar o gccgo (por exemplo, goroutines limitadas, sem coleta de lixo).

fbrereto
fonte
2
1. Crie um idioma sem recursos para gerenciamento manual de memória, 2. Remova a coleta de lixo? Eu sou o único coçando a cabeça com isso?
György Andrasek 11/11/2009
2

Você está andando em território desconhecido aqui. Aqui está o exemplo do Go para chamar o código C, talvez você possa fazer algo assim depois de ler as convenções de manipulação e chamada de nomes C ++ e várias tentativas e erros.

Se você ainda quiser experimentá-lo, boa sorte.

György Andrasek
fonte
1

O problema aqui é que uma implementação compatível não precisa colocar suas classes em um arquivo .cpp de compilação. Se o compilador puder otimizar a existência de uma classe, desde que o programa se comporte da mesma maneira sem ela, ele poderá ser omitido do executável de saída.

C tem uma interface binária padronizada. Portanto, você poderá saber que suas funções são exportadas. Mas o C ++ não tem esse padrão por trás disso.

Billy ONeal
fonte
1

Pode ser necessário adicionar -lc++ao LDFlagsGolang / CGo para reconhecer a necessidade da biblioteca padrão.

ggobieski
fonte
0

Engraçado quantas edições mais amplas este anúncio foi divulgado. Dan Lyke teve uma discussão muito divertida e atenciosa em seu site, Flutterby, sobre o desenvolvimento de Interprocess Standards como uma maneira de inicializar novas linguagens (e outras ramificações, mas essa é a mais pertinente aqui).

Don Wakefield
fonte
0

Isso pode ser alcançado usando o comando cgo.

Essencialmente 'Se a importação de "C" for imediatamente precedida por um comentário, esse comentário, chamado de preâmbulo, será usado como cabeçalho ao compilar as partes C do pacote. Por exemplo: '
source: https://golang.org/cmd/cgo/

// #include <stdio.h>
// #include <errno.h>
import "C"
Devendra Mukharaiya
fonte