Jak używać C ++ w Go


173

W nowym języku Go , jak wywołać kod C ++? Innymi słowy, jak mogę opakować moje klasy C ++ i używać ich w Go?


1
W rozmowie technicznej SWIG był bardzo krótko wspomniany, coś w stylu „.. dopóki nie skończymy wypijać ..”
— StackedCrooked

1
@Matt: Prawdopodobnie chce użyć istniejącej biblioteki C ++ bez konieczności przenoszenia jej do C lub Go. Chciałem tego samego.
— Graeme Perrow

Nie przychodzi mi do głowy żadna przyzwoita biblioteka dostępna dla C ++, a nie dla C. Chciałbym wiedzieć, o czym myślisz.
— Matt Joiner

13
@Matt: Jednym z przykładów jest biblioteka Boost, a istnieją tysiące innych przydatnych bibliotek C ++. Ale może po prostu karmię tutaj trolla ...
— Frank,

@Matt: w moim przypadku chciałem stworzyć interfejs Go do naszej istniejącej biblioteki klienta, ale biblioteka to głównie C ++. Przeniesienie go do C lub Go po prostu nie wchodzi w grę.
— Graeme Perrow

Odpowiedzi:


154

Aktualizacja: Udało mi się połączyć małą testową klasę C ++ z Go

Jeśli otoczysz kod C ++ interfejsem C, powinieneś móc wywołać swoją bibliotekę za pomocą cgo (zobacz przykład gmp w $GOROOT/misc/cgo/gmp).

Nie jestem pewien, czy idea klasy w C ++ jest naprawdę wyrażalna w Go, ponieważ nie ma dziedziczenia.

Oto przykład:

Mam klasę C ++ zdefiniowaną jako:

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

którego chcę używać w Go. Użyję interfejsu C.

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

(Używam struktury void*zamiast C, więc kompilator zna rozmiar Foo)

Wdrożenie to:

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

po wykonaniu tego wszystkiego plik Go to:

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

Plik makefile, którego użyłem do skompilowania tego, to:

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

Spróbuj to przetestować z:

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

Musisz zainstalować udostępnioną bibliotekę za pomocą polecenia make install, a następnie uruchomić make test. Oczekiwany wynik to:

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

1
Uważaj na to, nie mam pojęcia, co może się stać z pamięcią, jeśli prześlesz ją między dwoma językami.
— Scott Wales

11
Muszę powiedzieć, że ten przykład przypomina mi, dlaczego chcę pisać w czystym Go. Zobacz, o ile większa i brzydsza jest strona C ++. Ick.
— Jeff Allen,

@ScottWales jest jakaś szansa, że ​​umieściłeś to w repozytorium na Github czy coś? Bardzo chciałbym zobaczyć działający przykład
— netpoetica

7
@Arne: Nie odrzucasz odpowiedzi, ponieważ nie jest najlepsza. Negujesz odpowiedź, ponieważ nie jest pomocna. Dopóki działa, ta odpowiedź jest nadal pomocna, nawet jeśli istnieją lepsze rozwiązania.
— Graeme Perrow

Dobra wiadomość, Go skompiluje teraz cpp, więc plik makefile nie jest już wymagany. Opakowania unsafe.Pointer nie działały dla mnie. Skompilowana dla mnie niewielka modyfikacja: play.golang.org/p/hKuKV51cRp go test powinno działać bez pliku makefile
— Drew

47

Wygląda na to, że obecnie SWIG jest najlepszym rozwiązaniem na to:

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

Obsługuje dziedziczenie, a nawet pozwala na podklasę klasy C ++ za pomocą struktury Go, więc gdy nadpisane metody są wywoływane w kodzie C ++, kod Go jest uruchamiany.

Sekcja o C ++ w Go FAQ została zaktualizowana i teraz wspomina o SWIG i nie mówi już „ ponieważ Go jest zbierające śmieci, byłoby to nierozsądne, przynajmniej naiwnie ”.


9
Żałuję, że nie ma sposobu, żeby to podnieść. Pozostałe odpowiedzi są nieaktualne. Plus SWIG zaktualizował swig.org/Doc3.0/Go.html
— dragonx.

34

