【问题标题】:How to use C++ in Go如何在 Go 中使用 C++
【发布时间】:2010-12-15 08:29:59
【问题描述】:

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

【问题讨论】:

  • 在技术谈话中非常简短地提到了 SWIG,例如“..直到我们完成 swig..”
  • @Matt:他可能想使用现有的 C++ 库,而不必将其移植到 C 或 Go。我想要同样的东西。
  • 我想不出有一个像样的库可用于 C++ 而不是 C。我很想知道您的想法。
  • @Matt:一个例子是 Boost 库,还有数以千计的其他有用的 C++ 库。但也许我只是在这里喂一个巨魔......
  • @Matt:就我而言,我想为我们现有的客户端库创建一个 Go 接口,但该库主要是 C++。将其移植到 C 或 Go 根本不是一种选择。

标签: c++ wrapper go


【解决方案1】:

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

如果你用 C 接口包装你的 C++ 代码,你应该能够用 cgo 调用你的库(参见$GOROOT/misc/cgo/gmp 中的 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

(我使用 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

【讨论】:

  • 小心这个,我不知道如果你在两种语言之间发送内存会发生什么。
  • 不得不说,这个例子提醒了我为什么要写纯 Go。看看 C++ 方面有多大和有多丑。伊克。
  • @ScottWales 你有没有机会把它放在 Github 上的 repo 或任何东西上?我很想看到一个可行的例子
  • @Arne:您不会对答案投反对票,因为它不是最好的。您对答案投了反对票,因为它没有帮助。只要它有效,即使有更好的解决方案,这个答案仍然有用。
  • 好消息,Go 现在将编译 cpp,因此不再需要 makefile。 unsafe.Pointer 包装器对我不起作用。为我编译的轻微修改:play.golang.org/p/hKuKV51cRpgo test 应该在没有 makefile 的情况下工作
【解决方案2】:

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

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

它支持继承,甚至允许使用 Go 结构子类化 C++ 类,因此当在 C++ 代码中调用被覆盖的方法时,Go 代码会被触发。

Section about C++ in Go FAQ 已更新,现在提到 SWIG 并且不再说“因为 Go 是垃圾收集的,这样做是不明智的,至少是天真地”。

【讨论】:

【解决方案3】:

你还不能从我读到的in the FAQ

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

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

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

【讨论】:

    【解决方案4】:

    从 go1.2+ 开始,cgo 会自动合并和编译 C++ 代码:

    http://golang.org/doc/go1.2#cgo_and_cpp

    【讨论】:

      【解决方案5】:

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

      (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++ 中实现的C API,以便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,调用 C++ API 的 go 程序。

      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)
      

      重要

      go 程序中 import "C" 以上的 cmets 不是可选的。您必须完全按照显示的方式放置它们,以便cgo 知道要加载哪个头文件和库,在这种情况下:

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

      Link to GitHub repo with the full example.

      【讨论】:

      • 谢谢 - 这很有帮助!
      【解决方案6】:

      看起来这是关于 Golang 的早期问题之一。同时回答永不更新。在这三到四年里,有太多的新图书馆和博客发布了。下面是我觉得有用的几个链接。

      SWIG and Go

      Calling C++ Code From Go With SWIG

      On comparing languages, C++ and Go

      GoForCPPProgrammers

      【讨论】:

        【解决方案7】:

        在使用 gcc Go 编译器 gccgo 时,有人谈论 interoperability between C and Go。然而,在使用 gccgo 时,Go 的互操作性和实现的功能集都存在限制(例如,有限的 goroutine,没有垃圾收集)。

        【讨论】:

        • 1.制作一种没有手动内存管理工具的语言, 2. 删除垃圾收集?我是唯一一个对此挠头的人吗?
        【解决方案8】:

        您正在这里踏上未知的领域。 Here 是调用 C 代码的 Go 示例,也许您可​​以在阅读 C++ name mangling 和调用约定以及大量试验和错误之后执行类似的操作。

        如果你仍然想尝试,祝你好运。

        【讨论】:

          【解决方案9】:

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

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

          【讨论】:

            【解决方案10】:

            您可能需要将 -lc++ 添加到 LDFlags 以便 Golang/CGo 识别对标准库的需求。

            【讨论】:

              【解决方案11】:

              有趣的是,该公告挖掘了多少更广泛的问题。 Dan Lyke 在他的网站 Flutterby 上进行了一次非常有趣和深思熟虑的讨论,讨论了开发 Interprocess Standards 作为一种引导新语言的方式(以及其他分支,但这是与此密切相关的分支)。

              【讨论】:

                【解决方案12】:

                这可以使用命令 cgo 来实现。

                本质上 '如果“C”的导入紧跟在注释之前,则该注释称为前导码,在编译包的 C 部分时用作标头。例如:'
                来源:https://golang.org/cmd/cgo/

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

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2022-08-05
                  • 2019-01-02
                  • 1970-01-01
                  • 2021-12-06
                  • 1970-01-01
                  • 2011-08-18
                  • 2018-01-15
                  • 2015-10-12
                  相关资源
                  最近更新 更多