Як використовувати C ++ у Go


173

Як я можу викликати код C ++ у новій мові Go ? Іншими словами, як я можу обгортати свої C ++ класи та використовувати їх у програмі Go?


1
У технічній розмові SWIG було дуже коротко згадувано, щось на кшталт "... поки ми не зробимо
свинг

1
@Matt: Ймовірно, він хоче використовувати існуючу бібліотеку C ++, не потребуючи порту на C або Go. Я хотів те саме.
Graeme Perrow

Я не можу придумати єдину гідну бібліотеку, доступну для C ++, а не для C. Я хотів би знати, що ви маєте на увазі.
Метт Столяр

13
@Matt: Одним із прикладів є бібліотека Boost, і є тисячі інших корисних бібліотек C ++. Але, можливо, я просто годую тут троля ...
Френк

@Matt: у моєму випадку я хотів зробити інтерфейс Go для нашої існуючої клієнтської бібліотеки, але в основному бібліотека C ++. Перенесення його на C або Go просто не є варіантом.
Graeme Perrow

Відповіді:


154

Оновлення: мені вдалося зв'язати невеликий тестовий клас C ++ з Go

Якщо ви з'єднаєте код C ++ за допомогою інтерфейсу С, ви повинні мати можливість викликати свою бібліотеку cgo (див. Приклад gmp в $GOROOT/misc/cgo/gmp).

Я не впевнений, чи ідея класу на C ++ справді виразна в Go, оскільки вона не має спадщини.

Ось приклад:

У мене клас C ++ визначений як:

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

який я хочу використовувати в Go. Я буду використовувати інтерфейс С

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

(Я використовую void*замість C структури, щоб компілятор знав розмір Foo)

Реалізація:

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

З усім, що зроблено, файл 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));
}

Makefile, який я використовував для складання цього, був:

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

Спробуйте протестувати його за допомогою:

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

Вам потрібно буде встановити спільну бібліотеку з make install, а потім запустіть make test. Очікуваний вихід:

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
Будьте обережні з цим, я поняття не маю, що може статися з пам'яттю, якщо ви надішлете її між двома мовами.
Скотт Уельс

11
Треба сказати, цей приклад нагадує мені, чому я хочу писати чисто Go. Подивіться, наскільки більша і гірша сторона C ++. Ick.
Джефф Аллен

@ScottWales є будь-який шанс, що ви могли б поставити це у репортажі на Github чи що-небудь ще? Я хотів би побачити робочий приклад
netpoetica

7
@Arne: Ви не відповідаєте за відповідь, оскільки це не найкраще. Ви відповіли, що це не корисно. Поки це працює, ця відповідь все ще корисна, навіть якщо є кращі рішення.
Graeme Perrow

Хороша новина, Go зараз буде компілювати cpp, так що makefile більше не потрібен. Небезпечні. Обгортки покажчика для мене не працювали. Невелика модифікація, складена для мене: play.golang.org/p/hKuKV51cRp go test повинна працювати без makefile
Дрю

47

Здається, що на даний момент SWIG є найкращим рішенням для цього:

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

Він підтримує успадкування і навіть дозволяє підклас класу C ++ з Go структура, тому, коли виклику методів, що перекриваються, викликаються в коді C ++, код Go запускається.

Розділ про C ++ в Go FAQ часто оновлюється, і зараз він згадує SWIG, і більше не говорить, « оскільки Go зібраний сміття, це буде нерозумно, принаймні наївно ».


9
Мені б хотілося, щоб був спосіб підбити це. Інші відповіді застаріли. Плюс SWIG переосмислив swig.org/Doc3.0/Go.html
dragonx

34

Ви ще не можете зрозуміти, що я читав у FAQ :

Чи пов'язують програми Go з програмами C / C ++?

Є дві реалізації компілятора Go, gc (програма 6g та друзі) та gccgo. Gc використовує іншу конвенцію викликів і лінкер, і тому може бути пов'язаний тільки з програмами C, використовуючи ту саму конвенцію. Є такий компілятор C, але немає компілятора C ++. Gccgo - це передумови GCC, які з обережністю можуть бути пов'язані з компільованими GCC програмами C або C ++.

Програма cgo забезпечує механізм "інтерфейсу іноземних функцій", який дозволяє безпечно викликати бібліотеки C з коду Go. SWIG розширює цю можливість на бібліотеках C ++.



13

Я створив наступний приклад на основі відповіді Скотта Уельса . Я тестував його у goверсії macOS High Sierra 10.13.3go1.10 darwin/amd64 .

(1) Код для library.hppAPI C ++, який ми прагнемо викликати.

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

(2) Код для library.cppреалізації 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) Код для library-bridge.hмосту, необхідний для викриття CAPI, реалізованого C++так, щоб він goміг його використовувати.

#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) Кодекс для library-bridge.cppреалізації мосту.

#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) Нарешті, library.goпрограма go, що викликає 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())
}

Використовуючи наступний 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

Я можу запустити приклад програми так:

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

Важливо

Зауваження, наведені вище import "C"в goпрограмі, НЕ ОБОВ'ЯЗКОВІ . Ви повинні розмістити їх точно так, як показано, щоб було cgoвідомо, який заголовок та бібліотеку завантажувати, у цьому випадку:

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

Посилання на GitHub repo з повним прикладом .


Дякую - це було дуже корисно!
Роберт Коуем

11

Здається, це одне з ранніх питань про Голанг. І в той же час відповідає, що ніколи не оновлюється. Протягом цих трьох-чотирьох років надто багато нових бібліотек та публікацій в блозі не було. Нижче наведено декілька посилань, що я вважав корисним.

SWIG та Go

Виклик коду C ++ з GoIG за допомогою SWIG

Якщо порівнювати мови, C ++ та Go

GoForCPPProgrammers


3

Ми говоримо про сумісність між C і Go при використанні компілятора gcc Go, gccgo. Однак існують обмеження як для сумісності, так і для реалізованого набору функцій Go при використанні gccgo (наприклад, обмежені умови, відсутність збирання сміття).


2
1. Складіть мову, не маючи засобів для ручного управління пам’яттю, 2. Видаліть сміття? Чи я єдиний при цьому чухає голову?
Дьорджі Андрасек


1

Проблема тут полягає в тому, що сумісна реалізація не потребує класів у компільований .cpp-файл. Якщо компілятор може оптимізувати існування класу до тих пір, поки програма поводиться однаково без нього, то його можна випустити з вихідного виконуваного файлу.

C має стандартизований бінарний інтерфейс. Тому ви зможете знати, що ваші функції експортуються. Але C ++ не має такого стандарту.


1

Вам може знадобитися додати -lc++до програми LDFlagsGolang / CGo, щоб визнати потребу в стандартній бібліотеці.


0

Смішно, наскільки більш широкі випуски затримали це оголошення. Ден Лайк провів дуже цікаву та продуману дискусію на своєму веб-сайті, Flutterby, щодо розробки Міжпроцесорних стандартів як способу завантаження нових мов (та інших розгалужень, але саме тут є німецька).


0

Цього можна досягти, використовуючи команду cgo.

По суті, "Якщо імпорту" C "негайно передує коментар, цей коментар, який називається преамбулою, використовується як заголовок при компілюванні частин C пакета. Наприклад: '
джерело: https://golang.org/cmd/cgo/

// #include <stdio.h>
// #include <errno.h>
import "C"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.