如何在Go中使用C ++


173

在新的Go语言中,如何调用C ++代码?换句话说,如何包装我的C ++类并在Go中使用它们?


1
在技​​术演讲中,非常简单地提到了SWIG,例如“ ..直到我们完成
一遍

1
@Matt:他可能想使用现有的C ++库,而不必将其移植到C或Go。我想要同样的东西。
Graeme Perrow 2010年

我想不出一个可用于C ++而不是C的像样的库。我很想知道您的想法。
马特·乔纳

13
@Matt:一个例子是Boost库,还有成千上万个其他有用的C ++库。但也许我只是在这里喂一个巨魔……
弗兰克(Frank)2010年

@Matt:就我而言,我想为我们现有的客户端库建立一个Go接口,但是该库主要是C ++。将其移植到C或Go根本不是一种选择。
Graeme Perrow

Answers:


154

更新:我已经成功地将一个小型测试C ++类与Go链接在一起

如果您使用C接口包装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中使用。我将使用C接口

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

(我使用a 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 ++方面更大更丑陋。ck
杰夫·艾伦

@ScottWales您是否有可能将其放入Github的回购协议中?我希望看到一个工作示例
netpoetica

7
@Arne:您不会对答案不满意,因为这不是最好的答案。您对答案不满意,因为它没有帮助。只要可行,即使有更好的解决方案,此答案仍然有用。
2014年

好消息,Go将立即编译cpp,因此不再需要makefile。不安全的指针包装器对我不起作用。为我编译的一个小修改:play.golang.org/p/hKuKV51cRp go test应该在没有makefile的情况下可以工作
Drew

47

似乎目前SWIG是最佳解决方案:

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

它支持继承,甚至允许使用Go struct子类化C ++类,因此,当在C ++代码中调用重写的方法时,将触发Go代码。

Go常见问题解答中有关C ++的部分进行了更新,现在提到了SWIG,不再说“ 因为Go已被垃圾回收,这样做是不明智的,至少是天真的。”


9
我希望有办法提高这一点。其他答案已经过时。另外,SWIG版本已更新为 swig.org/Doc3.0/Go.html
dragonx '16

34

根据我在FAQ中阅读的内容您还不太了解:

Go程序是否与C / C ++程序链接?

Go编译器有两种实现,即gc(6g程序和朋友)和gccgo。Gc使用不同的调用约定和链接器,因此只能使用相同的约定与C程序链接。有这样的C编译器,但没有C ++编译器。Gccgo是一个GCC前端,可以小心地与GCC编译的C或C ++程序链接。

cgo程序提供了“外部函数接口”的机制,以允许从Go代码安全地调用C库。SWIG将此功能扩展到C ++库。



13

我基于Scott Wales的答案创建了以下示例。我已经在运行macOS High Sierra 10.13.3的go版本中对其进行了测试go1.10 darwin/amd64

(1)的代码library.hpp,我们打算调用C ++ API。

#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桥梁的代码,需要公开C实现的API,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程序,用于调用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())
}

使用以下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存储库


谢谢-这非常有帮助!
罗伯特·考汉姆


3

关于使用gcc Go编译器gccgo时C和Go之间的互操作性。但是,使用gccgo时,Go的互操作性和已实现的功能集都有限制(例如,有限的goroutine,无垃圾收集)。


2
1.编写一种不具备手动内存管理功能的语言,2.删除垃圾回收?我是唯一一个为此挠​​头的人吗?
捷尔吉Andrasek


1

这里的问题是,兼容的实现不需要将您的类放入编译的.cpp文件中。如果编译器可以优化类的存在,只要程序在没有类的情况下表现相同,则可以从输出可执行文件中将其省略。

C具有标准化的二进制接口。因此,您将能够知道函数已导出。但是C ++背后没有这样的标准。


1

您可能需要添加-lc++LDFlagsfor Golang / CGo中才能认识到对标准库的需求。


0

有趣的是,该公告已经解决了许多更广泛的问题。Dan Lyke在他的网站Flutterby上进行了非常有趣且深思熟虑的讨论,内容涉及开发进程间标准,作为引导新语言(以及其他分支的一种方式,但这是与本文密切相关的一种)。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.