【GO】Golang/C++混合编程 - 实战

文章系列
【GO】Golang/C++混合编程 - SWIG
【GO】Golang/C++混合编程 - 初识
【GO】Golang/C++混合编程 - 入门
【GO】Golang/C++混合编程 - 基础
【GO】Golang/C++混合编程 - 进阶一
【GO】Golang/C++混合编程 - 进阶二
【GO】Golang/C++混合编程 - 实战

Golang/C++混合编程

代码组成

目录结构

├── cgo
│   ├── bridge
│   │   ├── example_bridge.cpp
│   │   ├── example_bridge.h
│   │   ├── example.go
│   │   ├── cgo.go
│   │   ├── ...

公共参数

// cgo.go

package xxx

// #cgo CXXFLAGS: -I./
// #cgo CXXFLAGS: -I{your_path}
// #cgo LDFLAGS: -L{your_path} -l{your_lib}
// #cgo LDFLAGS: -framework {your_framework}
// #cgo CXXFLAGS: -std={your_cxx_version}
//
import "C"

此时,同目录下所有文件,都可以使用 cgo.go 内的内容

命名规则

文件

  • {name}_bridge.cpp -> {name}.cpp 的封装
  • {name}_bridge.h -> {name}.h 的封装
  • {name}.go -> cgo 代码的封装

类/结构体

  • struct A_T -> class A

函数

  • A's func -> {xx}A{xx}

C++类型

枚举

// example_enum.h

#ifndef _EXAMPLE_ENUM_H_
#define _EXAMPLE_ENUM_H_

#ifdef __cplusplus
extern "C" {
#endif

typedef enum {
    DataZero = 0,
    DataOne = 1,
    DataTwo = 2
} ExampleEnumType_T;

#ifdef __cplusplus
}
#endif

#endif // _EXAMPLE_ENUM_H_
// example_enum.go

package example_enum

// #include "example_enum.h"
import "C"

const (
    DataZero = iota - 1
    DataOne
    DataTwo
)
// use in go

func main() {
    A(example_enum.DataZero)
}

func A(data int) {
    cgo_A(data)
}

func cgo_A(data int) {
    c_enum := C.ExampleEnumType_T(data)
    // ...
}

std::map<std::string, std::string>

不同的 map 类型需要不同的封装,如 std::map<std::string, std::string> -> StringStringMap_T

通过 C 函数,封装 C++ 的 map 类型操作,从而在 Go 中使用

作为参数传递时,需要使用 void* 类型,在 GO 中使用 unsafe.Pointer 类型接收,后在 C++ 中转换成 std::map<std::string, std::string>* 类型

// std_map.cpp

#include <map>
#include <string>
#include "std_map.h"

struct StringStringMap_T{
    std::map<std::string, std::string>* map;
};

StringStringMap_T* NewStringStringMap() {
   StringStringMap_T* p = new StringStringMap_T();
   p->map = new std::map<std::string, std::string>();
   return p;
}

void DeleteStringStringMap(StringStringMap_T* p) {
   delete p;
}

void StringStringMapPut(StringStringMap_T* p, const char* key, const char* value) {
    (*p->map)[key] = value;
}

const char* StringStringMapGet(StringStringMap_T* p, const char* key) {
    if (p && p->map) {
        auto it = p->map->find(key);
        if (it != p->map->end()) {
            return it->second.c_str();
        }
    }
    return nullptr;
}

void StringStringMapDelete(StringStringMap_T* p, const char* key) {
    p->map->erase(key);
}

void StringStringMapDeleteAll(StringStringMap_T* p) {
    delete p->map;
}

void* StringStringMapGetStdMap(StringStringMap_T* p) {
   return p->map;
}
// std_map.h

#ifndef _STD_MAP_H_
#define _STD_MAP_H_

#ifdef __cplusplus
extern "C" {
#endif

typedef struct StringStringMap_T StringStringMap_T;
StringStringMap_T* NewStringStringMap();
void DeleteStringStringMap(StringStringMap_T* p);
void StringStringMapPut(StringStringMap_T* p, const char* key, const char* value);
const char* StringStringMapGet(StringStringMap_T* p, const char* key);
void StringStringMapDelete(StringStringMap_T* p, const char* key);
void StringStringMapDeleteAll(StringStringMap_T* p);
void* StringStringMapGetStdMap(StringStringMap_T* p);


#ifdef __cplusplus
}
#endif

