文章系列
【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 对象的映射关系。