Cara menggunakan C ++ in Go

173

Dalam bahasa Go yang baru , bagaimana cara saya memanggil kode C ++? Dengan kata lain, bagaimana saya bisa membungkus kelas C ++ saya dan menggunakannya di Go?

jujur
sumber
1
Dalam pembicaraan teknologi, SWIG sangat singkat disebutkan, sesuatu seperti "..sampai kita menyelesaikan
swig
1
@Matt: Kemungkinan dia ingin menggunakan pustaka C ++ yang sudah ada tanpa harus porting ke C atau Go. Saya menginginkan hal yang sama.
Graeme Perrow
Saya tidak bisa memikirkan satu perpustakaan yang layak tersedia untuk C ++ dan bukan untuk C. Saya ingin tahu apa yang ada dalam pikiran Anda.
Matt Joiner
13
@ Matt: Salah satu contohnya adalah perpustakaan Boost, dan ada ribuan perpustakaan C ++ lainnya yang bermanfaat. Tapi mungkin aku hanya memberi makan troll di sini ...
Frank
@ Matt: dalam kasus saya, saya ingin membuat antarmuka Go ke pustaka klien kami yang sudah ada tetapi pustaka utamanya adalah C ++. Porting ke C atau Go bukan pilihan.
Graeme Perrow

Jawaban:

154

Pembaruan: Saya telah berhasil menghubungkan tes C ++ kelas kecil dengan Go

Jika Anda membungkus Anda kode C ++ dengan antarmuka C Anda harus dapat memanggil perpustakaan Anda dengan cgo (lihat contoh gmp in $GOROOT/misc/cgo/gmp).

Saya tidak yakin apakah gagasan tentang kelas di C ++ benar-benar dapat diekspresikan di Go, karena tidak memiliki warisan.

Ini sebuah contoh:

Saya memiliki kelas C ++ yang didefinisikan sebagai:

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

yang ingin saya gunakan di Go. Saya akan menggunakan antarmuka C.

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

(Saya menggunakan void*bukan C struct sehingga kompiler tahu ukuran Foo)

Implementasinya adalah:

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

dengan semua yang dilakukan, file Go adalah:

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

Makefile yang saya gunakan untuk mengkompilasi ini adalah:

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

Coba mengujinya dengan:

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

Anda harus menginstal pustaka bersama dengan make install, lalu jalankan make test. Output yang diharapkan adalah:

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
sumber
1
Hati-hati dengan ini, saya tidak tahu apa yang mungkin terjadi pada memori jika Anda mengirimnya di antara dua bahasa.
Scott Wales
11
Harus saya katakan, contoh ini mengingatkan saya mengapa saya ingin menulis Go murni. Lihat seberapa besar dan jelek sisi C ++. Ya
Jeff Allen
@ScottWales kemungkinan Anda mungkin telah menempatkan ini di repo di Github atau apa pun? Saya akan senang melihat contoh kerja
netpoetica
7
@ Andre: Anda tidak menurunkan pilihan jawaban karena itu bukan yang terbaik. Anda menurunkan jawaban karena itu tidak membantu. Selama ini berfungsi, jawaban ini masih membantu bahkan jika ada solusi yang lebih baik.
Graeme Perrow
Berita bagus, Go akan mengkompilasi cpp sekarang sehingga makefile tidak lagi diperlukan. Pembungkus unsafe.Pointer tidak berfungsi untuk saya. Sedikit modifikasi yang dikompilasi untuk saya: play.golang.org/p/hKuKV51cRp go test harus bekerja tanpa makefile
Drew
47

Tampaknya saat ini SWIG adalah solusi terbaik untuk ini:

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

Ini mendukung warisan dan bahkan memungkinkan untuk mensubkelas kelas C ++ dengan Go struct sehingga ketika metode yang ditimpa dipanggil dalam kode C ++, kode Go dipecat.

Bagian tentang C ++ di Go FAQ diperbarui dan sekarang menyebutkan SWIG dan tidak lagi mengatakan " karena Go adalah sampah, tidak bijaksana untuk melakukannya, setidaknya secara naif ".

kolen
sumber
9
Saya berharap ada cara untuk mengatasi ini. Jawaban lainnya sudah usang. Plus SWIG telah mengversi swig.org/Doc3.0/Go.html
dragonx
34

Anda belum bisa membaca apa yang saya baca di FAQ :

Apakah program Go terhubung dengan program C / C ++?