#endif // _STD_MAP_H_
// std_map.go

package std_map

// #include "std_map.h"
import "C"
import "unsafe"

// ------------------------------------------------
// cgo wrapper for StringStringMap
type cgo_StringStringMap_T C.StringStringMap_T

func cgo_NewStringStringMap() *cgo_StringStringMap_T {
    p := C.NewStringStringMap()
    return (*cgo_StringStringMap_T)(p)
}

func cgo_DeleteStringStringMap(p *cgo_StringStringMap_T) {
    C.DeleteStringStringMap((*C.StringStringMap_T)(p))
}

func cgo_StringStringMapPut(p *cgo_StringStringMap_T, key, value string) {
    C.StringStringMapPut((*C.StringStringMap_T)(p), C.CString(key), C.CString(value))
}

func cgo_StringStringMapGet(p *cgo_StringStringMap_T, key string) string {
    value := C.StringStringMapGet((*C.StringStringMap_T)(p), C.CString(key))
    return C.GoString(value)
}

func cgo_StringStringMapDelete(p *cgo_StringStringMap_T, key string) {
    C.StringStringMapDelete((*C.StringStringMap_T)(p), C.CString(key))
}

func cgo_StringStringMapDeleteAll(p *cgo_StringStringMap_T) {
    C.StringStringMapDeleteAll((*C.StringStringMap_T)(p))
}

func cgo_StringStringMapGetStdMap(p *cgo_StringStringMap_T) unsafe.Pointer {
    return C.StringStringMapGetStdMap((*C.StringStringMap_T)(p))
}

// ------------------------------------------------
// go wrapper for cgo_StringStringMap_T
type StringStringMap struct {
    cptr *cgo_StringStringMap_T
}

func NewStringStringMap() *StringStringMap {
    return &StringStringMap{
        cptr: cgo_NewStringStringMap(),
    }
}

func (p *StringStringMap) Delete() {
    cgo_DeleteStringStringMap(p.cptr)
}

func (p *StringStringMap) Put(key, value string) {
    cgo_StringStringMapPut(p.cptr, key, value)
}

func (p *StringStringMap) Get(key string) string {
    return cgo_StringStringMapGet(p.cptr, key)
}

func (p *StringStringMap) DeleteOne(key string) {
    cgo_StringStringMapDelete(p.cptr, key)
}

func (p *StringStringMap) DeleteAll() {
    cgo_StringStringMapDeleteAll(p.cptr)
}

func (p *StringStringMap) GetStdMap() unsafe.Pointer {
    return cgo_StringStringMapGetStdMap(p.cptr)
}
// use in go

func main(){
    cmap := std_map.NewStringStringMap()
    cmap.Put("key1", "value1")

    TestStdMap("key1", cmap.GetStdMap())
}

func TestStdMap(key string, ptr unsafe.Pointer) {
    cgo_TestStdMap(p.cptr, key, ptr)
}

func cgo_TestStdMap(key string, ptr unsafe.Pointer) {
    C.TestStdMap(C.CString(key), ptr)
}

// xxx.cpp
void TestStdMap(const char* key, void* ptr) {
   std::map<std::string, std::string>* map_ptr = static_cast<std::map<std::string, std::string>*>(ptr);
   auto it = map_ptr->find(key);
   if (it != map_ptr->end()) {
      std::cout << it->second << std::endl;
      return;
   }
   std::cout << "not found" << std::endl;
}

std::shared_ptr

在使用 CGO 调用 C++ 时,如果 C++ 变量类型为std::shared_ptr时,我们无法直接在 Go 中使用,因为 Go 语言没有直接支持 C++ 的智能指针。但是我们可以通过一些技巧来实现 Go 和 C++ 之间的共享指针传递。

通常使用如下方法,封装一个新的类,将std::shared_ptr作为其成员变量使用,并在类的构造函数和析构函数中分别调用std::shared_ptr的构造和析构函数。这样,在 Go 中就可以通过这个类来间接使用std::shared_ptr了。

在获取std::shared_ptr类型变量时,我们使用其内置的get()方法获取其真实类型,并用void *作为返回值类型,并在 GO 中使用unsafe.Pointer来接收这个返回值。

变量接收函数,使用void *类型接收,并在 C++ 中将其转换为目标类型后,再将其转换为对应的std::shared_ptr后进行使用。

#include "xxx.h"
#include "xxxx.h"

struct Instant_T {
   std::shared_ptr<a::b::c::d> p;
   
