JSB手动绑定时iOS模拟器运行时报错

  • creator版本:v2.2.1
  • 语言:javascript
    学习手动JSB绑定,进行类绑定时运行出错,不知道哪里的问题,麻烦大佬们帮忙看下。

报错信息:

JS: testJSBridge
ERROR: ReferenceError: Can't find variable: ns, location: src/project.dev.js:170:27
STACK:
testJSBridge@src/project.dev.js:170:27
startRequestAuth@src/project.dev.js:165:28
emit@src/cocos2d-jsb.js:16732:22
emitEvents@src/cocos2d-jsb.js:16715:23
_onTouchEnded@src/cocos2d-jsb.js:16301:47
src/cocos2d-jsb.js:33212:35
_doDispatchEvent@src/cocos2d-jsb.js:10186:103
dispatchEvent@src/cocos2d-jsb.js:10887:25
_touchEndHandler@src/cocos2d-jsb.js:10078:25
_onTouchEventCallback@src/cocos2d-jsb.js:23216:59
_dispatchEventToListeners@src/cocos2d-jsb.js:23300:108
_dispatchTouchEvent@src/cocos2d-jsb.js:23249:41
dispatchEvent@src/cocos2d-jsb.js:23496:35
handleTouchesEnd@src/cocos2d-jsb.js:31230:37
touchend@src/cocos2d-jsb.js:31432:43
src/cocos2d-jsb.js:31445:22
dispatchEvent@jsb-adapter/jsb-builtin.js:2860:39
jsb-adapter/jsb-builtin.js:2899:33

截图

PS: 参考官方手动教程代码,但是不知道漏了哪一步。
官方教程链接-JSB 2.0 绑定教程

JS端代码:

testJSBridge: function () {
	cc.log('testJSBridge');

	var myObj = new ns.JSBridge();
	// myObj.foo();
}

OC端绑定文件:

  • 头文件
#ifndef JSBridge_h
#define JSBridge_h

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

bool js_register_ns_JSBridge(se::Object* global);

#endif /* JSBridge_h */
  • cpp文件
//
//  JSBridge.cpp
//  hello_world-mobile
//
//  Created by baichao hwang on 2019/12/19.
//

#include "JSBridge.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_JSBridge_proto = nullptr;
static se::Class* __jsb_ns_JSBridge_class = nullptr;

namespace ns {
class JSBridge
{
public:
    JSBridge()
    : xxx(0)
    {}
    
    void foo() {
        printf("JSBridge::foo\n");
        
        if (_cb != nullptr) {
            _cb(xxx);
        }
    }
    
    static void static_func() {
        printf("JSBridge::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;
};
}


static bool js_JSBridge_setCallback(se::State& s)
{
    const auto& args = s.args();
    int argc = (int)args.size();
    if (argc >= 1)
    {
        ns::JSBridge* cobj = (ns::JSBridge*)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());
            
            // 如果当前 JSBridge 是可以被 new 出来的类,我们 使用 se::Object::attachObject 把 jsFunc 和 jsTarget 关联到当前对象中
            s.thisObject()->attachObject(jsFunc.toObject());
            s.thisObject()->attachObject(jsTarget.toObject());
            
            // 如果当前 JSBridge 类是一个单例类,或者永远只有一个实例的类,我们不能用 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_JSBridge_setCallback)

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

static bool js_JSBridge_constructor(se::State& s)
{
    ns::JSBridge* cobj = new ns::JSBridge();
    s.thisObject()->setPrivateData(cobj);
    return true;
}
SE_BIND_CTOR(js_JSBridge_constructor, __jsb_ns_JSBridge_class, js_JSBridge_finalize)

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

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

static bool js_JSBridge_set_xxx(se::State& s)
{
    const auto& args = s.args();
    int argc = (int)args.size();
    if (argc > 0)
    {
        ns::JSBridge* cobj = (ns::JSBridge*)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_JSBridge_set_xxx)

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






bool js_register_ns_JSBridge(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("JSBridge", ns, nullptr, _SE(js_JSBridge_constructor)); // 如果无构造函数,最后一个参数可传入 nullptr,则这个类在 JS 中无法被 new JSBridge()出来
    
    // 为这个 Class 对象定义成员函数、属性、静态函数、析构函数
    cls->defineFunction("foo", _SE(js_JSBridge_foo));
    cls->defineProperty("xxx", _SE(js_JSBridge_get_xxx), _SE(js_JSBridge_set_xxx));
    
    cls->defineFunction("setCallback", _SE(js_JSBridge_setCallback));
    
    cls->defineFinalizeFunction(_SE(js_JSBridge_finalize));
    
    // 注册类型到 JS VirtualMachine 的操作
    cls->install();
    
    // JSBClassType 为 Cocos 引擎绑定层封装的类型注册的辅助函数,此函数不属于 ScriptEngine 这层
    JSBClassType::registerClass<ns::JSBridge>(cls);
    
    // 保存注册的结果,便于其他地方使用,比如类继承
    __jsb_ns_JSBridge_proto = cls->getProto();
    __jsb_ns_JSBridge_class = cls;
    
    // 为每个此 Class 实例化出来的对象附加一个属性
    __jsb_ns_JSBridge_proto->setProperty("yyy", se::Value("helloyyy"));
    // 注册静态成员变量和静态成员函数
    se::Value ctorVal;
    if (ns->getProperty("JSBridge", &ctorVal) && ctorVal.isObject())
    {
        ctorVal.toObject()->setProperty("static_val", se::Value(200));
        ctorVal.toObject()->defineFunction("static_func", _SE(js_JSBridge_static_func));
    }
    
    // 清空异常
    se::ScriptEngine::getInstance()->clearException();
    return true;
}

@dumganhar @panda 大佬,论坛里没有发现别人遇到类似问题,似乎大家这里执行都直接成功,不知道什么问题,麻烦帮忙看下。

代码执行了吗?js_register_ns_JSBridge 有调用到没?

请问这个应该在什么地方 执行呢?另外带的参数传什么参数呢?

是在AppDelegate.cpp 这个文件的applicationDidFinishLaunching 方法里面吗?

是类似这样执行吗?

是的,这里加下

抱歉依旧遇到了另外一个问题。头文件导不进去:joy:抱歉这方面初学者。


我绑定的类放到了工程里,需要绑定的位置在引擎的框架里,框架里怎么调用我的类呢?还是有什么其他方式呢?谢谢。

js_register_ns_JSBridge 已经添加注册,执行时依旧同样的错误。根据log看整个绑定的类都没有执行到。
请问下还可能是哪里出的问题呢?

你好,顺便贴一下导出的creator配置,与导出配置有关系吗?

已解决,猜测应该是这两句需要保证顺序。

se->addRegisterCallback(js_register_ns_SomeClass);
    // bind jsb end  
se->start();

注册绑定完成才调start()方法。