Ada dua implementasi kompilator Go, gc (program 6g dan teman-teman) dan gccgo. Gc menggunakan konvensi panggilan dan tautan yang berbeda dan karenanya hanya dapat dihubungkan dengan program C yang menggunakan konvensi yang sama. Ada kompiler C seperti itu tetapi tidak ada kompiler C ++. Gccgo adalah front-end GCC yang dengan hati-hati dapat dihubungkan dengan program C atau C ++ yang dikompilasi GCC.

Program cgo menyediakan mekanisme untuk "antarmuka fungsi asing" untuk memungkinkan panggilan yang aman dari perpustakaan C dari kode Go. SWIG memperluas kemampuan ini ke pustaka C ++.

Dirk Eddelbuettel
sumber
13

Saya telah membuat contoh berikut berdasarkan jawaban Scott Wales . Saya sudah mengujinya di macOS High Sierra 10.13.3 goversi yang sedang berjalango1.10 darwin/amd64 .

(1) Kode untuk library.hpp, API C ++ yang ingin kami panggil.

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

(2) Kode untuk library.cpp, implementasi 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) Kode untuk library-bridge.hjembatan diperlukan untuk mengekspos CAPI yang diimplementasikan C++sehingga godapat menggunakannya.

#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) Kode untuk library-bridge.cpp, implementasi jembatan.

#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) Akhirnya,, library.goprogram go memanggil C ++ API.

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

Gunakan Makefile berikut

liblibrary.so: library.cpp library-bridge.cpp
    clang++ -o liblibrary.so library.cpp library-bridge.cpp \
    -std=c++17 -O3 -Wall -Wextra -fPIC -shared

Saya dapat menjalankan contoh program sebagai berikut:

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

Penting

Komentar import "C"di atas dalam goprogram BUKAN OPSIONAL . Anda harus meletakkannya persis seperti yang ditunjukkan sehingga cgotahu header dan pustaka mana yang akan dimuat, dalam hal ini:

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

Tautan ke repo GitHub dengan contoh lengkap .

Escualo
sumber
Terima kasih - ini sangat membantu!
Robert Cowham
3

Ada pembicaraan tentang interoperabilitas antara C dan Go saat menggunakan kompiler gcc Go, gccgo. Ada beberapa batasan untuk interoperabilitas dan set fitur Go yang diterapkan saat menggunakan gccgo, namun (misalnya, goroutine terbatas, tidak ada pengumpulan sampah).

untuk melakukannya
sumber
2
1. Membuat bahasa tanpa fasilitas untuk manajemen memori manual, 2. Menghapus pengumpulan sampah? Apakah aku satu-satunya yang menggaruk kepalaku?
György Andrasek
2

Anda berjalan di wilayah yang belum dipetakan di sini. Berikut ini adalah contoh Go untuk memanggil kode C, mungkin Anda dapat melakukan sesuatu seperti itu setelah membaca tentang C ++ nama mangling dan konvensi pemanggilan, dan banyak trial and error.

Jika Anda masih merasa ingin mencobanya, semoga berhasil.

György Andrasek
sumber
1

Masalahnya di sini adalah implementasi yang sesuai tidak perlu menempatkan kelas Anda dalam file .cpp kompilasi. Jika kompiler dapat mengoptimalkan keberadaan suatu kelas, selama program berperilaku dengan cara yang sama tanpanya, maka ia dapat dihilangkan dari output yang dapat dieksekusi.

C memiliki antarmuka biner standar. Karenanya, Anda dapat mengetahui bahwa fungsi Anda diekspor. Tetapi C ++ tidak memiliki standar seperti itu.

Billy ONeal
sumber
1

Anda mungkin perlu menambahkan -lc++ke LDFlagsuntuk Golang / CGo untuk mengenali kebutuhan untuk perpustakaan standar.

ggobieski
sumber
0

Lucu berapa banyak masalah yang lebih luas yang pengumuman ini dikeruk. Dan Lyke melakukan diskusi yang sangat menghibur dan penuh pertimbangan di situs webnya, Flutterby, tentang mengembangkan Standar Prosesproses sebagai cara bootstrap bahasa baru (dan konsekuensi lainnya, tapi itulah yang berhubungan erat di sini).

Don Wakefield
sumber
0

Ini dapat dicapai dengan menggunakan perintah cgo.

Intinya 'Jika impor "C" segera didahului dengan komentar, komentar itu, yang disebut preamble, digunakan sebagai header ketika menyusun bagian-bagian C dari paket. Misalnya: '
sumber: https://golang.org/cmd/cgo/

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