gsbind
概述
gsbind 不是一个插件,gsbind是一个轻量级的头文件库,用于支持以非ffi的方式进行gs向C/C++的跨语言函数调用。
提示:
gsbind仅包含两个头文件,gsbind.h与memory_mgr.h。于项目 Gsbind 下获取。当前gip buildcpp指令流程已做平,指令执行时默认会自动拉取最新的 gsbind 头文件至your_pkg_name/cpp/gsbind/目录下。
对比FFI
gsbind 性能优势的核心在于 “将 FFI 的运行时成本转移到编译时”:
gsbind 充分利用了C++的模板和元编程特性,在编译期完成了大量传统 FFI 在运行时才执行的工作,当通过 gsbind 声明绑定代码时,这些模板会在编译时实例化并生成针对特定类型和函数的、高度优化的跨语言调用代码。
FFI 相对于 gsbind 的优势在于其不需要侵入修改 C++ 代码, 无需源码和编译,快速集成相应动态库。
FFI 需要在运行时动态查找函数符号、解析函数签名、确定参数类型和调用约定等,相对于 gsbind 具有额外开销。
gsbind 与 FFI 对比总览表
| 对比维度 | gsbind | FFI |
|---|---|---|
| 实 现机制 | 基于 C++ 模板元编程,编译时生成绑定代码,自动映射 C++ 函数到 gs efun | 运行时动态加载动态库(.so/.dll),需手动声明函数签名与数据类型 |
| 开发体验 | 🔧 中等复杂度:需编译绑定代码,但语法简洁(声明式绑定) | 🚀 快速上手:无需编译,直接调用现有库,适合快速原型 |
| 适用场景 | 高性能计算, 需复杂交互(如操作gs复杂数据类型) | 调用闭源动态库,快速原型验证,轻量级跨语言集成 |
| 性能 | ⭐⭐⭐⭐ 接近原生 C++:调用无运行时解析开销 | ⭐⭐ 固定调用开销:每次调用需跨语言边界,参数转换耗时 |
| 可变参数函数 | ⚠️ 有限支持,需使用高级功能自行处理可变参数转化问题 | ✅ 完整支持C++可变参数函数 |
| OS_PENDING_CALL | ✅ 支持 | ✅ 支持 |
| SAFE_CALL | ✅ 支持 | ✅ 支持 |
| 复杂结构值传递 | ❌ 当前不支持,后续版本尝试实现 | ✅ ffi调用支持传递struct |
| 高级功能支持 | ✅ 当前支持对 gs array/map/string/buffer 的基本操作 | ❌ 仅基础函数调用 |
gsbind支持: 当前 gsbind 为基础验证版本,仅支持基本函数调用功能,后续会进一步开发支持。
可变参数:当前 gsbind 带有高级功能的函数可以支持 pkg:hiredis 中的 redisCommand 函数的可变参数功能(需额外修改cpp代码,将可变参数处理替换为 gs 参数的传递和处理)。
注意事项
名称要求: 要求生成的动态库命名与 INIT_MODULE(module_name) 声明的 module_name 名称一致!!!!!!
内存管理: gsbind 默认注册 gs 的内存管理函数!!!
宏简介
此处简单介绍下基本的 C++ 侧 gsbind 支持必须的宏。
-
INIT_MODULE(module_name)
- 该宏会声明和定义内存管理器必要的函数或变量
- 该宏会声明,定义并导出 module_init_##module_name 和 module_shutdown_##module_name 函数
- 该宏以module用于注册函数的函数声明
void detail::module_def_funcs(ModuleHolder* m)作为结尾
-
DEFINE_FUNC(ret_type, func, name, arguments, doc, ...)
-
该宏用于调用
void def(Func&& f, const char* name, const char* prototype, const char* doc)添加注册函数 -
以
int add(int i, int j)函数为例, DEFINE_FUNC 应声明为DEFINE_FUNC(int, add, add, (int i, int j), "Add func"); -
注意 ret_type 和 arguments 中的类型应为gs类型而不是C++类型
-
该宏末尾参数为可选参数
PendingMode mode, bool is_safe_call -
PendingMode mode默认为PendingMode::NORMALPendingMode 使用场景 PendingMode::NORMAL 普通模式:直接调用函数,不进行任何额外处理。 PendingMode::PENDING 挂起模式:携程进入挂起状态,允许垃圾回收器(GC)在不等待当前协程的情况下进行回收。(当gsbind函数耗时较长时应使用此模式) PendingMode::LEAVE_DOMAIN 离开域模式:携程离开当前域并进入挂起状态。(在 PENDING 基础上希望携程放开当前域,被放开的域需用于进行其他操作时) PendingMode::POST_TO_MAIN 投递到主线程模式:将函数投递到主线程(zero)队列中执行。协程会离开当前域并进入挂起状态。(在 LEAVE_DOMAIN 基础上期望 gsbind 函数在 zero 携程执行)。 -
bool is_safe_call默认为falseis_safe_call 使用场景 false 默认值,在调用动态库函数过程中,若程序崩溃时不进行任何处理 true 在调用动态库函数过程中,若程序崩溃时尝试将崩溃作为异常抛出
-
-
DEFINE_FUNC_SIMPLE(ret_type, name, arguments, ...)
- 功能同DEFINE_FUNC,会将
prototype参数直接作为doc参数传入
- 功能同DEFINE_FUNC,会将
-
DEFINE_FUNC_LAZY(name, ...)
- 尽管使用此宏定义 gsbind 绑定函数最方便,大家也都在使用。但这里需要明确的是由于函数描述信息和函数原型均为自动生成,会造成函数功能信息的确实,所以不推荐。
- 功能同DEFINE_FUNC, 函数原型的
prototype参数由模板展开自动获取,prototype参数直接作为doc参数传入
-
由于 C++ 函数类型信息中没有参数名称,以
DEFINE_FUNC_LAZY定义的函数参数列表会被替换为 arg1,arg2... -
类成员函数已支持,定义方式形如
DEFINE_FUNC_LAZY(Class::get_a);
简单样例
在windows下开发一个简单的,仅有 int add(int i, int j) 函数的动态库。同时为动态库支持ffi调用方式与gsbind调用方式,比较两者性能。
- 工作目录下运行
gip new hello_gsbind -d指令。此指令通过 gip 使用模板新建 pkg,其会创建一个 hello_gsbind 文件夹。 - 在 /hello_gsbind/cpp/ 路径下打开终端,执行命令
git submodule add https://m68gitlab.g-bits.com/jszx/gsbind.git,以获取 gsbind 头文件。 - 在 /hello_gsbind/cpp/src/ 路径下修改 hello_gsinbd.cpp 文件,文件内容如下
//hello_gsbind.cpp
#include "gsbind.h"
//Support for FFI
extern "C"
{
GSBIND_API int add(int i, int j);
}
int add(int i, int j) {
return i + j;
}
//Support for gsbind
//Module name is gsbind_sample
INIT_MODULE(gsbind_sample)
{
DEFINE_FUNC_LAZY(add);
}
- 在 /hello_gsbind/cpp 路径下修改CMakeLists.txt,内容如下
cmake_minimum_required(VERSION 3.16)
project(gsbind_sample)
set(CMAKE_CXX_STANDARD 14)
if (WIN32)
set(SUFFIX_NAME "dll")
elseif (APPLE)
set(SUFFIX_NAME "dylib")
else ()
set(SUFFIX_NAME "so")
endif()
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/gsbind)
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src DIR_SRCS)
add_library(${PROJECT_NAME} SHARED ${DIR_SRCS})
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
- 在 /hello_gsbind/cpp 路径下执行
cmake . - 在 /hello_gsbind/cpp 打开 gsbind_sample.sln,生成 gsbind_sample.dll 动态库,将动态库移动至 /hello_gsbind/src 路径
- 在 /hello_gsbind/ 路径下执行
code .打开 Vscode - 在 /hello_gsbind/src 路径下新建 gsbind_sample.ffi 文件为如下内容(在只支持gsbind,可不撰写此 .ffi 文件)
//gsbind_sample.ffi
module(gsbind_sample)
{
int add(int i, int j);
}
- 在 /hello_gsbind/src 路径下编写gs脚本 hello_gsbind.gs 内容如下:
// hello_gsbind.gs
// Import gsbind sample
import .gsbind_sample;
// Import ffi sample
import builtin.ffi.gsffi;
const string dll = "/__dll/gsbind_samlpe.gs";
gsffi.load_library(dll, __DIR__ "gsbind_sample.ffi", this_domain(), this.name());
dll.add_dependent(this);
void benchmark()
{
int start_time, end_time;
for(int iter = 0 upto 3)
{
start_time = time.time_ms();
for(int i = 0 upto 10000000)
{
dll.add(1,2);
}
end_time = time.time_ms();
printf(HIC"FFI call add func(10M) time cost: %dms\n"NOR, end_time - start_time);
}
for(int iter = 0 upto 3)
{
start_time = time.time_ms();
for(int i = 0 upto 10000000)
{
gsbind_sample.add(1,2);
}
end_time = time.time_ms();
printf(HIC"Gsbind call add func(10M) time cost: %dms\n"NOR, end_time - start_time);
}
}
benchmark();