Nie możesz jeszcze z tego, co przeczytałem w FAQ :

Czy programy Go łączą się z programami C / C ++?

Istnieją dwie implementacje kompilatora Go, gc (program 6g i przyjaciele) i gccgo. Gc używa innej konwencji wywoływania i konsolidatora i dlatego może być łączony tylko z programami w C używającymi tej samej konwencji. Jest taki kompilator C, ale nie ma kompilatora C ++. Gccgo to front-end GCC, który można ostrożnie łączyć z programami C lub C ++ skompilowanymi przez GCC.

Program cgo zapewnia mechanizm „interfejsu funkcji obcych”, który umożliwia bezpieczne wywoływanie bibliotek C z kodu Go. SWIG rozszerza tę możliwość na biblioteki C ++.



13

Poniższy przykład stworzyłem na podstawie odpowiedzi Scotta Walesa . Przetestowałem to w uruchomionej gowersji macOS High Sierra 10.13.3 go1.10 darwin/amd64.

(1) Kod library.hppinterfejsu API C ++, który chcemy wywołać.

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

(2) Kod library.cpp, implementacja 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) Kod library-bridge.hmostu potrzebny do udostępnienia Cinterfejsu API zaimplementowanego w programie C++, aby gomóc go używać.

#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) Kod library-bridge.cppimplementacji mostu.

#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) Na koniec library.goprogram go wywołujący 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())
}

Używając następującego pliku 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

Przykładowy program mogę uruchomić w następujący sposób:

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

Ważny

Komentarze powyżej import "C"w goprogramie NIE SĄ OPCJONALNE . Musisz umieścić je dokładnie tak, jak pokazano, aby cgowiedzieć, który nagłówek i bibliotekę załadować, w tym przypadku:

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

Link do repozytorium GitHub z pełnym przykładem .


Dziękuję - to było bardzo pomocne!
— Robert Cowham


3

Podczas korzystania z kompilatora gcc Go, gccgo, mówi się o interoperacyjności między C i Go . Istnieją jednak ograniczenia zarówno co do współdziałania, jak i zaimplementowanego zestawu funkcji Go podczas korzystania z gccgo (np. Ograniczone gorutyny, brak czyszczenia pamięci).


2
1. Stworzyć język bez możliwości ręcznego zarządzania pamięcią, 2. Usunąć czyszczenie pamięci? Czy tylko ja drapie się w tym w głowę?
— György Andrasek

2

Kroczysz tu po niezbadanym terenie. Oto przykład Go do wywoływania kodu w C, być może możesz zrobić coś takiego po przeczytaniu o zniekształcaniu nazw w C ++ i konwencjach wywoływania oraz wielu próbach i błędach.

Jeśli nadal masz ochotę spróbować, powodzenia.


1

Problem polega na tym, że zgodna implementacja nie musi umieszczać twoich klas w skompilowanym pliku .cpp. Jeśli kompilator może zoptymalizować istnienie klasy, o ile program zachowuje się bez niej w ten sam sposób, można go pominąć w wyjściowym pliku wykonywalnym.

C ma ustandaryzowany interfejs binarny. Dzięki temu będziesz mógł wiedzieć, że Twoje funkcje są eksportowane. Ale C ++ nie ma za sobą takiego standardu.


1

Może być konieczne dodanie -lc++do LDFlagsfor Golang / CGo, aby rozpoznać potrzebę biblioteki standardowej.


0

Zabawne, jak wiele szerszych kwestii pogłębiło to ogłoszenie. Dan Lyke przeprowadził bardzo zabawną i przemyślaną dyskusję na swojej stronie internetowej Flutterby na temat rozwijania standardów międzyprocesowych jako sposobu na ładowanie nowych języków (i innych konsekwencji, ale to jest tutaj istotne).


0

Można to osiągnąć za pomocą polecenia cgo.

W istocie „Jeśli import„ C ”jest bezpośrednio poprzedzony komentarzem, komentarz ten, zwany preambułą, jest używany jako nagłówek podczas kompilowania części C pakietu. Na przykład: „
źródło: https://golang.org/cmd/cgo/

// #include <stdio.h>
// #include <errno.h>
import "C"
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.