CocosCreator2.0.9的JSB绑定 - 手动绑定

前言

大部分的cocos2d-x的内容都是由官方绑定好的。很方便的使用 cc.Xxx 就可以调用。可是有一些第三方的SDK,或者希望尝试使用C++的代码,就可以手动的绑定。其实就是在js里面用点,用括号,用new,等方式直接调用C++代码。
具体能做什么?是否能把一些复杂的JS计算逻辑放入C++?又或者是否能开启多线程?又或者使用一些C++编码的sdk?等学会了这招才好试试看。
文章就是将我一步一步的实现使用手册里面手动绑定一个C++类到JS的过程记录了下来。

准备

建立工程

使用CocosCreator2.0.9直接建立HelloTypeScript工程,在此基础上进行修改并发布ios的xcode并使用iphone模拟器执行。之后也以这样的方式进行代码调试。完成这一步操作后可以先构建出xcode工程进行这个空基础工程的测试。
真个实验过程先用ios平台进行测试,之后再试着发布到android。

先来个简单的

打开HelloWorld.ts,将start()里面的

    this.label.string = this.text;

改为

    this.label.string = foo;

由于是typescript, foo会被标红,最后再说。
当然,更好的写法是

        if (cc.sys.isNative) {
            this.label.string = foo;
        } else {
            this.label.string = this.text;
        }

因为C++代码只有原生平台能使用,做个平台区分。
此时,我们开始为js全局范围内的foo绑定一个值,下面采用C++的代码来完成:
打开Xcode工程,在Classes目录下新建一个DefaultJSBind.h文件盒DefaultJSBind.cpp文件:

//
//  DefaultJSBind.h
//  HelloTypeScript
//
//  Created by Wang Yichun on 2019/4/23.
//

#ifndef DefaultJSBind_h
#define DefaultJSBind_h

void defaultBind();

#endif /* DefaultJSBind_h */

//
//  DefaultJSBind.cpp
//  HelloTypeScript-mobile
//
//  Created by Wang Yichun on 2019/4/23.
//

#include "DefaultJSBind.h"

#include "cocos/scripting/js-bindings/jswrapper/SeApi.h"

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

接着在AppDelegate.cpp 的applicationDidFinishLaunching()中调用defaultBind():

    se->start();
    
    se::AutoHandleScope hs;
    
    defaultBind();
    
    jsb_run_script("jsb-adapter/jsb-builtin.js");
    jsb_run_script("main.js");

接着运行就好了。
在这里插入图片描述

绑定整个C++类给JS

这部分对应手册里面 注册一个 CPP 类到 JS 虚拟机中 这一节

同样在Classes文件夹中建立 SomeClass.h 和 SomeClass.cpp 文件。

//
//  SomeClass.h
//  HelloTypeScript
//
//  Created by Wang Yichun on 2019/4/23.
//

#ifndef SomeClass_h
#define SomeClass_h

#include "cocos/scripting/js-bindings/jswrapper/SeApi.h"

bool js_register_ns_SomeClass(se::Object* global);

#endif /* SomeClass_h */

//
//  SomeClass.cpp
//  HelloTypeScript-mobile
//
//  Created by Wang Yichun on 2019/4/23.
//

#include "SomeClass.h"

#include "cocos/scripting/js-bindings/manual/jsb_module_register.hpp"
#include "cocos/scripting/js-bindings/manual/jsb_global.h"
#include "cocos/scripting/js-bindings/jswrapper/SeApi.h"
#include "cocos/scripting/js-bindings/event/EventDispatcher.h"
#include "cocos/scripting/js-bindings/manual/jsb_classtype.hpp"

#include "cocos2d.h"
USING_NS_CC;

static se::Object* __jsb_ns_SomeClass_proto = nullptr;
static se::Class* __jsb_ns_SomeClass_class = nullptr;

namespace ns {
    class SomeClass
    {
    public:
        SomeClass()
        : xxx(0)
        {}
        
        void foo() {
            printf("SomeClass::foo\n");
            
            if (_cb != nullptr) {
                _cb(xxx);
            }
        }
        
        static void static_func() {
            printf("SomeClass::static_func\n");
        }
        
        void setCallback(const std::function<void(int)>& cb) {
            _cb = cb;
            if (_cb != nullptr)
            {
                printf("setCallback(cb)\n");
            }
            else
            {
                printf("setCallback(nullptr)\n");
            }
        }
        
        int xxx;
    private:
        std::function<void(int)> _cb;
    };
} // namespace ns {


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

