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.