• JSB 2.0 绑定教程
    • 抽象层
      • 架构
      • 宏(Macro)
    • API
      • CPP 命名空间(namespace)
      • 类型
        • se::ScriptEngine
        • se::Value
        • se::Object
        • se::HandleObject (推荐的管理手动创建对象的辅助类)
        • se::Class
        • se::AutoHandleScope
        • se::State
    • 抽象层依赖 Cocos 引擎么?
    • 手动绑定
      • 回调函数声明
      • 为 JS 对象设置一个属性值
      • 为 JS 对象定义一个属性读写回调
      • 为 JS 对象设置一个函数
      • 注册一个 CPP 类到 JS 虚拟机中
      • 如何绑定 CPP 接口中的回调函数?
      • 如何使用 cocos2d-x bindings 这层的类型转换辅助函数?
        • se::Value 转换为 C++ 类型
        • C++ 类型转换为 se::Value
        • (IMPORTANT)理解 native_ptr_to_seval 与 native_ptr_to_rooted_seval 的区别
    • 自动绑定
      • 配置模块 ini 文件
      • 理解 ini 文件中每个字段的意义
    • 远程调试与 Profile
      • Chrome 远程调试 V8
        • Windows
        • Android
      • Safari 远程调试 JavaScriptCore
        • macOS
        • iOS
    • Q & A
      • se::ScriptEngine 与 ScriptingCore 的区别,为什么还要保留 ScriptingCore?
      • se::Object::root/unroot 与 se::Object::incRef/decRef 的区别?
      • 对象生命周期的关联与解除关联
      • cocos2d::Ref 子类与非 cocos2d::Ref 子类 JS/CPP 对象生命周期管理有何不同?
      • 绑定 cocos2d::Ref 子类的析构函数需要注意的事项
      • 请不要在栈(Stack)上分配 cocos2d::Ref 的子类对象
      • 如何监听脚本错误

    JSB 2.0 绑定教程

    抽象层

    架构

     JSB 2.0 使用指南  - 图1

    宏(Macro)

    抽象层必然会比直接使用 JS 引擎 API 的方式多占用一些 CPU 执行时间,如何把抽象层本身的开销降到最低成为设计的第一目标。

    JS 绑定的大部分工作其实就是设定 JS 相关操作的 CPP 回调,在回调函数中关联 CPP 对象。其实主要包含如下两种类型:

    • 注册 JS 函数(包含全局函数,类构造函数、类析构函数、类成员函数,类静态成员函数),绑定一个 CPP 回调
    • 注册 JS 对象的属性读写访问器,分别绑定读与写的 CPP 回调
      如何做到抽象层开销最小而且暴露统一的 API 供上层使用?

    以注册 JS 函数的回调定义为例,JavaScriptCore,SpiderMonkey,V8,ChakraCore 的定义各不相同,具体如下:

    JavaScriptCore:

    1. JSValueRef JSB_foo_func(
    2. JSContextRef _cx,
    3. JSObjectRef _function,
    4. JSObjectRef _thisObject,
    5. size_t argc,
    6. const JSValueRef _argv[],
    7. JSValueRef* _exception
    8. );

    SpiderMonkey:

    1. bool JSB_foo_func(
    2. JSContext* _cx,
    3. unsigned argc,
    4. JS::Value* _vp
    5. );

    V8:

    1. void JSB_foo_func(
    2. const v8::FunctionCallbackInfo<v8::Value>& v8args
    3. );

    ChakraCore:

    1. JsValueRef JSB_foo_func(
    2. JsValueRef _callee,
    3. bool _isConstructCall,
    4. JsValueRef* _argv,
    5. unsigned short argc,
    6. void* _callbackState
    7. );

    我们评估了几种方案,最终确定使用来抹平不同 JS 引擎回调函数定义与参数类型的不同,不管底层是使用什么引擎,开发者统一使用一种回调函数的定义。我们借鉴了 lua 的回调函数定义方式,抽象层所有的 JS 到 CPP 的回调函数的定义为:

    1. bool foo(se::State& s)
    2. {
    3. ...
    4. ...
    5. }
    6. SE_BIND_FUNC(foo) // 此处以回调函数的定义为例

    开发者编写完回调函数后,记住使用 SE_BIND_XXX 系列的宏对回调函数进行包装。目前提供了如下几个宏:

    • SE_BIND_PROP_GET:包装一个 JS 对象属性读取的回调函数
    • SE_BIND_PROP_SET:包装一个 JS 对象属性写入的回调函数
    • SE_BIND_FUNC:包装一个 JS 函数,可用于全局函数、类成员函数、类静态函数
    • SE_DECLARE_FUNC:声明一个 JS 函数,一般在 .h 头文件中使用
    • SE_BIND_CTOR:包装一个 JS 构造函数
    • SE_BIND_SUB_CLS_CTOR:包装一个 JS 子类的构造函数,此子类使用 cc.Class.extend 继承 Native 绑定类
    • SE_FINALIZE_FUNC:包装一个 JS 对象被 GC 回收后的回调函数
    • SE_DECLARE_FINALIZE_FUNC:声明一个 JS 对象被 GC 回收后的回调函数
    • _SE:包装回调函数的名称,转义为每个 JS 引擎能够识别的回调函数的定义,注意,第一个字符为下划线,类似 Windows 下用的_T("xxx")来包装 Unicode 或者 MultiBytes 字符串

    API

    CPP 命名空间(namespace)

    CPP 抽象层所有的类型都在 se 命名空间下,其为 ScriptEngine 的缩写。

    类型

    se::ScriptEngine

    se::ScriptEngine 为 JS 引擎的管理员,掌管 JS 引擎初始化、销毁、重启、Native 模块注册、加载脚本、强制垃圾回收、JS 异常清理、是否启用调试器。它是一个单例,可通过 se::ScriptEngine::getInstance() 得到对应的实例。

    se::Value

    se::Value 可以被理解为 JS 变量在 CPP 层的引用。JS 变量有 object, number, string, boolean, null, undefined 六种类型,因此 se::Value 使用 union 包含 object, number, string, boolean4 种有值类型无值类型: null, undefined 可由 _type 直接表示。

    1. namespace se {
    2. class Value {
    3. enum class Type : char
    4. {
    5. Undefined = 0,
    6. Null,
    7. Number,
    8. Boolean,
    9. String,
    10. Object
    11. };
    12. ...
    13. ...
    14. private:
    15. union {
    16. bool _boolean;
    17. double _number;
    18. std::string* _string;
    19. Object* _object;
    20. } _u;
    21. Type _type;
    22. ...
    23. ...
    24. };
    25. }

    如果 se::Value 中保存基础数据类型,比如 numberstringboolean,其内部是直接存储一份值副本。object 的存储比较特殊,是通过 se::Object* 对 JS 对象的弱引用(weak reference)。

    se::Object

    se::Object 继承于 se::RefCounter 引用计数管理类。目前抽象层中只有 se::Object 继承于 se::RefCounter。上一小节我们说到,se::Object 是保存了对 JS 对象的弱引用,这里笔者有必要解释一下为什么是弱引用。

    原因一:JS 对象控制 CPP 对象的生命周期的需要

    当在脚本层中通过 var sp = new cc.Sprite("a.png"); 创建了一个 Sprite 后,在构造回调函数绑定中我们会创建一个 se::Object 并保留在一个全局的 map (NativePtrToObjectMap) 中,此 map 用于查询 cocos2d::Sprite 指针获取对应的 JS 对象 se::Object

    1. static bool js_cocos2d_Sprite_finalize(se::State& s)
    2. {
    3. CCLOG("jsbindings: finalizing JS object %p (cocos2d::Sprite)", s.nativeThisObject());
    4. cocos2d::Sprite* cobj = (cocos2d::Sprite*)s.nativeThisObject();
    5. if (cobj->getReferenceCount() == 1)
    6. cobj->autorelease();
    7. else
    8. cobj->release();
    9. return true;
    10. }
    11. SE_BIND_FINALIZE_FUNC(js_cocos2d_Sprite_finalize)
    12. static bool js_cocos2dx_Sprite_constructor(se::State& s)
    13. {
    14. cocos2d::Sprite* cobj = new (std::nothrow) cocos2d::Sprite(); // cobj 将在 finalize 函数中被释放
    15. s.thisObject()->setPrivateData(cobj); // setPrivateData 内部会去保存 cobj 到 NativePtrToObjectMap 中
    16. return true;
    17. }
    18. SE_BIND_CTOR(js_cocos2dx_Sprite_constructor, __jsb_cocos2d_Sprite_class, js_cocos2d_Sprite_finalize)

    设想如果强制要求 se::Object 为 JS 对象的强引用(strong reference),即让 JS 对象不受 GC 控制,由于 se::Object 一直存在于 map 中,finalize 回调将永远无法被触发,从而导致内存泄露。

    正是由于 se::Object 保存的是 JS 对象的弱引用,JS 对象控制 CPP 对象的生命周期才能够实现。以上代码中,当 JS 对象被释放后,会触发 finalize 回调,开发者只需要在 js_cocos2d_Sprite_finalize 中释放对应的 c++ 对象即可,se::Object 的释放已经被包含在 SE_BIND_FINALIZE_FUNC 宏中自动处理,开发者无需管理在JS 对象控制 CPP 对象模式中 se::Object 的释放,但是在 CPP 对象控制 JS 对象 模式中,开发者需要管理对 se::Object 的释放,具体下一节中会举例说明。

    原因二:更加灵活,手动调用 root 方法以支持强引用

    se::Object 中提供了 root/unroot 方法供开发者调用,root 会把 JS 对象放入到不受 GC 扫描到的区域,调用 root 后,se::Object 就强引用了 JS 对象,只有当 unroot 被调用,或者 se::Object 被释放后,JS 对象才会放回到受 GC 扫描到的区域。

    一般情况下,如果对象是非 cocos2d::Ref 的子类,会采用 CPP 对象控制 JS 对象的生命周期的方式去绑定。引擎内 spine, dragonbones, box2d 等第三方库的绑定就是采用此方式。当 CPP 对象被释放的时候,需要在 NativePtrToObjectMap 中查找对应的 se::Object,然后手动 unroot 和 decRef。以 spine 中 spTrackEntry 的绑定为例:

    1. spTrackEntry_setDisposeCallback([](spTrackEntry* entry){
    2. // spTrackEntry 的销毁回调
    3. se::Object* seObj = nullptr;
    4. auto iter = se::NativePtrToObjectMap::find(entry);
    5. if (iter != se::NativePtrToObjectMap::end())
    6. {
    7. // 保存 se::Object 指针,用于在下面的 cleanup 函数中释放其内存
    8. seObj = iter->second;
    9. // Native 对象 entry 的内存已经被释放,因此需要立马解除 Native 对象与 JS 对象的关联。
    10. // 如果解除引用关系放在下面的 cleanup 函数中处理,有可能触发 se::Object::setPrivateData 中
    11. // 的断言,因为新生成的 Native 对象的地址可能与当前对象相同,而 cleanup 可能被延迟到帧结束前执行。
    12. se::NativePtrToObjectMap::erase(iter);
    13. }
    14. else
    15. {
    16. return;
    17. }
    18. auto cleanup = [seObj](){
    19. auto se = se::ScriptEngine::getInstance();
    20. if (!se->isValid() || se->isInCleanup())
    21. return;
    22. se::AutoHandleScope hs;
    23. se->clearException();
    24. // 由于上面逻辑已经把映射关系解除了,这里传入 false 表示不用再次解除映射关系,
    25. // 因为当前 seObj 的 private data 可能已经是另外一个不同的对象
    26. seObj->clearPrivateData(false);
    27. seObj->unroot(); // unroot,使 JS 对象受 GC 管理
    28. seObj->decRef(); // 释放 se::Object
    29. };
    30. // 确保不再垃圾回收中去操作 JS 引擎的 API
    31. if (!se::ScriptEngine::getInstance()->isGarbageCollecting())
    32. {
    33. cleanup();
    34. }
    35. else
    36. { // 如果在垃圾回收,把清理任务放在帧结束中进行
    37. CleanupTask::pushTaskToAutoReleasePool(cleanup);
    38. }
    39. });

    对象类型

    绑定对象的创建已经被隐藏在对应的 SE_BIND_CTORSE_BIND_SUB_CLS_CTOR 函数中,开发者在绑定回调中如果需要用到当前对象对应的 se::Object,只需要通过 s.thisObject() 即可获取。其中 s 为 se::State 类型,具体会在后续章节中说明。

    此外,se::Object 目前支持以下几种对象的手动创建:

    • Plain Object : 通过 se::Object::createPlainObject 创建,类似 JS 中的 var a = {};
    • Array Object : 通过 se::Object::createArrayObject 创建,类似 JS 中的 var a = [];
    • Uint8 Typed Array Object : 通过 se::Object::createTypedArray 创建,类似 JS 中的 var a = new Uint8Array(buffer);
    • Array Buffer Object : 通过 se::Object::createArrayBufferObject,类似 JS 中的 var a = new ArrayBuffer(len);
      手动创建对象的释放

    se::Object::createXXX 方法与 cocos2d-x 中的 create 方法不同,抽象层是完全独立的一个模块,并不依赖与 cocos2d-x 的 autorelease 机制。虽然 se::Object 也是继承引用计数类,但开发者需要处理手动创建出来的对象的释放。

    1. se::Object* obj = se::Object::createPlainObject();
    2. ...
    3. ...
    4. obj->decRef(); // 释放引用,避免内存泄露

    se::HandleObject (推荐的管理手动创建对象的辅助类)

    • 在比较复杂的逻辑中使用手动创建对象,开发者往往会忘记在不同的逻辑中处理 decRef
    1. bool foo()
    2. {
    3. se::Object* obj = se::Object::createPlainObject();
    4. if (var1)
    5. return false; // 这里直接返回了,忘记做 decRef 释放操作
    6. if (var2)
    7. return false; // 这里直接返回了,忘记做 decRef 释放操作
    8. ...
    9. ...
    10. obj->decRef();
    11. return true;
    12. }

    就算在不同的返回条件分支中加上了 decRef 也会导致逻辑复杂,难以维护,如果后期加入另外一个返回分支,很容易忘记 decRef。

    • JS 引擎在 se::Object::createXXX 后,如果由于某种原因 JS 引擎做了 GC 操作,导致后续使用的 se::Object 内部引用了一个非法指针,引发程序崩溃
      为了解决上述两个问题,抽象层定义了一个辅助管理手动创建对象的类型,即 se::HandleObject

    se::HandleObject 是一个辅助类,用于更加简单地管理手动创建的 se::Object 对象的释放、root 和 unroot 操作。以下两种代码写法是等价的,使用 se::HandleObject 的代码量明显少很多,而且更加安全。

    1. {
    2. se::HandleObject obj(se::Object::createPlainObject());
    3. obj->setProperty(...);
    4. otherObject->setProperty("foo", se::Value(obj));
    5. }
    6. 等价于:
    7. {
    8. se::Object* obj = se::Object::createPlainObject();
    9. obj->root(); // 在手动创建完对象后立马 root,防止对象被 GC
    10. obj->setProperty(...);
    11. otherObject->setProperty("foo", se::Value(obj));
    12. obj->unroot(); // 当对象被使用完后,调用 unroot
    13. obj->decRef(); // 引用计数减一,避免内存泄露
    14. }

    注意:

    • 不要尝试使用 se::HandleObject 创建一个 native 与 JS 的绑定对象,在 JS 控制 CPP 的模式中,绑定对象的释放会被抽象层自动处理,在 CPP 控制 JS 的模式中,前一章节中已经有描述了。
    • se::HandleObject 对象只能够在栈上被分配,而且栈上构造的时候必须传入一个 se::Object 指针。

    se::Class

    se::Class 用于暴露 CPP 类到 JS 中,它会在 JS 中创建一个对应名称的 constructor function。

    它有如下方法:

    • static se::Class* create(className, obj, parentProto, ctor): 创建一个 Class,注册成功后,在 JS 层中可以通过var xxx = new SomeClass();的方式创建一个对象
    • bool defineFunction(name, func): 定义 Class 中的成员函数
    • bool defineProperty(name, getter, setter): 定义 Class 属性读写器
    • bool defineStaticFunction(name, func): 定义 Class 的静态成员函数,可通过 SomeClass.foo() 这种非 new 的方式访问,与类实例对象无关
    • bool defineStaticProperty(name, getter, setter): 定义 Class 的静态属性读写器,可通过 SomeClass.propertyA 直接读写,与类实例对象无关
    • bool defineFinalizeFunction(func): 定义 JS 对象被 GC 后的 CPP 回调
    • bool install(): 注册此类到 JS 虚拟机中
    • Object* getProto(): 获取注册到 JS 中的类(其实是 JS 的 constructor)的 prototype 对象,类似 function Foo(){}的 Foo.prototype
    • const char getName() const: 获取当前 Class 的名称
      *注意:

    Class 类型创建后,不需要手动释放内存,它会被封装层自动处理。

    更具体 API 说明可以翻看 API 文档或者代码注释

    se::AutoHandleScope

    se::AutoHandleScope 对象类型完全是为了解决 V8 的兼容问题而引入的概念。V8 中,当有 CPP 函数中需要触发 JS 相关操作,比如调用 JS 函数,访问 JS 属性等任何调用 v8::Local<> 的操作,V8 强制要求在调用这些操作前必须存在一个 v8::HandleScope 作用域,否则会引发程序崩溃。

    因此抽象层中引入了 se::AutoHandleScope 的概念,其只在 V8 上有实现,其他 JS 引擎目前都只是空实现。

    开发者需要记住,在任何代码执行中,需要调用 JS 的逻辑前,声明一个 se::AutoHandleScope 即可,比如:

    1. class SomeClass {
    2. void update(float dt) {
    3. se::ScriptEngine::getInstance()->clearException();
    4. se::AutoHandleScope hs;
    5. se::Object* obj = ...;
    6. obj->setProperty(...);
    7. ...
    8. ...
    9. obj->call(...);
    10. }
    11. };

    se::State

    之前章节我们有提及 State 类型,它是绑定回调中的一个环境,我们通过 se::State 可以取得当前的 CPP 指针、se::Object 对象指针、参数列表、返回值引用。

    1. bool foo(se::State& s)
    2. {
    3. // 获取 native 对象指针
    4. SomeClass* cobj = (SomeClass*)s.nativeThisObject();
    5. // 获取 se::Object 对象指针
    6. se::Object* thisObject = s.thisObject();
    7. // 获取参数列表
    8. const se::ValueArray& args = s.args();
    9. // 设置返回值
    10. s.rval().setInt32(100);
    11. return true;
    12. }
    13. SE_BIND_FUNC(foo)

    抽象层依赖 Cocos 引擎么?

    不依赖。

    ScriptEngine 这层设计之初就将其定义为一个独立模块,完全不依赖 Cocos 引擎。开发者完整可以通过 copy、paste 把 cocos/scripting/js-bindings/jswrapper 下的所有抽象层源码拷贝到其他项目中直接使用。

    手动绑定

    回调函数声明

    1. static bool Foo_balabala(se::State& s)
    2. {
    3. const auto& args = s.args();
    4. int argc = (int)args.size();
    5. if (argc >= 2) // 这里约定参数个数必须大于等于 2,否则抛出错误到 JS 层且返回 false
    6. {
    7. ...
    8. ...
    9. return true;
    10. }
    11. SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", argc, 2);
    12. return false;
    13. }
    14. // 如果是绑定函数,则用 SE_BIND_FUNC,构造函数、析构函数、子类构造函数等类似
    15. SE_BIND_FUNC(Foo_balabala)

    为 JS 对象设置一个属性值

    1. se::Object* globalObj = se::ScriptEngine::getInstance()->getGlobalObject(); // 这里为了演示方便,获取全局对象
    2. globalObj->setProperty("foo", se::Value(100)); // 给全局对象设置一个 foo 属性,值为 100

    在 JS 中就可以直接使用 foo 这个全局变量了

    1. cc.log("foo value: " + foo); // 打印出 foo value: 100

    为 JS 对象定义一个属性读写回调

    1. // 全局对象的 foo 属性的读回调
    2. static bool Global_get_foo(se::State& s)
    3. {
    4. NativeObj* cobj = (NativeObj*)s.nativeThisObject();
    5. int32_t ret = cobj->getValue();
    6. s.rval().setInt32(ret);
    7. return true;
    8. }
    9. SE_BIND_PROP_GET(Global_get_foo)
    10. // 全局对象的 foo 属性的写回调
    11. static bool Global_set_foo(se::State& s)
    12. {
    13. const auto& args = s.args();
    14. int argc = (int)args.size();
    15. if (argc >= 1)
    16. {
    17. NativeObj* cobj = (NativeObj*)s.nativeThisObject();
    18. int32_t arg1 = args[0].toInt32();
    19. cobj->setValue(arg1);
    20. // void 类型的函数,无需设置 s.rval,未设置默认返回 undefined 给 JS 层
    21. return true;
    22. }
    23. SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", argc, 1);
    24. return false;
    25. }
    26. SE_BIND_PROP_SET(Global_set_foo)
    27. void some_func()
    28. {
    29. se::Object* globalObj = se::ScriptEngine::getInstance()->getGlobalObject(); // 这里为了演示方便,获取全局对象
    30. globalObj->defineProperty("foo", _SE(Global_get_foo), _SE(Global_set_foo)); // 使用_SE 宏包装一下具体的函数名称
    31. }

    为 JS 对象设置一个函数

    1. static bool Foo_function(se::State& s)
    2. {
    3. ...
    4. ...
    5. }
    6. SE_BIND_FUNC(Foo_function)
    7. void some_func()
    8. {
    9. se::Object* globalObj = se::ScriptEngine::getInstance()->getGlobalObject(); // 这里为了演示方便,获取全局对象
    10. globalObj->defineFunction("foo", _SE(Foo_function)); // 使用_SE 宏包装一下具体的函数名称
    11. }

    注册一个 CPP 类到 JS 虚拟机中

    1. static se::Object* __jsb_ns_SomeClass_proto = nullptr;
    2. static se::Class* __jsb_ns_SomeClass_class = nullptr;
    3. namespace ns {
    4. class SomeClass
    5. {
    6. public:
    7. SomeClass()
    8. : xxx(0)
    9. {}
    10. void foo() {
    11. printf("SomeClass::foo\n");
    12. Director::getInstance()->getScheduler()->schedule([this](float dt){
    13. static int counter = 0;
    14. ++counter;
    15. if (_cb != nullptr)
    16. _cb(counter);
    17. }, this, 1.0f, CC_REPEAT_FOREVER, 0.0f, false, "iamkey");
    18. }
    19. static void static_func() {
    20. printf("SomeClass::static_func\n");
    21. }
    22. void setCallback(const std::function<void(int)>& cb) {
    23. _cb = cb;
    24. if (_cb != nullptr)
    25. {
    26. printf("setCallback(cb)\n");
    27. }
    28. else
    29. {
    30. printf("setCallback(nullptr)\n");
    31. }
    32. }
    33. int xxx;
    34. private:
    35. std::function<void(int)> _cb;
    36. };
    37. } // namespace ns {
    38. static bool js_SomeClass_finalize(se::State& s)
    39. {
    40. ns::SomeClass* cobj = (ns::SomeClass*)s.nativeThisObject();
    41. delete cobj;
    42. return true;
    43. }
    44. SE_BIND_FINALIZE_FUNC(js_SomeClass_finalize)
    45. static bool js_SomeClass_constructor(se::State& s)
    46. {
    47. ns::SomeClass* cobj = new ns::SomeClass();
    48. s.thisObject()->setPrivateData(cobj);
    49. return true;
    50. }
    51. SE_BIND_CTOR(js_SomeClass_constructor, __jsb_ns_SomeClass_class, js_SomeClass_finalize)
    52. static bool js_SomeClass_foo(se::State& s)
    53. {
    54. ns::SomeClass* cobj = (ns::SomeClass*)s.nativeThisObject();
    55. cobj->foo();
    56. return true;
    57. }
    58. SE_BIND_FUNC(js_SomeClass_foo)
    59. static bool js_SomeClass_get_xxx(se::State& s)
    60. {
    61. ns::SomeClass* cobj = (ns::SomeClass*)s.nativeThisObject();
    62. s.rval().setInt32(cobj->xxx);
    63. return true;
    64. }
    65. SE_BIND_PROP_GET(js_SomeClass_get_xxx)
    66. static bool js_SomeClass_set_xxx(se::State& s)
    67. {
    68. const auto& args = s.args();
    69. int argc = (int)args.size();
    70. if (argc > 0)
    71. {
    72. ns::SomeClass* cobj = (ns::SomeClass*)s.nativeThisObject();
    73. cobj->xxx = args[0].toInt32();
    74. return true;
    75. }
    76. SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", argc, 1);
    77. return false;
    78. }
    79. SE_BIND_PROP_SET(js_SomeClass_set_xxx)
    80. static bool js_SomeClass_static_func(se::State& s)
    81. {
    82. ns::SomeClass::static_func();
    83. return true;
    84. }
    85. SE_BIND_FUNC(js_SomeClass_static_func)
    86. bool js_register_ns_SomeClass(se::Object* global)
    87. {
    88. // 保证 namespace 对象存在
    89. se::Value nsVal;
    90. if (!global->getProperty("ns", &nsVal))
    91. {
    92. // 不存在则创建一个 JS 对象,相当于 var ns = {};
    93. se::HandleObject jsobj(se::Object::createPlainObject());
    94. nsVal.setObject(jsobj);
    95. // 将 ns 对象挂载到 global 对象中,名称为 ns
    96. global->setProperty("ns", nsVal);
    97. }
    98. se::Object* ns = nsVal.toObject();
    99. // 创建一个 Class 对象,开发者无需考虑 Class 对象的释放,其交由 ScriptEngine 内部自动处理
    100. auto cls = se::Class::create("SomeClass", ns, nullptr, _SE(js_SomeClass_constructor)); // 如果无构造函数,最后一个参数可传入 nullptr,则这个类在 JS 中无法被 new SomeClass()出来
    101. // 为这个 Class 对象定义成员函数、属性、静态函数、析构函数
    102. cls->defineFunction("foo", _SE(js_SomeClass_foo));
    103. cls->defineProperty("xxx", _SE(js_SomeClass_get_xxx), _SE(js_SomeClass_set_xxx));
    104. cls->defineFinalizeFunction(_SE(js_SomeClass_finalize));
    105. // 注册类型到 JS VirtualMachine 的操作
    106. cls->install();
    107. // JSBClassType 为 Cocos 引擎绑定层封装的类型注册的辅助函数,此函数不属于 ScriptEngine 这层
    108. JSBClassType::registerClass<ns::SomeClass>(cls);
    109. // 保存注册的结果,便于其他地方使用,比如类继承
    110. __jsb_ns_SomeClass_proto = cls->getProto();
    111. __jsb_ns_SomeClass_class = cls;
    112. // 为每个此 Class 实例化出来的对象附加一个属性
    113. __jsb_ns_SomeClass_proto->setProperty("yyy", se::Value("helloyyy"));
    114. // 注册静态成员变量和静态成员函数
    115. se::Value ctorVal;
    116. if (ns->getProperty("SomeClass", &ctorVal) && ctorVal.isObject())
    117. {
    118. ctorVal.toObject()->setProperty("static_val", se::Value(200));
    119. ctorVal.toObject()->defineFunction("static_func", _SE(js_SomeClass_static_func));
    120. }
    121. // 清空异常
    122. se::ScriptEngine::getInstance()->clearException();
    123. return true;
    124. }

    如何绑定 CPP 接口中的回调函数?

    1. static bool js_SomeClass_setCallback(se::State& s)
    2. {
    3. const auto& args = s.args();
    4. int argc = (int)args.size();
    5. if (argc >= 1)
    6. {
    7. ns::SomeClass* cobj = (ns::SomeClass*)s.nativeThisObject();
    8. se::Value jsFunc = args[0];
    9. se::Value jsTarget = argc > 1 ? args[1] : se::Value::Undefined;
    10. if (jsFunc.isNullOrUndefined())
    11. {
    12. cobj->setCallback(nullptr);
    13. }
    14. else
    15. {
    16. assert(jsFunc.isObject() && jsFunc.toObject()->isFunction());
    17. // 如果当前 SomeClass 是可以被 new 出来的类,我们 使用 se::Object::attachObject 把 jsFunc 和 jsTarget 关联到当前对象中
    18. s.thisObject()->attachObject(jsFunc.toObject());
    19. s.thisObject()->attachObject(jsTarget.toObject());
    20. // 如果当前 SomeClass 类是一个单例类,或者永远只有一个实例的类,我们不能用 se::Object::attachObject 去关联
    21. // 必须使用 se::Object::root,开发者无需关系 unroot,unroot 的操作会随着 lambda 的销毁触发 jsFunc 的析构,在 se::Object 的析构函数中进行 unroot 操作。
    22. // js_cocos2dx_EventDispatcher_addCustomEventListener 的绑定代码就是使用此方式,因为 EventDispatcher 始终只有一个实例,
    23. // 如果使用 s.thisObject->attachObject(jsFunc.toObject);会导致对应的 func 和 target 永远无法被释放,引发内存泄露。
    24. // jsFunc.toObject()->root();
    25. // jsTarget.toObject()->root();
    26. cobj->setCallback([jsFunc, jsTarget](int counter){
    27. // CPP 回调函数中要传递数据给 JS 或者调用 JS 函数,在回调函数开始需要添加如下两行代码。
    28. se::ScriptEngine::getInstance()->clearException();
    29. se::AutoHandleScope hs;
    30. se::ValueArray args;
    31. args.push_back(se::Value(counter));
    32. se::Object* target = jsTarget.isObject() ? jsTarget.toObject() : nullptr;
    33. jsFunc.toObject()->call(args, target);
    34. });
    35. }
    36. return true;
    37. }
    38. SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", argc, 1);
    39. return false;
    40. }
    41. SE_BIND_FUNC(js_SomeClass_setCallback)

    SomeClass 类注册后,就可以在 JS 中这样使用了:

    1. var myObj = new ns.SomeClass();
    2. myObj.foo();
    3. ns.SomeClass.static_func();
    4. cc.log("ns.SomeClass.static_val: " + ns.SomeClass.static_val);
    5. cc.log("Old myObj.xxx:" + myObj.xxx);
    6. myObj.xxx = 1234;
    7. cc.log("New myObj.xxx:" + myObj.xxx);
    8. cc.log("myObj.yyy: " + myObj.yyy);
    9. var delegateObj = {
    10. onCallback: function(counter) {
    11. cc.log("Delegate obj, onCallback: " + counter + ", this.myVar: " + this.myVar);
    12. this.setVar();
    13. },
    14. setVar: function() {
    15. this.myVar++;
    16. },
    17. myVar: 100
    18. };
    19. myObj.setCallback(delegateObj.onCallback, delegateObj);
    20. setTimeout(function(){
    21. myObj.setCallback(null);
    22. }, 6000); // 6 秒后清空 callback

    Console 中会输出:

    1. SomeClass::foo
    2. SomeClass::static_func
    3. ns.SomeClass.static_val: 200
    4. Old myObj.xxx:0
    5. New myObj.xxx:1234
    6. myObj.yyy: helloyyy
    7. setCallback(cb)
    8. Delegate obj, onCallback: 1, this.myVar: 100
    9. Delegate obj, onCallback: 2, this.myVar: 101
    10. Delegate obj, onCallback: 3, this.myVar: 102
    11. Delegate obj, onCallback: 4, this.myVar: 103
    12. Delegate obj, onCallback: 5, this.myVar: 104
    13. Delegate obj, onCallback: 6, this.myVar: 105
    14. setCallback(nullptr)

    如何使用 cocos2d-x bindings 这层的类型转换辅助函数?

    类型转换辅助函数位于cocos/scripting/js-bindings/manual/jsb_conversions.hpp/.cpp中,其包含:

    se::Value 转换为 C++ 类型

    1. bool seval_to_int32(const se::Value& v, int32_t* ret);
    2. bool seval_to_uint32(const se::Value& v, uint32_t* ret);
    3. bool seval_to_int8(const se::Value& v, int8_t* ret);
    4. bool seval_to_uint8(const se::Value& v, uint8_t* ret);
    5. bool seval_to_int16(const se::Value& v, int16_t* ret);
    6. bool seval_to_uint16(const se::Value& v, uint16_t* ret);
    7. bool seval_to_boolean(const se::Value& v, bool* ret);
    8. bool seval_to_float(const se::Value& v, float* ret);
    9. bool seval_to_double(const se::Value& v, double* ret);
    10. bool seval_to_long(const se::Value& v, long* ret);
    11. bool seval_to_ulong(const se::Value& v, unsigned long* ret);
    12. bool seval_to_longlong(const se::Value& v, long long* ret);
    13. bool seval_to_ssize(const se::Value& v, ssize_t* ret);
    14. bool seval_to_std_string(const se::Value& v, std::string* ret);
    15. bool seval_to_Vec2(const se::Value& v, cocos2d::Vec2* pt);
    16. bool seval_to_Vec3(const se::Value& v, cocos2d::Vec3* pt);
    17. bool seval_to_Vec4(const se::Value& v, cocos2d::Vec4* pt);
    18. bool seval_to_Mat4(const se::Value& v, cocos2d::Mat4* mat);
    19. bool seval_to_Size(const se::Value& v, cocos2d::Size* size);
    20. bool seval_to_Rect(const se::Value& v, cocos2d::Rect* rect);
    21. bool seval_to_Color3B(const se::Value& v, cocos2d::Color3B* color);
    22. bool seval_to_Color4B(const se::Value& v, cocos2d::Color4B* color);
    23. bool seval_to_Color4F(const se::Value& v, cocos2d::Color4F* color);
    24. bool seval_to_ccvalue(const se::Value& v, cocos2d::Value* ret);
    25. bool seval_to_ccvaluemap(const se::Value& v, cocos2d::ValueMap* ret);
    26. bool seval_to_ccvaluemapintkey(const se::Value& v, cocos2d::ValueMapIntKey* ret);
    27. bool seval_to_ccvaluevector(const se::Value& v, cocos2d::ValueVector* ret);
    28. bool sevals_variadic_to_ccvaluevector(const se::ValueArray& args, cocos2d::ValueVector* ret);
    29. bool seval_to_blendfunc(const se::Value& v, cocos2d::BlendFunc* ret);
    30. bool seval_to_std_vector_string(const se::Value& v, std::vector<std::string>* ret);
    31. bool seval_to_std_vector_int(const se::Value& v, std::vector<int>* ret);
    32. bool seval_to_std_vector_float(const se::Value& v, std::vector<float>* ret);
    33. bool seval_to_std_vector_Vec2(const se::Value& v, std::vector<cocos2d::Vec2>* ret);
    34. bool seval_to_std_vector_Touch(const se::Value& v, std::vector<cocos2d::Touch*>* ret);
    35. bool seval_to_std_map_string_string(const se::Value& v, std::map<std::string, std::string>* ret);
    36. bool seval_to_FontDefinition(const se::Value& v, cocos2d::FontDefinition* ret);
    37. bool seval_to_Acceleration(const se::Value& v, cocos2d::Acceleration* ret);
    38. bool seval_to_Quaternion(const se::Value& v, cocos2d::Quaternion* ret);
    39. bool seval_to_AffineTransform(const se::Value& v, cocos2d::AffineTransform* ret);
    40. //bool seval_to_Viewport(const se::Value& v, cocos2d::experimental::Viewport* ret);
    41. bool seval_to_Data(const se::Value& v, cocos2d::Data* ret);
    42. bool seval_to_DownloaderHints(const se::Value& v, cocos2d::network::DownloaderHints* ret);
    43. bool seval_to_TTFConfig(const se::Value& v, cocos2d::TTFConfig* ret);
    44. //box2d seval to native convertion
    45. bool seval_to_b2Vec2(const se::Value& v, b2Vec2* ret);
    46. bool seval_to_b2AABB(const se::Value& v, b2AABB* ret);
    47. template<typename T>
    48. bool seval_to_native_ptr(const se::Value& v, T* ret);
    49. template<typename T>
    50. bool seval_to_Vector(const se::Value& v, cocos2d::Vector<T>* ret);
    51. template<typename T>
    52. bool seval_to_Map_string_key(const se::Value& v, cocos2d::Map<std::string, T>* ret)

    C++ 类型转换为 se::Value

    1. bool int8_to_seval(int8_t v, se::Value* ret);
    2. bool uint8_to_seval(uint8_t v, se::Value* ret);
    3. bool int32_to_seval(int32_t v, se::Value* ret);
    4. bool uint32_to_seval(uint32_t v, se::Value* ret);
    5. bool int16_to_seval(uint16_t v, se::Value* ret);
    6. bool uint16_to_seval(uint16_t v, se::Value* ret);
    7. bool boolean_to_seval(bool v, se::Value* ret);
    8. bool float_to_seval(float v, se::Value* ret);
    9. bool double_to_seval(double v, se::Value* ret);
    10. bool long_to_seval(long v, se::Value* ret);
    11. bool ulong_to_seval(unsigned long v, se::Value* ret);
    12. bool longlong_to_seval(long long v, se::Value* ret);
    13. bool ssize_to_seval(ssize_t v, se::Value* ret);
    14. bool std_string_to_seval(const std::string& v, se::Value* ret);
    15. bool Vec2_to_seval(const cocos2d::Vec2& v, se::Value* ret);
    16. bool Vec3_to_seval(const cocos2d::Vec3& v, se::Value* ret);
    17. bool Vec4_to_seval(const cocos2d::Vec4& v, se::Value* ret);
    18. bool Mat4_to_seval(const cocos2d::Mat4& v, se::Value* ret);
    19. bool Size_to_seval(const cocos2d::Size& v, se::Value* ret);
    20. bool Rect_to_seval(const cocos2d::Rect& v, se::Value* ret);
    21. bool Color3B_to_seval(const cocos2d::Color3B& v, se::Value* ret);
    22. bool Color4B_to_seval(const cocos2d::Color4B& v, se::Value* ret);
    23. bool Color4F_to_seval(const cocos2d::Color4F& v, se::Value* ret);
    24. bool ccvalue_to_seval(const cocos2d::Value& v, se::Value* ret);
    25. bool ccvaluemap_to_seval(const cocos2d::ValueMap& v, se::Value* ret);
    26. bool ccvaluemapintkey_to_seval(const cocos2d::ValueMapIntKey& v, se::Value* ret);
    27. bool ccvaluevector_to_seval(const cocos2d::ValueVector& v, se::Value* ret);
    28. bool blendfunc_to_seval(const cocos2d::BlendFunc& v, se::Value* ret);
    29. bool std_vector_string_to_seval(const std::vector<std::string>& v, se::Value* ret);
    30. bool std_vector_int_to_seval(const std::vector<int>& v, se::Value* ret);
    31. bool std_vector_float_to_seval(const std::vector<float>& v, se::Value* ret);
    32. bool std_vector_Touch_to_seval(const std::vector<cocos2d::Touch*>& v, se::Value* ret);
    33. bool std_map_string_string_to_seval(const std::map<std::string, std::string>& v, se::Value* ret);
    34. bool uniform_to_seval(const cocos2d::Uniform* v, se::Value* ret);
    35. bool FontDefinition_to_seval(const cocos2d::FontDefinition& v, se::Value* ret);
    36. bool Acceleration_to_seval(const cocos2d::Acceleration* v, se::Value* ret);
    37. bool Quaternion_to_seval(const cocos2d::Quaternion& v, se::Value* ret);
    38. bool ManifestAsset_to_seval(const cocos2d::extension::ManifestAsset& v, se::Value* ret);
    39. bool AffineTransform_to_seval(const cocos2d::AffineTransform& v, se::Value* ret);
    40. bool Data_to_seval(const cocos2d::Data& v, se::Value* ret);
    41. bool DownloadTask_to_seval(const cocos2d::network::DownloadTask& v, se::Value* ret);
    42. template<typename T>
    43. bool Vector_to_seval(const cocos2d::Vector<T*>& v, se::Value* ret);
    44. template<typename T>
    45. bool Map_string_key_to_seval(const cocos2d::Map<std::string, T*>& v, se::Value* ret);
    46. template<typename T>
    47. bool native_ptr_to_seval(typename std::enable_if<!std::is_base_of<cocos2d::Ref,T>::value,T>::type* v, se::Value* ret, bool* isReturnCachedValue = nullptr);
    48. template<typename T>
    49. bool native_ptr_to_seval(typename std::enable_if<!std::is_base_of<cocos2d::Ref,T>::value,T>::type* v, se::Class* cls, se::Value* ret, bool* isReturnCachedValue = nullptr)
    50. template<typename T>
    51. bool native_ptr_to_seval(typename std::enable_if<std::is_base_of<cocos2d::Ref,T>::value,T>::type* v, se::Value* ret, bool* isReturnCachedValue = nullptr);
    52. template<typename T>
    53. bool native_ptr_to_seval(typename std::enable_if<std::is_base_of<cocos2d::Ref,T>::value,T>::type* v, se::Class* cls, se::Value* ret, bool* isReturnCachedValue = nullptr);
    54. template<typename T>
    55. bool native_ptr_to_rooted_seval(typename std::enable_if<!std::is_base_of<cocos2d::Ref,T>::value,T>::type* v, se::Value* ret, bool* isReturnCachedValue = nullptr);
    56. template<typename T>
    57. bool native_ptr_to_rooted_seval(typename std::enable_if<!std::is_base_of<cocos2d::Ref,T>::value,T>::type* v, se::Class* cls, se::Value* ret, bool* isReturnCachedValue = nullptr);
    58. // Spine conversions
    59. bool speventdata_to_seval(const spEventData& v, se::Value* ret);
    60. bool spevent_to_seval(const spEvent& v, se::Value* ret);
    61. bool spbonedata_to_seval(const spBoneData& v, se::Value* ret);
    62. bool spbone_to_seval(const spBone& v, se::Value* ret);
    63. bool spskeleton_to_seval(const spSkeleton& v, se::Value* ret);
    64. bool spattachment_to_seval(const spAttachment& v, se::Value* ret);
    65. bool spslotdata_to_seval(const spSlotData& v, se::Value* ret);
    66. bool spslot_to_seval(const spSlot& v, se::Value* ret);
    67. bool sptimeline_to_seval(const spTimeline& v, se::Value* ret);
    68. bool spanimationstate_to_seval(const spAnimationState& v, se::Value* ret);
    69. bool spanimation_to_seval(const spAnimation& v, se::Value* ret);
    70. bool sptrackentry_to_seval(const spTrackEntry& v, se::Value* ret);
    71. // Box2d
    72. bool b2Vec2_to_seval(const b2Vec2& v, se::Value* ret);
    73. bool b2Manifold_to_seval(const b2Manifold* v, se::Value* ret);
    74. bool b2AABB_to_seval(const b2AABB& v, se::Value* ret);

    辅助转换函数不属于Script Engine Wrapper抽象层,属于 cocos2d-x 绑定层,封装这些函数是为了在绑定代码中更加方便的转换。每个转换函数都返回 bool 类型,表示转换是否成功,开发者如果调用这些接口,需要去判断这个返回值。

    以上接口,直接根据接口名称即可知道具体的用法,接口中第一个参数为输入,第二个参数为输出参数。用法如下:

    1. se::Value v;
    2. bool ok = int32_to_seval(100, &v); // 第二个参数为输出参数,传入输出参数的地址
    1. int32_t v;
    2. bool ok = seval_to_int32(args[0], &v); // 第二个参数为输出参数,传入输出参数的地址

    (IMPORTANT)理解 native_ptr_to_seval 与 native_ptr_to_rooted_seval 的区别

    开发者一定要理解清楚这二者的区别,才不会因为误用导致 JS 层内存泄露这种比较难查的 bug。

    • native_ptr_to_seval 用于 JS 控制 CPP 对象生命周期 的模式。当在绑定层需要根据一个 CPP 对象指针获取一个 se::Value 的时候,可调用此方法。引擎内大部分继承于 cocos2d::Ref 的子类都采取这种方式去获取 se::Value。记住一点,当你管理的绑定对象是由 JS 控制生命周期,需要转换为 seval 的时候,请用此方法,否则考虑用 native_ptr_to_rooted_seval
    • native_ptr_to_rooted_seval用于CPP 控制 JS 对象生命周期的模式。一般而言,第三方库中的对象绑定都会用到此方法。此方法会根据传入的 CPP 对象指针查找 cache 住的 se::Object,如果不存在,则创建一个 rooted 的 se::Object,即这个创建出来的 JS 对象将不受 GC 控制,并永远在内存中。开发者需要监听 CPP 对象的释放,并在释放的时候去做 se::Object 的 unroot 操作,具体可参照前面章节中描述的 spTrackEntry_setDisposeCallback 中的内容。

    自动绑定

    配置模块 ini 文件

    配置方法与 1.6 中的方法相同,主要注意的是:1.7 中废弃了 script_control_cpp ,因为 script_control_cpp 字段会影响到整个模块,如果模块中需要绑定 cocos2d::Ref 子类和非 cocos::Ref 子类,原来的绑定配置则无法满足需求。1.7 中取而代之的新字段为 classes_owned_by_cpp ,表示哪些类是需要由 CPP 来控制 JS 对象的生命周期。

    1.7 中另外加入的一个配置字段为 persistent_classes , 用于表示哪些类是在游戏运行中一直存在的,比如:TextureCache SpriteFrameCache FileUtils EventDispatcher ActionManager Scheduler

    其他字段与 1.6 一致。

    具体可以参考引擎目录下的 tools/tojs/cocos2dx.ini 等 ini 配置。

    理解 ini 文件中每个字段的意义

    1. # 模块名称
    2. [cocos2d-x]
    3. # 绑定回调函数的前缀,也是生成的自动绑定文件的前缀
    4. prefix = cocos2dx
    5. # 绑定的类挂载在 JS 中的哪个对象中,类似命名空间
    6. target_namespace = cc
    7. # 自动绑定工具基于 Android 编译环境,此处配置 Android 头文件搜索路径
    8. android_headers = -I%(androidndkdir)s/platforms/android-14/arch-arm/usr/include -I%(androidndkdir)s/sources/cxx-stl/gnu-libstdc++/4.8/libs/armeabi-v7a/include -I%(androidndkdir)s/sources/cxx-stl/gnu-libstdc++/4.8/include -I%(androidndkdir)s/sources/cxx-stl/gnu-libstdc++/4.9/libs/armeabi-v7a/include -I%(androidndkdir)s/sources/cxx-stl/gnu-libstdc++/4.9/include
    9. # 配置 Android 编译参数
    10. android_flags = -D_SIZE_T_DEFINED_
    11. # 配置 clang 头文件搜索路径
    12. clang_headers = -I%(clangllvmdir)s/%(clang_include)s
    13. # 配置 clang 编译参数
    14. clang_flags = -nostdinc -x c++ -std=c++11 -U __SSE__
    15. # 配置引擎的头文件搜索路径
    16. cocos_headers = -I%(cocosdir)s/cocos -I%(cocosdir)s/cocos/platform/android -I%(cocosdir)s/external/sources
    17. # 配置引擎编译参数
    18. cocos_flags = -DANDROID
    19. # 配置额外的编译参数
    20. extra_arguments = %(android_headers)s %(clang_headers)s %(cxxgenerator_headers)s %(cocos_headers)s %(android_flags)s %(clang_flags)s %(cocos_flags)s %(extra_flags)s
    21. # 需要自动绑定工具解析哪些头文件
    22. headers = %(cocosdir)s/cocos/cocos2d.h %(cocosdir)s/cocos/scripting/js-bindings/manual/BaseJSAction.h
    23. # 在生成的绑定代码中,重命名头文件
    24. replace_headers=CCProtectedNode.h::2d/CCProtectedNode.h,CCAsyncTaskPool.h::base/CCAsyncTaskPool.h
    25. # 需要绑定哪些类,可以使用正则表达式,以空格为间隔
    26. classes =
    27. # 哪些类需要在 JS 层通过 cc.Class.extend,以空格为间隔
    28. classes_need_extend =
    29. # 需要为哪些类绑定属性,以逗号为间隔
    30. field = Acceleration::[x y z timestamp]
    31. # 需要忽略绑定哪些类,以逗号为间隔
    32. skip = AtlasNode::[getTextureAtlas],
    33. ParticleBatchNode::[getTextureAtlas],
    34. # 重命名函数,以逗号为间隔
    35. rename_functions = ComponentContainer::[get=getComponent],
    36. LayerColor::[initWithColor=init],
    37. # 重命名类,以逗号为间隔
    38. rename_classes = SimpleAudioEngine::AudioEngine,
    39. SAXParser::PlistParser,
    40. # 配置哪些类不需要搜索其父类
    41. classes_have_no_parents = Node Director SimpleAudioEngine FileUtils TMXMapInfo Application GLViewProtocol SAXParser Configuration
    42. # 配置哪些父类需要被忽略
    43. base_classes_to_skip = Ref Clonable
    44. # 配置哪些类是抽象类,抽象类没有构造函数,即在 js 层无法通过 var a = new SomeClass();的方式构造 JS 对象
    45. abstract_classes = Director SpriteFrameCache Set SimpleAudioEngine
    46. # 配置哪些类是始终以一个实例的方式存在的,游戏运行过程中不会被销毁
    47. persistent_classes = TextureCache SpriteFrameCache FileUtils EventDispatcher ActionManager Scheduler
    48. # 配置哪些类是需要由 CPP 对象来控制 JS 对象生命周期的,未配置的类,默认采用 JS 控制 CPP 对象生命周期
    49. classes_owned_by_cpp =

    远程调试与 Profile

    默认远程调试和 Profile 是在 debug 模式中生效的,如果需要在 release 模式下也启用,需要手动修改 cocos/scripting/js-bindings/jswrapper/config.hpp 中的宏开关。

    1. #if defined(COCOS2D_DEBUG) && COCOS2D_DEBUG > 0
    2. #define SE_ENABLE_INSPECTOR 1
    3. #define SE_DEBUG 2
    4. #else
    5. #define SE_ENABLE_INSPECTOR 0
    6. #define SE_DEBUG 0
    7. #endif

    改为:

    1. #if 1 // 这里改为 1,强制启用调试
    2. #define SE_ENABLE_INSPECTOR 1
    3. #define SE_DEBUG 2
    4. #else
    5. #define SE_ENABLE_INSPECTOR 0
    6. #define SE_DEBUG 0
    7. #endif

    Chrome 远程调试 V8

    Windows

    • 编译、运行游戏(或在 Creator 中直接使用模拟器运行)
    • 用 Chrome 浏览器打开chrome-devtools://devtools/bundled/inspector.html?v8only=true&ws=127.0.0.1:5086/00010002-0003-4004-8005-000600070008
      断点调试: JSB 2.0 使用指南  - 图9

    抓取 JS Heap JSB 2.0 使用指南  - 图10

    Profile JSB 2.0 使用指南  - 图11

    Android

    • 保证 Android 设备与 PC 或者 Mac 在同一个局域网中
    • 编译,运行游戏
    • 用 Chrome 浏览器打开chrome-devtools://devtools/bundled/inspector.html?v8only=true&ws=xxx.xxx.xxx.xxx:5086/00010002-0003-4004-8005-000600070008, 其中 xxx.xxx.xxx.xxx 为局域网中 Android 设备的 IP 地址
    • 调试界面与 Windows 相同

    Safari 远程调试 JavaScriptCore

    macOS

    • 打开 Mac 上的 Safari,偏好设置 -> 高级 -> 显示开发者选项
    • 为 Xcode 工程添加 entitlements 文件,如果 entitlements 存在则跳过此步骤。如果不存在,则到工程的 Capabilities 设置中打开 App Sandbox,然后再关闭,这时 .entitlements 文件会自动被添加进工程。 JSB 2.0 使用指南  - 图12,还需要确保 Build Setting 里面 Code Signing Entitlemenets 选项中包含 entitlements 文件。  JSB 2.0 使用指南  - 图13
    • 打开 entitlements 文件,添加 com.apple.security.get-task-allow,值类型为 Boolean,值为 YES.  JSB 2.0 使用指南  - 图14
    • 签名 : General -> 选择你的 Mac 工程 -> Signing -> 选择你的开发者证书
    • 编译、运行游戏
    • 如果是直接在 Creator 的模拟器中运行,则可以跳过第 2,3,4,5 步骤
    • Safari 菜单中选择 Develop -> 你的 Mac 设备名称 -> Cocos2d-x JSB 会自动打开 Web Inspector 页面,然后即可进行设置断点、Timeline profile、console 等操作。 JSB 2.0 使用指南  - 图15 JSB 2.0 使用指南  - 图16 JSB 2.0 使用指南  - 图17
      注意

    如果开发者有修改引擎源码或者自己合并了一些 Patch,需要重新编译模拟器,记得重新设置一下模拟器工程的证书。

     JSB 2.0 使用指南  - 图18

    然后再调用 gulp gen-simulator 生成模拟器。

    iOS

    • 先打开 iPhone 的设置 -> Safari -> 高级 -> Web 检查器
    • 为 Xcode 工程添加 entitlements 文件,如果 entitlements 存在则跳过此步骤。如果不存在,则到工程的 Capabilities 设置中打开 App Sandbox,然后再关闭,这时 .entitlements 文件会自动被添加进工程。 (图示与 macOS 的第 2 步类似)
    • 打开 entitlements 文件,添加 com.apple.security.get-task-allow,值类型为 Boolean,值为 YES。(图示与 macOS 的第 3 步类似)
    • 签名 : General -> 选择你的 iOS 工程 -> Signing -> 选择你的开发者证书
    • 编译、运行游戏
    • Safari 菜单中选择 Develop -> 你的 iPhone 设备名称 -> Cocos2d-x JSB 会自动打开 Web Inspector 页面,然后即可进行设置断点、Timeline profile、console 等操作。(图示与 macOS 的第 6 步类似)

    Q & A

    se::ScriptEngine 与 ScriptingCore 的区别,为什么还要保留 ScriptingCore?

    在 1.7 中,抽象层被设计为一个与引擎没有关系的独立模块,对 JS 引擎的管理从 ScriptingCore 被移动到了 se::ScriptEngine 类中,ScriptingCore 被保留下来是希望通过它把引擎的一些事件传递给封装层,充当适配器的角色。

    ScriptingCore 只需要在 AppDelegate 中被使用一次即可,之后的所有操作都只需要用到 se::ScriptEngine。

    1. bool AppDelegate::applicationDidFinishLaunching()
    2. {
    3. ...
    4. ...
    5. director->setAnimationInterval(1.0 / 60);
    6. // 这两行把 ScriptingCore 这个适配器设置给引擎,用于传递引擎的一些事件,
    7. // 比如 Node 的 onEnter, onExit, Action 的 update,JS 对象的持有与解除持有
    8. ScriptingCore* sc = ScriptingCore::getInstance();
    9. ScriptEngineManager::getInstance()->setScriptEngine(sc);
    10. se::ScriptEngine* se = se::ScriptEngine::getInstance();
    11. ...
    12. ...
    13. }

    se::Object::root/unroot 与 se::Object::incRef/decRef 的区别?

    root/unroot 用于控制 JS 对象是否受 GC 控制,root 表示不受 GC 控制,unroot 则相反,表示交由 GC 控制,对一个 se::Object 来说,root 和 unroot 可以被调用多次,se::Object 内部有_rootCount 变量用于表示 root 的次数。当 unroot 被调用,且_rootCount 为 0 时,se::Object 关联的 JS 对象将交由 GC 管理。还有一种情况,即如果 se::Object 的析构被触发了,如果_rootCount > 0,则强制把 JS 对象交由 GC 控制。

    incRef/decRef 用于控制 se::Object 这个 cpp 对象的生命周期,前面章节已经提及,建议用户使用 se::HandleObject 来控制手动创建非绑定对象的方式控制 se::Object 的生命周期。因此,一般情况下,开发者不需要接触到 incRef/decRef。

    对象生命周期的关联与解除关联

    se::Object::attachObject/dettachObject

    objA->attachObject(objB);类似于 JS 中执行objA.nativeRefs[index] = objB,只有当 objA 被 GC 后,objB 才有可能被 GCobjA->dettachObject(objB);类似于 JS 中执行delete objA.nativeRefs[index];,这样 objB 的生命周期就不受 objA 控制了

    cocos2d::Ref 子类与非 cocos2d::Ref 子类 JS/CPP 对象生命周期管理有何不同?

    目前引擎中 cocos2d::Ref 子类的绑定采用 JS 对象控制 CPP 对象生命周期的方式,这样做的好处是,解决了一直以来被诟病的需要在 JS 层 retain,release 对象的烦恼。

    非 cocos2d::Ref 子类采用 CPP 对象控制 JS 对象生命周期的方式。此方式要求,CPP 对象销毁后需要通知绑定层去调用对应 se::Object 的 clearPrivateData, unroot, decRef 的方法。JS 代码中一定要慎重操作对象,当有可能出现非法对象的逻辑中,使用 cc.sys.isObjectValid 来判断 CPP 对象是否被释放了。

    绑定 cocos2d::Ref 子类的析构函数需要注意的事项

    如果在 JS 对象的 finalize 回调中调用任何 JS 引擎的 API,可能导致崩溃。因为当前引擎正在进行垃圾回收的流程,无法被打断处理其他操作。finalize 回调中是告诉 CPP 层是否对应的 CPP 对象的内存,不能在 CPP 对象的析构中又去操作 JS 引擎 API。

    那如果必须调用,应该如何处理?

    cocos2d-x 的绑定中,如果引用计数为 1 了,我们不使用 release,而是使用 autorelease 延时 CPP 类的析构到帧结束去执行。

    1. static bool js_cocos2d_Sprite_finalize(se::State& s)
    2. {
    3. CCLOG("jsbindings: finalizing JS object %p (cocos2d::Sprite)", s.nativeThisObject());
    4. cocos2d::Sprite* cobj = (cocos2d::Sprite*)s.nativeThisObject();
    5. if (cobj->getReferenceCount() == 1)
    6. cobj->autorelease();
    7. else
    8. cobj->release();
    9. return true;
    10. }
    11. SE_BIND_FINALIZE_FUNC(js_cocos2d_Sprite_finalize)

    请不要在栈(Stack)上分配 cocos2d::Ref 的子类对象

    Ref 的子类必须在堆(Heap)上分配,即通过 new ,然后通过 release 来释放。当 JS 对象的 finalize 回调函数中统一使用 autoreleaserelease 来释放。如果是在栈上的对象,reference count 很有可能为 0,而这时调用 release ,其内部会调用 delete ,从而导致程序崩溃。所以为了防止这个行为的出现,开发者可以在继承于 cocos2d::Ref 的绑定类中,标识析构函数为 protected 或者 private ,保证在编译阶段就能发现这个问题。

    例如:

    1. class CC_EX_DLL EventAssetsManagerEx : public cocos2d::EventCustom
    2. {
    3. public:
    4. ...
    5. ...
    6. private:
    7. virtual ~EventAssetsManagerEx() {}
    8. ...
    9. ...
    10. };
    11. EventAssetsManagerEx event(...); // 编译阶段报错
    12. dispatcher->dispatchEvent(&event);
    13. // 必须改为
    14. EventAssetsManagerEx* event = new EventAssetsManagerEx(...);
    15. dispatcher->dispatchEvent(event);
    16. event->release();

    如何监听脚本错误

    在 AppDelegate.cpp 中通过 se::ScriptEngine::getInstance()->setExceptionCallback(…)设置 JS 层异常回调。

    1. bool AppDelegate::applicationDidFinishLaunching()
    2. {
    3. ...
    4. ...
    5. se::ScriptEngine* se = se::ScriptEngine::getInstance();
    6. se->setExceptionCallback([](const char* location, const char* message, const char* stack){
    7. // Send exception information to server like Tencent Bugly.
    8. // ...
    9. // ...
    10. });
    11. jsb_register_all_modules();
    12. ...
    13. ...
    14. return true;
    15. }