static bool js_SomeClass_finalize(se::State& s)
{
    ns::SomeClass* cobj = (ns::SomeClass*)s.nativeThisObject();
    delete cobj;
    return true;
}
SE_BIND_FINALIZE_FUNC(js_SomeClass_finalize)

static bool js_SomeClass_constructor(se::State& s)
{
    ns::SomeClass* cobj = new ns::SomeClass();
    s.thisObject()->setPrivateData(cobj);
    return true;
}
SE_BIND_CTOR(js_SomeClass_constructor, __jsb_ns_SomeClass_class, js_SomeClass_finalize)

static bool js_SomeClass_foo(se::State& s)
{
    ns::SomeClass* cobj = (ns::SomeClass*)s.nativeThisObject();
    cobj->foo();
    return true;
}
SE_BIND_FUNC(js_SomeClass_foo)

static bool js_SomeClass_get_xxx(se::State& s)
{
    ns::SomeClass* cobj = (ns::SomeClass*)s.nativeThisObject();
    s.rval().setInt32(cobj->xxx);
    return true;
}
SE_BIND_PROP_GET(js_SomeClass_get_xxx)

static bool js_SomeClass_set_xxx(se::State& s)
{
    const auto& args = s.args();
    int argc = (int)args.size();
    if (argc > 0)
    {
        ns::SomeClass* cobj = (ns::SomeClass*)s.nativeThisObject();
        cobj->xxx = args[0].toInt32();
        return true;
    }
    
    SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", argc, 1);
    return false;
}
SE_BIND_PROP_SET(js_SomeClass_set_xxx)

static bool js_SomeClass_static_func(se::State& s)
{
    ns::SomeClass::static_func();
    return true;
}
SE_BIND_FUNC(js_SomeClass_static_func)

bool js_register_ns_SomeClass(se::Object* global)
{
    // 保证 namespace 对象存在
    se::Value nsVal;
    if (!global->getProperty("ns", &nsVal))
    {
        // 不存在则创建一个 JS 对象,相当于 var ns = {};
        se::HandleObject jsobj(se::Object::createPlainObject());
        nsVal.setObject(jsobj);
        
        // 将 ns 对象挂载到 global 对象中,名称为 ns
        global->setProperty("ns", nsVal);
    }
    se::Object* ns = nsVal.toObject();
    
    // 创建一个 Class 对象,开发者无需考虑 Class 对象的释放,其交由 ScriptEngine 内部自动处理
    auto cls = se::Class::create("SomeClass", ns, nullptr, _SE(js_SomeClass_constructor)); // 如果无构造函数,最后一个参数可传入 nullptr,则这个类在 JS 中无法被 new SomeClass()出来
    
    // 为这个 Class 对象定义成员函数、属性、静态函数、析构函数
    cls->defineFunction("foo", _SE(js_SomeClass_foo));
    cls->defineProperty("xxx", _SE(js_SomeClass_get_xxx), _SE(js_SomeClass_set_xxx));
    
    cls->defineFunction("setCallback", _SE(js_SomeClass_setCallback));
    
    cls->defineFinalizeFunction(_SE(js_SomeClass_finalize));
    
    // 注册类型到 JS VirtualMachine 的操作
    cls->install();
    
    // JSBClassType 为 Cocos 引擎绑定层封装的类型注册的辅助函数,此函数不属于 ScriptEngine 这层
    JSBClassType::registerClass<ns::SomeClass>(cls);
    
    // 保存注册的结果,便于其他地方使用,比如类继承
    __jsb_ns_SomeClass_proto = cls->getProto();
    __jsb_ns_SomeClass_class = cls;
    
    // 为每个此 Class 实例化出来的对象附加一个属性
    __jsb_ns_SomeClass_proto->setProperty("yyy", se::Value("helloyyy"));
    
    // 注册静态成员变量和静态成员函数
    se::Value ctorVal;
    if (ns->getProperty("SomeClass", &ctorVal) && ctorVal.isObject())
    {
        ctorVal.toObject()->setProperty("static_val", se::Value(200));
        ctorVal.toObject()->defineFunction("static_func", _SE(js_SomeClass_static_func));
    }
    
    // 清空异常
    se::ScriptEngine::getInstance()->clearException();
    return true;
}

对比手册里的代码已经做了一些修改,去掉了一个Director的计时器,因为这个版本使用手册的代码找不到Director。
接着修改 Helloworld.ts 文件为:

