---
title: "Cocos-JSB"
author: "Perrin Yong"
author_profile: https://www.pystone.net/profile/
published_by: "Perrin Yong"
canonical: https://www.pystone.net/notes/cocos-jsb/
type: note
content_role: unspecified
visibility: public
id_stability: rename-stable
source_path: "10-计算机、信息技术与工程/05-游戏图形与运行时/Cocos/Cocos-JSB.md"
content_hash: b10779ff1a8f0d54ce6dbfb5608454cf7199a6cb09e1f515f62404305001ef54
knowledge_version: 224c990773de.5fa8af6e39fa
site_commit: 224c990773de166d23a886306577dd90379529ce
notes_commit: 5fa8af6e39fa3891d1b9b4832bfa6c4e0ecaaf0a
---
一些问题：
// 在全局注册集中调用
#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](https://docs.cocos.com/creator/3.8/manual/zh/advanced-topics/jsb-auto-binding.html?utm_source=chatgpt.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`。


## 自动绑定


## 准备环境

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` 追加 编译项，以默认项目为例：

```cpp
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++ 头/源（你的原生能力）

```cpp
// 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 的自动绑定流程为例（核心思想：指明要导出的头文件、命名空间、需要生成哪些函数/类）。在你的引擎/项目绑定配置目录中新增类似：

```ini
## 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`）：

```cpp
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 侧调用

```ts
import { native } from 'cc';
const sum = (native as any).mymath.add(3, 5); // 期望 8
```


## 手动绑定（Manual Binding）

## 1) 目录结构（相对项目根）

```xml
<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`

```cpp
#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`

```cpp
#include "MyMath.h"
// 这里目前没特别逻辑，保留源文件便于扩展与编译器增量构建
```

---

## 3) 绑定层（se::Class + SE\_BIND\_\* 宏）

> 头文件路径使用引擎提供的 JS 包装抽象层：`bindings/jswrapper/SeApi.h`

## `native/engine/common/Classes/MyMathBinding.cpp`

```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`

```cpp
#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`

```cpp
#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`

```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()`，按下方方式**补几行**（不要误删你工程里的其他逻辑）：

```cpp
#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}`，请替换即可）：


```cmake
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
)

```

```cmake
## 在现有内容的基础上，追加这几行：
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`

```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`

```ts
declare global {
    // 挂到 globalThis 下的 native 命名空间
    var native: {
        mymath: {
            add(a: number, b: number): number;
            hypot(x: number, y: number): number;
        };
        net: {
            ping(): string;
        };
    };
}
export {};
```