   Instant_T(int size, char* tag) {
      std::string etag;
      etag.assign(tag);
      p = std::shared_ptr<a::b::c::d>(new a::b::c::d(size, etag));
   }
   ~Instant_T() {
      p.reset();
   }
};

Instant_T* NewInstant(int size, char* tag) {
   Instant_T* p = new Instant_T(size, tag);
   return p;
}

void DeleteInstant(Instant_T* p) {
   delete p;
}

void* GetInstantPPtr(Instant_T* p) {
   return p->p.get();
}
// RecvSharePtr 使用 std::shared_ptr
void RecvSharePtr(Instant2_T* p, void* ptr) {
   a::b::c::d* edge_ptr = static_cast<a::b::c::d*>(ptr);
   std::shared_ptr<a::b::c::d> share_edge_ptr(edge_ptr);

   p->user_share_ptr_func(share_edge_ptr);
}

go callback

官方示例中,多使用export关键字,将函数导出给C调用,以此作为回调函数。但是,我们在实际开发中多为面向对象编程,所以需要将回调函数封装到结构体中,并使用结构体指针作为回调函数的参数。此时我们可以参考如下使用方式:

// callback_bridge.cpp

#include "xxx.h"
#include "callback_bridge.h"

class CNodeCallback: public nebula::base::graph::NodeCallback {
public:
   void *goCallbackObj;
   GoOnEventCallback goOnEventCallback;

   void onEvent(std::string node_name, int event, int code, std::string message) {
     if (goOnEventCallback != nullptr) {
         char *c_node_name = new char[strlen(node_name.c_str())+1];
         strcpy(c_node_name, node_name.c_str());

         char *c_message = new char[strlen(message.c_str())+1];
         strcpy(c_message, message.c_str());

         goOnEventCallback(goCallbackObj, c_node_name, event, code, c_message);
      } else {
         std::cout<< "in callback_br.cpp:" << " C:NodeCallback::onEvent: goOnEventCallback is null" << std::endl;
      }
   };
};

struct CNodeCallback_T {
   std::shared_ptr<CNodeCallback> nodeCallback;
   CNodeCallback_T() {
      nodeCallback = std::make_shared<CNodeCallback>();
   }
   ~CNodeCallback_T() {
      nodeCallback.reset();
   }
};

CNodeCallback_T* NewCNodeCallback() {
   CNodeCallback_T* p = new CNodeCallback_T();
   return p;
}

void DeleteCNodeCallback(CNodeCallback_T* p) {
   delete p;
}

void* GetCNodeCallback(CNodeCallback_T* p) {
   return p->nodeCallback.get();
}

int SetCNodeGoOnEventCallback(CNodeCallback_T* p, void *o, GoOnEventCallback f) {
   p->nodeCallback->goCallbackObj = o;
   p->nodeCallback->goOnEventCallback = f; 
   return 0;
}
// callback_bridge.h

#ifndef _MEDIAKIT_CALLBACK_BR_H_
#define _MEDIAKIT_CALLBACK_BR_H_

#ifdef __cplusplus
extern "C" {
#endif

#include <stdio.h>
#include <string.h>
#include <stdbool.h>

typedef struct CNodeCallback_T CNodeCallback_T;
CNodeCallback_T* NewCNodeCallback();
void DeleteCNodeCallback(CNodeCallback_T* p);
void* GetCNodeCallback(CNodeCallback_T* p);

typedef void(*GoOnEventCallback) (void *goObj, char *node_name, int event, int code, char *message);
int SetCNodeGoOnEventCallback(CNodeCallback_T* p, void *o, GoOnEventCallback f);


#ifdef __cplusplus
}
#endif

#endif // _MEDIAKIT_CALLBACK_BR_H_
// callback.go

package callback

// #include "callback_bridge.h"
// void cgo_OnEventCallback(void *, char *, int, int, char *);
import "C"

import (
    "errors"
    "fmt"
    "sync"
    "unsafe"
)

// --------------------------------------------------------
// cgo wrapper for CNodeCallback
type cgo_CNodeCallback_T C.CNodeCallback_T

func cgo_NewCNodeCallback() *cgo_CNodeCallback_T {
    p := C.NewCNodeCallback()
    return (*cgo_CNodeCallback_T)(p)
}

func cgo_DeleteCNodeCallback(p *cgo_CNodeCallback_T) {
    C.DeleteCNodeCallback((*C.CNodeCallback_T)(p))
}