const {ccclass, property} = cc._decorator;

@ccclass
export default class Helloworld extends cc.Component {

    @property(cc.Label)
    label: cc.Label = null;

    @property
    text: string = 'hello2';

    start() {
        // init logic
        if (cc.sys.isNative) {
            this.label.string = foo;
            this.testSomeClass();
        } else {
            this.label.string = this.text;
        }
    }

    testSomeClass() {
        cc.log('testSomeClass');

        var myObj = new ns.SomeClass();
        myObj.foo();
        ns.SomeClass.static_func();
        cc.log("ns.SomeClass.static_val: " + ns.SomeClass.static_val);
        cc.log("Old myObj.xxx:" + myObj.xxx);
        myObj.xxx = 1234;
        cc.log("New myObj.xxx:" + myObj.xxx);
        cc.log("myObj.yyy: " + myObj.yyy);

        var delegateObj = {
            onCallback: function (counter) {
                cc.log("Delegate obj, onCallback: " + counter + ", this.myVar: " + this.myVar);
                this.setVar();
            },

            setVar: function () {
                this.myVar++;
            },

            myVar: 100
        };

        myObj.setCallback(delegateObj.onCallback, delegateObj);

        setTimeout(function () {
            myObj.setCallback(null);
        }, 6000); // 6 秒后清空 callback


        myObj.foo();
    }
}

也就是对C++那边绑定的ns名字空间中等SomeClass类进行测试性的调用。
这是相关的输出结果,注意CocosCreator构建发布时勾选“调试模式”

JS: testSomeClass
SomeClass::foo
SomeClass::static_func
JS: ns.SomeClass.static_val: 200
JS: Old myObj.xxx:0
JS: New myObj.xxx:1234
JS: myObj.yyy: helloyyy
setCallback(cb)
SomeClass::foo
JS: Delegate obj, onCallback: 1234, this.myVar: 100
setCallback(nullptr)

关于TypeScript里面的自动提示

由于这些JS里面的变量也好,类也好。都是在C++中生成的。因此TS的编辑器肯定认不出来。为了弥补这个缺陷,以不至于VSCode里面全屏的红线。需要加入一个globals.d.ts文件,放在项目Assets同级目录下。可以看见放在了creator.d.ts文件旁边。

declare var foo: any;

declare namespace ns {
	export class SomeClass {
		constructor();

		xxx: Number;
		yyy: Number;

		foo(): void;

		static static_func(): void;
		static static_val: Number;

		setCallback(callback: Function): void;
	}
}

这样即消除了红线,又可以在编码时拥有自动提示,提高编码效率。

在Android设备上

之后我又在android平台上做了一些测试,期间遇到的两个问题分别做了解决:

问题: /Users/frsyrup/Documents/Projs/HelloTypeScript/build/jsb-link/frameworks/runtime-src/proj.android-studio/app/jni/…/…/…/Classes/AppDelegate.cpp:71: error: undefined reference to ‘defaultBind()’
/Users/frsyrup/android-ndk-r16b/sources/cxx-stl/llvm-libc++/include/new:234: error: undefined reference to ‘js_register_ns_SomeClass(se::Object*)’
clang++: error: linker command failed with exit code 1 (use -v to see invocation)
解决:在Android.mk加入

LOCAL_SRC_FILES := hellojavascript/main.cpp \../../../Classes/AppDelegate.cpp \
				   ../../../Classes/jsb_module_register.cpp \
				   ../../../Classes/SomeClass.cpp \
				   ../../../Classes/DefaultJSBind.cpp \

问题:Cannot create a handle without a HandleScope
解决:AppDelegate.cpp中把我们的代码defaultBind()调用一定要放到se::AutoHandleScope hs之后

	//...
    jsb_register_all_modules();
    se->addRegisterCallback(js_register_ns_SomeClass);
    se->start();
    se::AutoHandleScope hs;
    defaultBind();
    jsb_run_script("jsb-adapter/jsb-builtin.js");
    jsb_run_script("main.js");
    //...

总结

从接触CocosCreator1.9.1以来,积累了JS与OC,JS与Java的代码相互调用的经验,经常为了去调用原生平台的一些API,接入原生SDK等。看到过绑定C++代码以形成 JS与C++的互调,不过一直没有去试验过。直到今天虽然做了ios和android平台上的实验。但并没有在实际的上线项目中使用过,还有没有什么坑不太清楚。
后面可以实验性的接入一些C++的代码库进行一些可用性的尝试。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值