【新功能介绍】【js宏开发】jside ffi: 跨语言调用机制
王子陶
@金山办公
功能介绍
在js宏中调用外部的使用C、C++、Go、Zig、Rust等系统级编程语言编写的动态库
快速入门
1. 用C++语言(或其他native语言)编写导出函数,并编译成动态库
以windows平台为例
#include <cstdint>
#if defined(_WIN32) || defined(_WIN64)
#define C_EXPORT __declspec(dllexport)
#else
#define C_EXPORT __attribute__((visibility("default")))
#endif
C_EXPORT int32_t add(int32_t a, int32_t b) {
return a + b;
}
C_EXPORT int32_t sum(int32_t count, int32_t* numbers) {
int32_t total = 0;
for (int32_t i = 0; i < count; ++i) {
total += numbers[i];
}
return total;
}2. 用js宏加载函数
const {add, sum} = ffi.LoadLibrary("C:/path/to/library.dll",{
add: { returnType: "int32", parameters: ["int32","int32"]},
sum: { returnType: "int32", parameters: ["int32","pointer"]},
})3. 调用函数
console.log(add(1,2))
let array = new Int32Array([1,2,3,4])
console.log(sum(array.length, array.buffer))特性
js调用native函数
js动态合成C语言是函数,作为回调函数
在js中合成和访问C语言结构体、联合体、定长数组、多重指针等, 甚至生成模板类型
从js访问C语言的指针,进行指针运算、读写、数组访问、分配释放等操作
跨操作系统。Windows、Gnu/Linux、MacOS、Ohos(鸿蒙都支持)。
跨CPU架构。x86/x64、aarch64、mips、loongarch(龙芯)等CPU都支持。
添加平台api,可以获取操作系统和CPU类型信息。
支持将ArrayBuffer当作void*类型传递给C函数
使用场景
调用sqlite,访问数据库
调用openssl,对数据加密
调用wgpu、opengl,进行3d渲染
调用dbus,与linux系统和桌面的其他组件通信,调用linux系统的功能
调用curl,用各种协议连接服务器
调用User32.dll,调用windows系统的功能
例子
结构体
let Vector3 = ffi.Struct([
{ name: "x", type: "float" },
{ name: "y", type: "float" },
{ name: "z", type: "float" },
])
let vector3 = new Vector3 ({x:4,y:5,z:6})
vector3.x = 1.0
vector3.y = 2.0
vector3.z = 3.0
console.log(vector3.toObject().x)结构体模板
template<typename T>
struct Vector3 {
T x;
T y;
T z;
};let Vector3 = (T) => ffi.Struct([
{ name: "x", type: T },
{ name: "y", type: T },
{ name: "z", type: T },
])
let Vector3f = Vector3("float")
let vector3 = new Vector3f ({x:4,y:5,z:6})文档
更多信息请参考文档。文档中使用Typescript来描述ffi的api
复杂例子
用ffi访问sqlite数据库
完整例子在文档中
// 数据库指针类型
let DbPtrType = "pointer"
// 数据库二重指针类型,传参时其值可以被转为二重指针
let DbPtrPtrType = ffi.Buffer(DbPtrType)
// 字段名列表类型
let FieldArray = ffi.Pointer("string")
// 值列表类型
let DataArray = ffi.Pointer("string")
let CallbackType = ffi.Function({ffi:"",returnType:"int32",parameters:[DbPtrType, "int32", FieldArray, DataArray]})
// 报错字符串类型,传参时其值可以被转为二重指针
let ErrorStringType = ffi.Buffer("string")
// 按照平台设置动态库路径
let os = Platform.OS()
let arch = Platform.Arch()
let sqlite_path = "TODO"
// 省略获取sqlite_path路径的代码
// 加载sqlite动态库和函数
const sqlite_lib = ffi.LoadLibrary(sqlite_path, {
sqlite3_open: { returnType: "int32", parameters: ["string",DbPtrPtrType]},
sqlite3_close: { returnType: "int32", parameters: [DbPtrType]},
sqlite3_exec: {returnType: "int32",parameters: [DbPtrType,"string",CallbackType,"pointer",ErrorStringType ]},
})
// 枚举值定义
const SQLITE_OK = 0
const SQLITE_DONE = 101
// 对sqlite的封装
class Sqlite{
constructor(path){
let db_ptr_ptr = new DbPtrPtrType()
// 打开数据库. db_ptr_ptr是个ArrayBuffer, 会被转为指针
this.call_api(()=>sqlite_lib.sqlite3_open(path, db_ptr_ptr))
// 取出db_ptr_ptr内部被写入的指针
this.db = db_ptr_ptr.read()
}
// 对错误处理流程进行封装
call_api(func) {
let code = func.apply(this)
if (code != SQLITE_OK && code != SQLITE_DONE){
throw new Error("sqlite api error: "+code)
}
}
// 关闭数据库
close(){
this.call_api(()=>sqlite_lib.sqlite3_close(this.db))
}
// 调用sql语句
exec(sql, callback){
console.log("exec: " + sql)
let error_string_ptr = new ErrorStringType()
// sqlite执行sql语句. 这里闭包类型(CallbackType)会被转为函数指针, Buffer类型(ErrorStringType)会被取指针
let code = sqlite_lib.sqlite3_exec(this.db,sql,callback || null,null,error_string_ptr)
// 错误处理
if (code != SQLITE_OK && code != SQLITE_DONE){
let message = error_string_ptr.read()
throw new Error(`sqlite api error: ${code}, message: ${message}`)
}
}
// 调用select语句, 并获取数据
// types是字段类型的字符串解析函数的数组
select(sql,types) {
let table = []
// 合成闭包
let callback = CallbackType.createJsFunction((p, cols /*: number*/ , argv /*: PointerValue*/, colv /*: PointerValue*/)=>{
// 闭包内部需要用try-catch , 因为js异常和C++异常无法被C语言处理
try{
let record = {}
// 遍历C语言式数组指针
for (let i =0; i< cols; i++){
// 读取字段名
let colName = colv.read(i)
// 读取字段值
let colData = argv.read(i)
record[colName] = types[i](colData)
}
table.push(record)
return SQLITE_OK;
} catch (e) {
alert(e)
}
})
this.exec(sql,callback)
return table
}
}
WPS产品体验官
创作者俱乐部成员