func cgo_GetCNodeCallbackPtr(p *cgo_CNodeCallback_T) unsafe.Pointer {
    return C.GetCNodeCallback((*C.CNodeCallback_T)(p))
}

//export cgo_OnEventCallback
func cgo_OnEventCallback(obj unsafe.Pointer, nodeName *C.char, event C.int, code C.int, message *C.char) {
    dt := (*DataTransfer)(obj)
    dt.onEvent(C.GoString(nodeName), int(event), int(code), C.GoString(message))
}

func cgo_UseGoCallback(p *cgo_CNodeCallback_T, o *DataTransfer) int {
    res := C.SetCNodeGoOnEventCallback((*C.CNodeCallback_T)(p), unsafe.Pointer(o), C.GoOnEventCallback(C.cgo_OnEventCallback))
    return int(res)
}

// ------------------------------------------------
// cgo wrapper for DataTransfer
type DataTransfer struct{}

func NewDataTransfer() *DataTransfer {
    return &DataTransfer{}
}

func (p *DataTransfer) onEvent(nodeName string, event int, code int, message string) {
    nc := getNodeCallbackByDataTransfer(p)
    if nc == nil {
        fmt.Println("no callback found")
        return
    }
    nc.innerOnEvent(nodeName, event, code, message)
}

// ------------------------------------------------
// callback func type
type OnEvent func(string, int, int, string)

// ------------------------------------------------
// go wrapper for cgo_CNodeCallback_T
type NodeCallback struct {
    cptr         *cgo_CNodeCallback_T
    dataTransfer *DataTransfer
    onEvent      OnEvent
}

func NewNodeCallback() *NodeCallback {
    obj := &NodeCallback{
        cptr:         cgo_NewCNodeCallback(),
        dataTransfer: NewDataTransfer(),
    }
    registerChildToParentMap(obj.dataTransfer, obj)
    return obj
}

func (p *NodeCallback) Delete() {
    cgo_DeleteCNodeCallback(p.cptr)
}

func (p *NodeCallback) GetCNodeCallbackPtr() unsafe.Pointer {
    return cgo_GetCNodeCallbackPtr((*cgo_CNodeCallback_T)(p.cptr))
}

func (p *NodeCallback) RegisterOnEventCb(f OnEvent) error {
    r := cgo_UseGoCallback((*cgo_CNodeCallback_T)(p.cptr), p.dataTransfer)
    if r != 0 {
        return errors.New("RegisterOnEventCb error")
    }

    p.onEvent = f
    return nil
}

func (p *NodeCallback) innerOnEvent(nodeName string, event int, code int, message string) {
    if p.onEvent == nil {
        fmt.Println("NodeCallback(), onEvent: ", nodeName, event, code, message)
    } else {
        p.onEvent(nodeName, event, code, message)
    }
}

// ------------------------------------------------
// 全局管理
var lock sync.RWMutex = sync.RWMutex{}
var childToParentMap = make(map[*DataTransfer]*NodeCallback)

func registerChildToParentMap(dt *DataTransfer, nc *NodeCallback) {
    lock.Lock()
    defer lock.Unlock()

    childToParentMap[dt] = nc
}

func unregisterChildToParentMap(dt *DataTransfer) {
    lock.Lock()
    defer lock.Unlock()

    delete(childToParentMap, dt)
}

func getNodeCallbackByDataTransfer(dt *DataTransfer) *NodeCallback {
    lock.RLock()
    defer lock.RUnlock()

    return childToParentMap[dt]
}
// main.go

// use in go
type Business struct {
}

func (b *Business) OnEventCallback(nodeName string, event, code int, message string) {
    fmt.Println("Business OnEventCallback", nodeName, event, code, message)
}

func main(){
    nodeCallback := mediakit_go.NewNodeCallback()
    
    b := &Business{}
    _ = nodeCallback.RegisterOnEventCb(b.OnEventCallback)
}

其中,cgo_CNodeCallback_T 为 C++ 中类的封装,cgo_OnEventCallback为导出函数,运行 C++ 调用。

cgo_UseGoCallback,在 C++ 层持有 GO 对象,以及回调函数指针。

DataTransfer作为中介,联通 GO 对象和 CGO 对象。

NodeCallback,GO 层对象,用于接收 C++ 回调。

childToParentMap,用于管理 NodeCallback 对象和 DataTransfer 对象的映射关系。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容