介绍
近日,Sciter 团队发布了一项震撼演示------将 three.js 经典的 webgl buffergeometry 线条绘制案例移植到 C 模块环境中。这个包含 500 个 3D 粒子(总计算量达 500×500/2=125,000 次/帧)的粒子系统,在动画过程中需要实时计算粒子间距离:
左侧为 Sciter 运行效果,右侧为 MS Edge,均保持 60FPS。 要知道这种平方级复杂度计算(O(N²))会导致 V8引擎 JIT 优化失效,并且高频函数容易调用引发 GC 停顿。
然后在最新发布的 Sciter 6.0.1.0 中,这一革命性功能------ CModules( C 语言模块)就正式落地!这意味着开发者现在可以用纯 C 语言编写高性能模块,并与 JavaScript 无缝交互。
象我们如果做游戏开发,需要在物理引擎/3D 计算加速一些高性能的计算,或在密码学需要高性能加密算法实现。都能直接在 sciter.js 本身来实现了。
CModules 核心特性速览
通过集成轻量级编译器 TinyCC(支持 ANSI C / C99 / GNU 扩展 ),Sciter 实现了三大创新设计:
-
模块化编译
- 独创的
#import "file.c"
语法,支持多文件联合编译(类似C项目的多编译单元管理) - 示例代码见官方 SDK 的 /sdk/samples.c 目录
- 独创的
-
双向交互通道
- 使用
export
关键字标记需要暴露给JS的C函数 - 引入
jsvalue
数据类型(直接操作JS对象的神器) - 支持整型家族(int8/16/32/64)、浮点类型、指针以及JS对象引用
- 使用
-
安全沙箱机制
- 默认关闭模式需通过
ALLOW_CMODULES
显式开启 - 编译时需在 premake5.lua 添加
--CMODULES
编译参数
- 默认关闭模式需通过
js
SciterSetOption(NULL, SCITER_SET_SCRIPT_RUNTIME_FEATURES,
ALLOW_FILE_IO |
ALLOW_SOCKET_IO |
ALLOW_EVAL |
ALLOW_SYSINFO |
ALLOW_CMODULES // <<<<<<<<<<<<<<<
);
这个带来的优势如下
- 内存直通技术:C 模块直接操作 WebGL 缓冲区内存,消除 JS 与 Native 的数据拷贝
- SIMD指令优化:TinyCC 编译器自动生成 SSE/AVX 向量化指令
- 零GC压力:堆外内存管理避免 JavaScript 引擎的垃圾回收抖动
从C到JS的无缝衔接
c
// 示例:高性能数学计算模块
export double calculateHypotenuse(double a, double b) {
return sqrt(a*a + b*b); // 毕达哥拉斯定理的C语言实现
}
// 示例:处理复杂JS对象
export jsvalue processUserData(jsvalue input) {
// 使用Sciter内置API操作JS对象
if(is_string(input)) {
const char* str = get_string(input);
//... 复杂字符串处理
}
return create_object(); // 返回新JS对象
}
使用
启用方法
lua
-- premake5.lua 配置示例
project "YourApp"
add_cmodules("--CMODULES") -- 魔法开关在此
我们在运行时初始化,需要在头文件中加上如下内容。
c
// 运行时初始化(安全第一!)
SciterSetOption(NULL,
SCITER_SET_SCRIPT_RUNTIME_FEATURES,
ALLOW_FILE_IO | // 文件操作
ALLOW_SOCKET_IO | // 网络通信
ALLOW_EVAL | // 动态执行
ALLOW_SYSINFO | // 系统信息
ALLOW_CMODULES // C模块开关 ←重点!
);
【技术深潜】与传统方案相比,Sciter的C模块实现了三大突破:
- 编译速度提升:TinyCC 的即时编译特性比传统编译器快 10 倍以上
- 内存安全:独创的 jsvalue 封装层避免野指针问题
- 跨平台一致性:Windows/macOS/Linux 三端统一 ABI 接口
目前该功能已在 GitHub 的 Sciter.GLX 分支开放测试,期待开发者们用 C 语言打破性能边界!其中jsbridge.h
头文件扮演着连接 JavaScript 与 C 世界的桥梁角色
转换速查
函数类别 | 明星函数 | 应用场景 |
---|---|---|
基础类型转换 | int32_to_jsvalue() | 处理标量参数/返回值 |
指针操作 | float_to_jsvalue() | 传递预先分配的缓冲区 |
高级构造 | _jsvalue_new() | 构建复杂JS对象/数组 |
数据提取 | _jsvalue_fetch() | 解析来自JS的结构化数据 |
类型探测 | _jsvalue_type() | 运行时类型安全检查 |
内存管理 | _jsvalue_addref() | 长生命周期对象管理 |
常见问题需要注意:
- 在非主线程操作jsvalue(Sciter JS单线程特性)
- 忽略jsvalue_type检查导致类型转换崩溃
- 循环引用造成的内存泄漏(C↔JS对象互引用)
最新调试工具链已加入JSVALUE_DEBUG_TRACING
编译选项,可实时追踪jsvalue生命周期。
实例 demo
如下是作者 Andrew 在这使用了一个 c 模块和一个 js 实现相同功能的对比。 这里的 C 模块的功能类似于 JS 模块,但其 C 源代码会被实时编译为原生代码。因此这些是原生模块,无需额外安装C编译器,因为 Sciter 内置了 tinycc 引擎。
js
<html>
<head>
<script|module>
import * as cmod from "./cmod.c"; // C module
import * as jsmod from "./jsmod.js"; // JS module
const floats = new Float32Array(3000);
for(let i = 0; i < floats.length; ++i)
floats[i] = Math.random();
function time(f) {
let a = new Date().getTime();
f();
let b = new Date().getTime();
return b - a;
}
let d1, d2;
// JS version
let t1 = time(() => d1 = jsmod.closestDistance(floats));
// native C version
let t2 = time(() => d2 = cmod.closestDistance(floats));
document.body.append(<ul>
<li>JS res=<var>{printf("%f",d1)}</var> exectime=<var>{t1}ms</var></li>
<li>C res=<var>{printf("%f",d2)}</var> exectime=<var>{t2}ms</var></li>
</ul>);
</script>
</head>
<body>
</body>
</html>
以下是那个cmod.c文件的源代码:
c++
#include <math.h>
#include <jsbridge.h>
export float closestDistance(floats dots) {
float m = 3.0;
for( int i = 0; i < dots.length; i += 3 ) {
for( int j = 0; j < dots.length; j += 3 ) {
if( i == j) continue;
float x = dots.data[i] - dots.data[j];
float y = dots.data[i+1] - dots.data[j+1];
float z = dots.data[i+2] - dots.data[j+2];
float d = sqrtf(x*x + y*y + z*z);
if( d < m ) m = d;
}
}
return m;
}
为了完整性,jsmod.js 也实现了相同的功能并对外暴露:
js
export function closestDistance(dots) {
let m = 3.0;
for( let i = 0; i < dots.length; i += 3 ) {
for( let j = 0; j < dots.length; j += 3 ) {
if( i == j) continue;
let x = dots[i] - dots[j];
let y = dots[i+1] - dots[j+1];
let z = dots[i+2] - dots[j+2];
let d = Math.sqrt(x*x + y*y + z*z);
if( d < m ) m = d;
}
}
return m;
}
sciter.js 大神的对比
