返回「计算机、信息技术与工程」

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…

更多
Markdown 结构化数据
本文目录 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::ScriptEnginese::Classse::Valuese::Object、以及 SE_BIND_FUNC 等宏。
  • 这层设计可独立于引擎使用,位于 cocos/bindings/jswrapper

自动绑定

准备环境

  1. 获取匹配 3.8 的 cocos-engine 源码(或使用内置引擎;定制请在“引擎管理器”里切换到自定义路径)。
  2. 安装引擎 native/external 依赖(gulp init 或从仓库下载指定 tag 的 zip)。
  3. 需要 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 {};