跳到主要内容
版本:master

gsbind

概述

gsbind 不是一个插件,gsbind是一个轻量级的头文件库,用于支持以非ffi的方式进行gsC/C++的跨语言函数调用。

提示: gsbind仅包含两个头文件,gsbind.hmemory_mgr.h。于项目 gsbind下获取。

对比FFI

gsbind 性能优势的核心在于 “将 FFI 的运行时成本转移到编译时”:

gsbind 充分利用了C++的模板和元编程特性,在编译期完成了大量传统 FFI 在运行时才执行的工作,当通过 gsbind 声明绑定代码时,这些模板会在编译时实例化并生成针对特定类型和函数的、高度优化的跨语言调用代码。

FFI 相对于 gsbind 的优势在于其不需要侵入修改 C++ 代码, 无需源码和编译,快速集成相应动态库。

FFI 需要在运行时动态查找函数符号、解析函数签名、确定参数类型和调用约定等,相对于 gsbind 具有额外开销。

gsbind 与 FFI 对比总览表

对比维度gsbindFFI
实现机制基于 C++ 模板元编程,编译时生成绑定代码,自动映射 C++ 函数到 gs efun运行时动态加载动态库.so/.dll),需手动声明函数签名与数据类型
开发体验🔧 中等复杂度:需编译绑定代码,但语法简洁(声明式绑定)🚀 快速上手:无需编译,直接调用现有库,适合快速原型
适用场景高性能计算, 需复杂交互(如操作gs复杂数据类型)调用闭源动态库,快速原型验证,轻量级跨语言集成
性能⭐⭐⭐⭐ 接近原生 C++:调用无运行时解析开销⭐⭐ 固定调用开销:每次调用需跨语言边界,参数转换耗时
可变参数函数❌ 当前不支持,后续版本实现✅ 完整支持C++可变参数函数
OS_PENDING_CALL❌ 当前不支持,后续版本实现✅ ffi调用完全支持
复杂结构值传递❌ 当前不支持,后续版本尝试实现✅ ffi调用支持传递struct
高级功能支持❌ 当前不支持,后续版本实现 gs 的 array, map 等复杂数据类型操作支持❌ 仅基础函数调用

gsbind支持: 当前gsbind为基础验证版本,仅支持基本函数调用功能,后续会进一步开发支持。

可变参数:当前版本若要使用如 pkg:hiredis 中的 redisCommand 函数的可变参数功能,请优先使用 ffi。gsbind会在后续进行支持。

pend调用:当前版本若要使用如 pkg:mongoc 中的 OS_PENDING_CALL 功能请优先使用 ffi。gsbind会在后续进行支持。

值传递限制:当前版本若要使用如 pkg:hiredis 中的 redisSetTimeout 函数进行值传递 struct timeval 等struct或复杂类型请优先使用 ffi。gsbind会在后续尝试进行支持。

注意事项

名称要求: 要求生成的动态库命名与 INIT_MODULE(module_name) 声明的 module_name 名称一致

内存管理: gsbind 默认注册 gs 的内存管理函数

参数传递: struct,class 等类型数据请以 C++ 指针传递

宏简介

此处简单介绍下基本的 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++类型
  • DEFINE_FUNC_SIMPLE(ret_type, name, arguments)

    • 功能同DEFINE_FUNC,会将prototype 参数直接作为doc参数传入
  • DEFINE_FUNC_LAZY(name)

    • 功能同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();
  • 运行 hello_gsbind.gs,运行结果如下:
Create page memory pool, size = 0x200000000.
>>> WINPOWER attached to the console
FFI call add func(10M) time cost: 2069ms
FFI call add func(10M) time cost: 2131ms
FFI call add func(10M) time cost: 2104ms
FFI call add func(10M) time cost: 2138ms
Gsbind call add func(10M) time cost: 194ms
Gsbind call add func(10M) time cost: 194ms
Gsbind call add func(10M) time cost: 192ms
Gsbind call add func(10M) time cost: 189ms

Welcome driver shell.
GS 1.32.250427 Copyright (C) G-bits
Shell>

从以上内容我们可以看到gs侧函数调用效率有一个数量级的提升。

类型转换

目前 gsbind 支持的类型转换如下:

  1. 函数参数或返回值的类型转换方式最终由C++侧的返回值或参数类型决定

  2. 函数调用参数从gs类型转换为c++类型操作如下:

    gs 类型C++ 类型
    intint8_t(带有范围检查过程)
    intint16_t(带有范围检查过程)
    intint32_t(带有范围检查过程)
    intuint8_t(带有范围检查过程)
    intuint16_t(带有范围检查过程)
    intuint32_t(带有范围检查过程)
    intchar(带有范围检查过程)
    intbool(带有范围检查过程)
    intlong (带有范围检查过程)
    intunsigned long(带有范围检查过程)
    intint64_t 或 uint64_t
    raw_pointerint64_t 或 uint64_t (raw_pointer.get_int_ptr)
    stringint64_t 或 uint64_t (string.data_ptr)
    bufferint64_t 或 uint64_t (buffer.data_ptr)
    floatfloat(带有范围检查过程)
    floatdouble
    floatlong double
    int指针类型
    raw_pointer指针类型(raw_pointer.get_int_ptr)
    string指针类型(string.data_ptr)
    buffer指针类型 (buffer.data_ptr)
  • 当由 gs int 类型转换为 C++ 各个整型类型时,转换带有有效范围检查,当超出有效范围时值被设置为有效范围内的最大或最小值。由下图可见传入的INT64_MAX超过了int32_t的有效范围,参数被设为INT32_MAX, 在 +1 后溢出为INT32_MIN并返回。
  • 当由 gs float 类型转换为 C++ 各个浮点类型时,转换带有的检查过程同上。
  • 当由 gs 传递带有 struct_id 的 raw_pointer类型,且在 gsbind 中函数原型中对应参数声明为 mixed 时,转换函数会进行内存空间大小检查。
  1. 函数调用返回值中 c++类型 -> gs类型转换:

    C++ 类型gs 类型
    所有的整型或int
    所有的浮点型float
    所有的指针类型raw_pointer(no subtype)
    voidvoid

样例项目

Todo list

gsbind 目前仅支持基础的跨语言函数调用,但其实现方式有很大的拓展支持与优化潜力,以下是后续可能进行的拓展的todo list。

  • gsbind 尝试支持在 C++ 动态库中操作 gs 数据类型
  • gsbind 尝试支持通过 ffi 自动生成 cpp 代码,自动封装及编译
  • gsbind 尝试添加 safe call 机制
  • gsbind 尝试支持 gs pending call
  • gsbind 尝试支持 C++ 函数重载,默认参数,Lambda表达式等
  • gsbind 尝试支持 C++ 类绑定至 gs
  • gsbind 尝试支持 C++ 智能指针