点击上方蓝色“后端开发杂谈”关注我们, 专注于后端日常开发技术分享
Go 调用 C++
CGO 是 C 语言和 Go语言之间的桥梁, 原则上无法支持 C++ 的类. CGO 不支持 C++ 语法的根本原因是 C++ 至今为止还没有一个 二进制接口规范(ABI). 一个 C++ 类的构造函数在编译为目标文件时如何生成链接符号,方法在不同的平台甚至是C++的不同版本之间 都是不一样的.
但是 C++ 是兼容 C 语言, 所以可以通过增加一组 C 语言函数接口作为 C++ 类和 CGO 之间的桥梁, 这样可以间接地实现 C++ 和 Go 之间的互联. 因为 CGO 只支持 C 语言中值类型的数据类型, 所以无法直接使用 C++ 的引用参数等特性的.
C++ 类到 GO 语言对象
实现 C++ 类到 Go 语对象的包装需要以下几个步骤: 首先是用纯 C 函数接口包装该 C++ 类; 其次是通过 CGO 将纯 C 函数接口映 射到 Go 函数; 最后是做一个 Go 包装对象, 将 C++ 类到方法用 Go 对象的方法实现.
需要注意的点:
1.必须采用了静态库/动态库链接的方式去编译链接. ( 使用代码的方式编译不了 )
2.在使用 gcc 编译包装了 C++ 库的时候, 一定要链接
stdc++
库 ( 即一定要有LDFLAGS
参数, 且必须包含-l stdc++
选项 ).
下面是一个详细的案例:
C++ 代码:
buffer.h, buffer.cpp 是使用 c++ 实现的一个简单缓存类
buffer.h
#include class Buffer { private: std::string* s_; public: Buffer(int size); ~Buffer(){} int Size(); char* Data();};
buffer.cpp
#include "buffer.h"#include
Buffer::Buffer(int size) { this->s_ = new std::string(size, char('\0'));}
int Buffer::Size() { return this->s_->size();}
char* Buffer::Data() { return (char*)this->s_->data();}
buffer_c.h 和 buffer.cpp 是使用 C 代码包装 C++ 的类 Buffer, 这个也称为桥接
需要深刻理解
extern "C"
的含义, 这是 Go 调用 C++ 的关键环节.
buffer_c.h
#ifdef __cplusplusextern "C" {#endif
// 定义 Buffer 的包装类 Buffer_T
typedef struct Buffer_T Buffer_T;
Buffer_T* NewBuffer(int size);void DeleteBuffer(Buffer_T* p);char* Buffer_Data(Buffer_T* p);int Buffer_Size(Buffer_T* p);
#ifdef __cplusplus}#endif
buffer_c.cpp
#include "buffer.h"#include "buffer_c.h"
// 注意这里的包装继承机制struct Buffer_T: Buffer { Buffer_T(int size): Buffer(size) {} ~Buffer_T() {}};
Buffer_T* NewBuffer(int size) { Buffer_T* p = new Buffer_T(size);
return p;}
void DeleteBuffer(Buffer_T* p) { delete p;}
char* Buffer_Data(Buffer_T* p) { return p->Data();}
int Buffer_Size(Buffer_T* p) { return p->Size();}
buffer_c.go 是使用 go 对 C 代码进行了一次包装.
注意: 在链接的时候一定要链接库 buffer 和 stdc++
buffer_c.go
package main/*#cgo CXXFLAGS: -std=c++11 -I .#cgo LDFLAGS: -L . -l buffer -l stdc++
#include "buffer_c.h"*/import "C"
/*这里采用了静态库链接的方式, 链接 libbuffer.a 文件在使用 gcc 编译包装了 C++ 库的时候, 一定要链接 "stdc++" 库.*/
type cgo_Buffer_T C.Buffer_T
func cgo_NewBuffer(size int) *cgo_Buffer_T {
p := C.NewBuffer(C.int(size)) return (*cgo_Buffer_T)(p)}
func cgo_DeleteBuffer(p *cgo_Buffer_T) { C.DeleteBuffer((*C.Buffer_T)(p))}
// 获取 buffer 的指针func cgo_Buffer_Data(p *cgo_Buffer_T) *C.char { return C.Buffer_Data((*C.Buffer_T)(p))}
// 获取 buffer 大小func cgo_Buffer_Size(p *cgo_Buffer_T) C.int { return C.Buffer_Size((*C.Buffer_T)(p))}
buffer_go.go, 按照 go 的规范包装 buffer_c.go 代码
buffer_go.go
package main
import "unsafe"
type Buffer struct {
cptr *cgo_Buffer_T}
func NewBuffer(size int) *Buffer { return &Buffer{cptr: cgo_NewBuffer(size)}}func (p *Buffer) Delete() { cgo_DeleteBuffer(p.cptr)}
// 获取Buffer的内容func (p *Buffer) Data() []byte { data := cgo_Buffer_Data(p.cptr)
size := cgo_Buffer_Size(p.cptr)
return ((*[1 << 31]byte)(unsafe.Pointer(data)))[0:int(size):int(size)]}
main.go, 最终的测试函数
main.go
// #include import "C"
import "unsafe"func main() {
// 创建 C++ Buffer
buf := NewBuffer(1024) defer buf.Delete()
// 将 "Hello" 放置到 Buffer 当中
copy(buf.Data(), []byte("Hello\x00") C.puts((*C.char)(unsafe.Pointer(&(buf.Data()[0]))))}
makefile 编译
makefile
export LIBRARY_PATH=$(CURDIR)SRC = $(wildcard *.go)
# build go, dep buffer static libgomain: $(SRC) libbuffer.a
go build -o gomain -ldflags "-w" -x $(SRC)
# build libbuffer static liblibbuffer.a: $(CXX) $(CXXFLAGS) -c buffer_c.cpp buffer_cpp.cpp $(AR) -r libbuffer.a buffer_c.o buffer_cpp.o
.PHONY : cleanclean: -rm -rf gomain *.o *.a *.gch
Go 调用 C++ 库的开发步骤:
第一步: 使用 C 包装 C++ 库当中的类. 即将 C++ 的类方法全部转换成 C 函数. 这一步虽然很简单, 但是需要注意细节. 提前做好 C 调用 C++ 代码的知识积累. 这一步也称为桥接过程.
第二步: 使用 Go 语言去包装 C 代码. 这一步比较麻烦, 需要注意 CGO 类型的转换.
第三步: 将上面 Go 包装 C 的代码再次封装成 Go 的对象. 这样做的目的是为了对上层业务透明. 这一步可以省略.
第四步: 业务调用第二步或者第三步封装好的 Go 函数. 从而完成Go调用C++库.