go 调用 另一个go 的方法_cgo 系列文章之调用Go调用C++库 (八)

点击上方蓝色“后端开发杂谈”关注我们, 专注于后端日常开发技术分享

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++库.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值