如何在Go中使用c++

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

146861 次浏览

你还不能完全从我读到的常见问题解答:

Go程序与C/ c++程序有关联吗?

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

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

这里的问题是,兼容的实现不需要将类放在compile .cpp文件中。如果编译器可以优化一个类的存在,只要程序在没有它的情况下也能以同样的方式运行,那么它就可以从输出可执行文件中删除。

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

你正走在一个未知的领域。在这里是调用C代码的Go示例,也许你可以在阅读c++名称混乱和调用约定以及大量的试验和错误之后做类似的事情。

如果你还想尝试,祝你好运。

在使用gcc Go编译器gccgo时,有关于C和Go之间的互操作性的讨论。然而,当使用gccgo时,Go的互操作性和实现的特性集都有限制(例如,有限的goroutines,没有垃圾收集)。

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

我想在围棋中使用它。我将使用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

有趣的是,这一声明提到了许多更广泛的问题。Dan Lyke在他的网站Flutterby上有一个非常有趣和深思熟虑的讨论,关于开发进程间的标准作为一种引导新语言的方式(以及其他分支,但这是这里相关的一个)。

目前看来,SWIG是最好的解决方案:

https://www.swig.org/Doc4.0/Go.html

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

在Go FAQ中有关c++的部分更新了,现在提到SWIG,不再说“__abc1”。

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

< a href = " http://golang.org/doc/go1.2 cgo_and_cpp " > http://golang.org/doc/go1.2 cgo_and_cpp < / >

看来这是关于戈朗的一个早期问题。同时回答永不更新。在这三到四年间,有太多的新图书馆和博客文章问世了。下面是我觉得有用的几个链接。

SWIG and Go

< a href = " http://zacg.github。io/blog/2013/06/06/ Calling - C -plus-plus- Code - From - Go - With - SWIG /">调用c++代码From Go With SWIG . io/blog/2013/06/06/ Calling - C -plus-plus- Code - From - Go - With - SWIG

< a href = " http://blog.grok。/a> . se/2013/10/ On - comparative -languages- C -and- Go /">关于比较语言,c++和Go . Go

< a href = " http://code.google.com/p/go-wiki/wiki/GoForCPPProgrammers " > GoForCPPProgrammers < / >

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

我基于斯科特·威尔士的回答创建了下面的例子。我已经在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) c++实现library.cpp的代码。

#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"上面的注释是没有可选的。你必须完全按照显示的那样放置它们,以便cgo知道要加载哪个头文件和库,在这种情况下:

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

链接到GitHub repo的完整示例

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

< p >在本质上 如果导入“C”之前紧接着有一个注释,这个注释被称为preamble,在编译包的C部分时被用作头文件。例如:“< br > 来源:https://golang.org/cmd/cgo/ < / p >
// #include <stdio.h>
// #include <errno.h>
import "C"