如何在 Go 中使用 C

新手上路,请多包涵

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

原文由 Frank 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 738
2 个回答

更新: 我已经成功地将一个小型测试 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

原文由 Scott Wales 发布,翻译遵循 CC BY-SA 4.0 许可协议

我根据 Scott Wales 的回答 创建了以下示例。我已经在运行 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 API 在 C++ 中实现的 —b1bd3978c583b 可以使用它的 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)

重要的

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

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

链接到带有完整示例的 GitHub 存储库

原文由 Escualo 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题