Cocos-JSB
一些问题: // 在全局注册集中调用 include "bindings/ManualBindings.h" // 举例,放到你的集中注册处 void register all modules() { auto se = se::ScriptEngine::getInstance(); se addRegisterCallback([](se::Object global){ se::Value nativeNS; if (!gl…
本文目录 29 个章节
一些问题: // 在全局注册集中调用 #include "bindings/ManualBindings.h" // 举例,放到你的集中注册处 void register_all_modules() { auto* se = se::ScriptEngine::getInstance(); se->addRegisterCallback([](se::Object* global){ se::Value nativeNS; if (!global->getProperty("native", &nativeNS) || !nativeNS.isObject()) { nativeNS.setObject(se::Object::createPlainObject()); global->setProperty("native", nativeNS); } register_all_mymath(nativeNS.toObject()); }); }
这个集中注册处,应该写在哪里,在什么时机会被调用,把调用该函数的代码写在哪里?
请帮我解释 抽象层、宏与回调签名
SE_BIND_FUNC 的本质和作用是什么
JSB的底层原理是什么(具体到编译器、运行时的原理)
JSB暴露类型个JS的方法是什么
Cocos-JSB
JSB 是什么:把 C++/Objective-C/Java 的原生能力“绑定”为 JS/TS 可调用接口的机制。它让你的脚本像调用普通 TS API 一样调用底层原生实现。
什么时候用 JSB:
频繁、低延迟调用的功能(如音视频、文件/网络 I/O、数学/加解密、AI SDK、复杂物理等);
需要直接复用原生库(C/C++/ObjC/Java)的场景;
避免反射
callStaticMethod的高开销与易错(字符串拼装、JNI 本地引用溢出)等问题。官方也明确建议大量频繁调用尽量走 JSB,性能和稳定性更好。docs.cocos.com
Cocos Creator 3.8 的引擎架构与 JSB 基础设施
双内核 + TS 框架层
- Creator 3.8 有两套内核:C++ 内核(原生平台) 与 TypeScript 内核(Web/小游戏);上层是统一的 TS 引擎框架层,尽量抹平平台差异。
ScriptEngine 抽象层(JSB 2.0 的地基)
- 引擎将具体 JS 引擎(V8 / JavaScriptCore / SpiderMonkey / Chakra 等)的差异封装在
se::ScriptEngine抽象层里;开发者写绑定时用统一的se::类型与宏,不直接面对 V8/JSC 的差异。核心类型如se::ScriptEngine、se::Class、se::Value、se::Object、以及SE_BIND_FUNC等宏。 - 这层设计可独立于引擎使用,位于
cocos/bindings/jswrapper。
自动绑定
准备环境
- 获取匹配 3.8 的 cocos-engine 源码(或使用内置引擎;定制请在“引擎管理器”里切换到自定义路径)。
- 安装引擎
native/external依赖(gulp init或从仓库下载指定 tag 的 zip)。 - 需要 Node.js、Python 及生成工具链(文档会写明具体依赖)。
Native目录生成
确保 native 目录存在 如果项目目录下没有 native 目录需要在编辑器中 Build(生成)一次来创建 native 目录。
复制 assets 和 native 目录到项目目录中
增加 C++编译项
在 <项目名>\native\engine\common\CMakeLists.txt 中为 CC_COMMON_SOURCES 追加 编译项,以默认项目为例:
list(APPEND CC_COMMON_SOURCES
${CMAKE_CURRENT_LIST_DIR}/Classes/Game.h
${CMAKE_CURRENT_LIST_DIR}/Classes/Game.cpp
${CMAKE_CURRENT_LIST_DIR}/Classes/xxx.h
${CMAKE_CURRENT_LIST_DIR}/Classes/xxx.cpp
)
写 C++ 头/源(你的原生能力)
// native/my_math/MyMath.h
#pragma once
class MyMath {
public:
static int add(int a, int b);
};
// native/my_math/MyMath.cpp
#include "MyMath.h"
int MyMath::add(int a, int b) { return a + b; }
编写自动绑定的 .ini 配置
以 Creator 的自动绑定流程为例(核心思想:指明要导出的头文件、命名空间、需要生成哪些函数/类)。在你的引擎/项目绑定配置目录中新增类似:
## mymath.ini(示例)
[cocos-bindings]
classes = MyMath
headers = native/my_math/MyMath.h
## 也可以细化只导出哪些方法
运行绑定生成脚本
- 在引擎的 tojs 工具下执行生成命令(不同版本可能是
tools/tojs/genbindings.py或 gulp 任务):python tools/tojs/genbindings.py -c mymath.ini - 生成的 绑定 C++ 源(通常是
auto/jsb_mymath_auto.cpp一类)会出现在绑定输出目录。
在原生工程中注册模块
在你的模块初始化处(例如自定义 register_all_mymath()),调用生成代码里的注册函数,把类挂到 JS(建议挂到 native 下的自命名空间而非全局 jsb):
bool register_all_mymath(se::Object* ns) {
// 由自动生成代码提供:创建 JS 类、绑定静态方法 add
extern bool js_register_cocos_mymath(se::Object* ns);
return js_register_cocos_mymath(ns);
}
// 在全局注册集中调用
#include "bindings/ManualBindings.h" // 举例,放到你的集中注册处
void register_all_modules() {
auto* se = se::ScriptEngine::getInstance();
se->addRegisterCallback([](se::Object* global){
se::Value nativeNS;
if (!global->getProperty("native", &nativeNS) || !nativeNS.isObject()) {
nativeNS.setObject(se::Object::createPlainObject());
global->setProperty("native", nativeNS);
}
register_all_mymath(nativeNS.toObject());
});
}
构建 / 编译 / 运行
- 构建原生工程:项目 → 构建发布 → 选择平台(Android/iOS/macOS/Windows)→ 构建 → 生成(Make)→ 运行;也可直接用对应 IDE 编译。
- 如果你采用自定义引擎,需在“引擎管理器”切换路径;修改 C++ 后可在 IDE 里直接编译。
TS 侧调用
import { native } from 'cc';
const sum = (native as any).mymath.add(3, 5); // 期望 8
手动绑定(Manual Binding)
1) 目录结构(相对项目根)
<YourProject>/
├─ assets/
│ ├─ scripts/
│ │ └─ NativeDemo.ts
│ └─ @types/ # 可选:给 TS 加类型
│ └─ native-demo.d.ts
└─ native/
└─ engine/
├─ common/
│ ├─ Classes/
│ │ ├─ Bindings.h
│ │ ├─ Bindings.cpp
│ │ ├─ MyMath.h
│ │ ├─ MyMath.cpp
│ │ ├─ MyMathBinding.cpp
│ │ └─ NetBinding.cpp
│ └─ CMakeLists.txt # 需追加几行(见下)
└─ common/Classes/Game.cpp # 已存在;需追加几行(见下)
2) 原生功能(C++)
native/engine/common/Classes/MyMath.h
#pragma once
#include <cmath>
class MyMath {
public:
static int add(int a, int b) { return a + b; }
static double hypot(double x, double y) { return std::hypot(x, y); }
};
native/engine/common/Classes/MyMath.cpp
#include "MyMath.h"
// 这里目前没特别逻辑,保留源文件便于扩展与编译器增量构建
3) 绑定层(se::Class + SE_BIND_* 宏)
头文件路径使用引擎提供的 JS 包装抽象层:
bindings/jswrapper/SeApi.h
native/engine/common/Classes/MyMathBinding.cpp
#include "bindings/jswrapper/SeApi.h"
#include "MyMathBinding.h"
#include "MyMath.h"
using namespace se;
// --- 静态方法:add(a,b): number ---
static bool js_mymath_add(State& s) {
const auto& args = s.args();
int a = (args.size() > 0 && args[0].isNumber()) ? args[0].toInt32() : 0;
int b = (args.size() > 1 && args[1].isNumber()) ? args[1].toInt32() : 0;
s.rval().setInt32(MyMath::add(a, b));
return true;
}
SE_BIND_FUNC(js_mymath_add)
// --- 静态方法:hypot(x,y): number ---
static bool js_mymath_hypot(State& s) {
const auto& args = s.args();
double x = (args.size() > 0 && args[0].isNumber()) ? args[0].toNumber() : 0.0;
double y = (args.size() > 1 && args[1].isNumber()) ? args[1].toNumber() : 0.0;
s.rval().setNumber(MyMath::hypot(x, y));
return true;
}
SE_BIND_FUNC(js_mymath_hypot)
// 将 MyMath 以“纯静态类”形式挂到 global.native.mymath
bool register_all_mymath(se::Object* ns) {
// 定义一个没有 ctor 的类:native.mymath
auto* cls = se::Class::create("mymath", ns, nullptr, nullptr);
// 静态方法
cls->defineStaticFunction("add", _SE(js_mymath_add));
cls->defineStaticFunction("hypot", _SE(js_mymath_hypot));
// 安装类:把 “mymath” 放到传入命名空间 ns(即 native)下
cls->install();
se::ScriptEngine::getInstance()->clearException();
return true;
}
native/engine/common/Classes/MyMathBinding.h
#pragma once
#include "bindings/jswrapper/SeApi.h"
// 把 MyMath 静态方法暴露到 JS 的注册函数
// ns 参数通常是 global.native 对应的 se::Object*
bool register_all_mymath(se::Object* ns);
4) 集中注册处(统一挂到 global.native.*)
native/engine/common/Classes/Bindings.h
#pragma once
#include "bindings/jswrapper/SeApi.h"
#include "MyMathBinding.h" // 引入 MyMath 的注册声明
// 各模块的注册函数
bool register_all_mymath(se::Object* ns);
bool register_all_net(se::Object* ns);
// 总注册:负责准备 global.native 命名空间,并调用各模块注册
bool register_all_modules(se::Object* global);
native/engine/common/Classes/Bindings.cpp
#include "Bindings.h"
bool register_all_modules(se::Object* global) {
// 准备 global.native 命名空间
se::Value nativeNS;
if (!global->getProperty("native", &nativeNS) || !nativeNS.isObject()) {
nativeNS.setObject(se::Object::createPlainObject());
global->setProperty("native", nativeNS);
}
auto* ns = nativeNS.toObject();
bool ok = true;
ok &= register_all_mymath(ns);
ok &= register_all_net(ns);
return ok;
}
5) 在引擎启动时注册(调用时机)
放在
Game.cpp::init():引擎创建 JS 运行时后,会依次执行你登记的 “RegisterCallback” 来把类型/函数挂到 JS 全局。
打开现有的 native/engine/common/Classes/Game.cpp,找到 Game::init(),按下方方式补几行(不要误删你工程里的其他逻辑):
#include "Game.h"
#include "Bindings.h" // <== 新增
int Game::init() {
// 你项目已有的初始化逻辑...
auto* se = se::ScriptEngine::getInstance();
// 【关键】把“总注册”加到回调队列
se->addRegisterCallback([](se::Object* global){
register_all_modules(global); // 我们的总注册函数
});
// 原有:启动/初始化 ScriptEngine、加载脚本等
BaseGame::init();
return 0;
}
注意:不同模板里类名可能叫
AppDelegate/Game/BaseGame,但核心点是不变的——把你的注册函数通过addRegisterCallback丢给ScriptEngine,由引擎在初始化 JS 环境时调用。
6) CMake 配置(把源码编进原生工程)
打开 native/engine/common/CMakeLists.txt(文件已存在)。给目标里追加源码文件(变量名通常为 ${APP_NAME};若模板使用 ${PROJECT_NAME},请替换即可):
list(APPEND CC_COMMON_SOURCES
${CMAKE_CURRENT_LIST_DIR}/Classes/Bindings.cpp
${CMAKE_CURRENT_LIST_DIR}/Classes/Bindings.h
${CMAKE_CURRENT_LIST_DIR}/Classes/MyMath.h
${CMAKE_CURRENT_LIST_DIR}/Classes/MyMath.cpp
${CMAKE_CURRENT_LIST_DIR}/Classes/MyMathBinding.cpp
${CMAKE_CURRENT_LIST_DIR}/Classes/MyMathBinding.h
)
## 在现有内容的基础上,追加这几行:
target_sources(${APP_NAME}
PRIVATE
common/Classes/Bindings.cpp
common/Classes/MyMath.cpp
common/Classes/MyMathBinding.cpp
common/Classes/NetBinding.cpp
)
## 如需(一般不用),也可加头文件目录:
## target_include_directories(${APP_NAME} PRIVATE common/Classes)
保存后回到 Creator:构建 -> 生成(Make);或用对应 IDE(Xcode/Android Studio/VS)编译运行。
7) 脚本侧使用(TS/JS)
assets/scripts/NativeDemo.ts
// 运行于原生平台(Android/iOS/macOS/Windows)
const g: any = globalThis as any;
export function runDemo() {
// 1) 调用 native.mymath
const sum = g.native.mymath.add(3, 5);
const h = g.native.mymath.hypot(3, 4);
console.log('native.mymath.add(3,5)=', sum); // 8
console.log('native.mymath.hypot(3,4)=', h); // 5
// 2) 调用 native.net
console.log('native.net.ping() =', g.native.net.ping()); // "pong"
}
(可选)在某个组件 onLoad() 或按钮回调里调用 runDemo() 即可。
8) (可选)给 TS 补类型声明
assets/@types/native-demo.d.ts
declare global {
// 挂到 globalThis 下的 native 命名空间
var native: {
mymath: {
add(a: number, b: number): number;
hypot(x: number, y: number): number;
};
net: {
ping(): string;
};
};
}
export {};