如何通过JS_CallFunctionValue函数完成JS层回调函数的调用。

这篇文章将为大家详细讲解有关cocos2d-x中回调函数的按键回调是怎样的,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

目前创新互联公司已为上千余家的企业提供了网站建设、域名、雅安服务器托管成都网站托管、企业网站设计、西青网站维护等服务,公司将坚持客户导向、应用为本的策略,正道将秉承"和谐、参与、激情"的文化,与客户和合作伙伴齐心协力一起成长,共同发展。

回调函数是界面交互和接入各种第三方SDK的关键所在,因为回调函数的C++代码是不能自动生成的,一切的一切,都需要手写完成。

比较不错的是,Cocos2d-x引擎对于回调函数提供了完整的包装机制。我们所需要做的就是了解这个机制,并使用他。学习引擎自己的代码例子,可以比较快速准确的上手这一机制。

首先,我们在Cocos2d-x 3.0 beta版中,使用他自带的工程创建工具,新建一个跨平台的JS项目。按照惯例,这是一个helloworld项目。在XCode运行时,我们可以看到右下角的回调按钮。我们来看看他是怎么实现的。分成两个过程来做:

一、绑定回调函数过程

首先,我们要去找回调函数JS的绑定代码,在myApp.js中,init函数里面,可以看到如下代码:

1 2 3 4 5 6 7 8 9 10 11 12//
add a "close" icon to exit the progress. it's an autorelease object
var closeItem
= cc.MenuItemImage.create(
    "res/CloseNormal.png",    "res/CloseSelected.png",    function ()
{
        cc.log("close
button was clicked."
);    },this);closeItem.setAnchorPoint(cc.p(0.5,
0.5));
var menu
= cc.Menu.create(closeItem);
menu.setPosition(cc.p(0,
0));
this.addChild(menu,
1);
closeItem.setPosition(cc.p(size.width
- 20, 20));

cc.MenuItemImage.create函数的第三个参数,绑定了匿名回调函数。第四个参数,传入的是回调函数调用时的this(如果不理解JS的this机制,请先阅读一些JS的资料)。这些都是意图和作用很明显的JS代码,不用细说。

然后,我们去看底层对应执行的C++代码。在cocos2d_specifics.cpp文件中,找到js_cocos2dx_CCMenuItemImage_create函数。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46//
"create" in JS
//
cc.MenuItemImage.create( normalImage, selectedImage, [disabledImage], callback_fn, [this]
JSBool
js_cocos2dx_CCMenuItemImage_create(JSContext *cx, uint32_t argc, jsval *vp)
{    if (argc
>= 2 && argc <= 5) {
        jsval
*argv = JS_ARGV(cx, vp);
        JSStringWrapper
arg0(argv[0]);
        JSStringWrapper
arg1(argv[1]);
        JSStringWrapper
arg2;
        bool thirdArgIsString
true;        jsval
jsCallback = JSVAL_VOID;
        jsval
jsThis = JSVAL_VOID;
        int last
= 2;
        if (argc
>= 3) {
            thirdArgIsString
= argv[2].isString();
            if (thirdArgIsString)
{
                arg2.set(argv[2],
cx);
                last
= 3;
            }        }        cocos2d::MenuItemImage*
ret = cocos2d::MenuItemImage::create(arg0.get(), arg1.get(), std::string(arg2.get()));
        if (argc
>= 3) {
            if (!thirdArgIsString)
{
                //cc.MenuItemImage.create(
normalImage, selectedImage, callback_fn, [this] )
                jsCallback
= argv[last++];
                if (argc
== 4) {
                    jsThis
= argv[last];
                }            }            else {                //cc.MenuItemImage.create(
normalImage, selectedImage, disabledImage, callback_fn, [this] )
                if (argc
>= 4) {
                    jsCallback
= argv[last++];
                    if (argc
== 5) {
                        jsThis
= argv[last];
                    }                }            }        }        JSObject
*obj = bind_menu_item(cx, ret, jsCallback, jsThis);
        JS_SET_RVAL(cx,
vp, OBJECT_TO_JSVAL(obj));
        return JS_TRUE;    }    JS_ReportError(cx, "Invalid
number of arguments. Expecting: 2 <= args <= 5"
);    return JS_FALSE;}

因为在C++层,这是一个重载过的函数,所以他的实现里面有很多参数个数的判断(关于重载问题请参考之前的章节)。过滤掉很多代码,我们直接看关键部分:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18if (argc
>= 3) {
            if (!thirdArgIsString)
{
                //cc.MenuItemImage.create(
normalImage, selectedImage, callback_fn, [this] )
                jsCallback
= argv[last++];
                if (argc
== 4) {
                    jsThis
= argv[last];
                }            }            else {                //cc.MenuItemImage.create(
normalImage, selectedImage, disabledImage, callback_fn, [this] )
                if (argc
>= 4) {
                    jsCallback
= argv[last++];
                    if (argc
== 5) {
                        jsThis
= argv[last];
                    }                }            }        }

在这里我们从参数中取出回调函数和this,分别赋值给jsCallback和jsThis。

1JSObject
*obj = bind_menu_item(cx, ret, jsCallback, jsThis);

由这句模板函数来实现回调的绑定,四个参数依次是,JS上下文,cc.MenuItemImage对应的C++对象,回调函数,和回调函数调用时的this。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17template<class T>JSObject*
bind_menu_item(JSContext *cx, T* nativeObj, jsval callback, jsval thisObj) {  
    js_proxy_t
*p = jsb_get_native_proxy(nativeObj);
    if (p)
{
        addCallBackAndThis(p->obj,
callback, thisObj);
        return p->obj;    else {        js_type_class_t
*classType = js_get_type_from_native(nativeObj);
        assert(classType);        JSObject
*tmp = JS_NewObject(cx, classType->jsclass, classType->proto, classType->parentProto);
        //
bind nativeObj <-> JSObject
        js_proxy_t
*proxy = jsb_new_proxy(nativeObj, tmp);
        JS_AddNamedObjectRoot(cx,
&proxy->obj, 
typeid(*nativeObj).name());              addCallBackAndThis(tmp,
callback, thisObj);
        return tmp;    }}

继续看bind_menu_item的实现。简单说一下,因为绑定的是一个JS函数,所以实际上,需要在SpiderMonkey里面做这个绑定操作。传进来的是一个C++对象(CCMenuItemImage类型),首先找到和这个C++对象对应的JS对象。如果找不到,就新建立一个。然后通过函数addCallBackAndThis执行绑定。

1 2 3 4 5 6 7 8 9static
void addCallBackAndThis(JSObject *obj, jsval callback, jsval &thisObj)
{    if(callback
!= JSVAL_VOID) {
        ScriptingCore::getInstance()->setReservedSpot(0,
obj, callback);
    }    if(thisObj
!= JSVAL_VOID) {
        ScriptingCore::getInstance()->setReservedSpot(1,
obj, thisObj);
    }}
1 2 3 4JSBool
ScriptingCore::setReservedSpot(uint32_t i, JSObject *obj, jsval value) {
    JS_SetReservedSlot(obj,
i, value);
    return JS_TRUE;}

最终我们看到,存储回调函数的方法是通过SpiderMonkey的ReservedSlot机制。0位存放的是回调函数,1位存放的是回调函数对应的this。

好,到此为止,回调函数的绑定全部结束。

二、调用回调函数过程

现在我们看从C++层启动JS回调的过程。我们省略掉事件派发机制,直接看按键事件发生时的调用代码。在按键事件发生时,会调用MenuItemImage的父类MenuItem中的activate函数。该函数在CCMenuItem.cpp中。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17void MenuItem::activate(){    if (_enabled)    {        if(
_callback )
        {            _callback(this);        }                                                                                                                                    if (kScriptTypeNone
!= _scriptType)
        {            BasicScriptData
data(
this);            ScriptEvent
scriptEvent(kMenuClickedEvent,&data);
            ScriptEngineManager::getInstance()->getScriptEngine()->sendEvent(&scriptEvent);        }    }}

非常简单,首先判断按键是否可用。然后如果有C++层回调就调用。如果有脚本层(JS或lua)回调,就包装一个kMenuClickedEvent事件,然后向对应的脚本引擎发送该事件。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45int
ScriptingCore::sendEvent(ScriptEvent* evt)
{    if (NULL
== evt)
        return 0;                                                                     JSAutoCompartment
ac(_cx, _global);
                                                                        switch (evt->type)    {        case kNodeEvent:            {                return handleNodeEvent(evt->data);            }            break;        case kMenuClickedEvent:            {                return handleMenuClickedEvent(evt->data);            }            break;        case kTouchEvent:            {                return handleTouchEvent(evt->data);            }            break;        case kTouchesEvent:            {                return handleTouchesEvent(evt->data);            }            break;        case kKeypadEvent:            {                return handleKeypadEvent(evt->data);            }            break;        case kAccelerometerEvent:            {                return handleAccelerometerEvent(evt->data);            }            break;        default:            break;    }                                                                        return 0;}

JS通过ScriptingCore::sendEvent进行事件分发。kMenuClickedEvent事件派发给handleMenuClickedEvent函数来处理。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20int
ScriptingCore::handleMenuClickedEvent(void* data)
{    if (NULL
== data)
        return 0;                                                               BasicScriptData*
basicScriptData = static_cast(data);
    if (NULL
== basicScriptData->nativeObject)
        return 0;                                                               MenuItem*
menuItem = static_cast(basicScriptData->nativeObject);
                                                               js_proxy_t
* p = jsb_get_native_proxy(menuItem);
    if (!p) return 0;    jsval
retval;
    jsval
dataVal;
    js_proxy_t
*proxy = jsb_get_native_proxy(menuItem);
    dataVal
= (proxy ? OBJECT_TO_JSVAL(proxy->obj) : JSVAL_NULL);
    executeJSFunctionFromReservedSpot(this->_cx,
p->obj, dataVal, retval);
    return 1;}
1 2 3 4 5 6 7 8 9 10 11 12 13 14static void executeJSFunctionFromReservedSpot(JSContext
*cx, JSObject *obj,
                                              jsval
&dataVal, jsval &retval) {
    jsval
func = JS_GetReservedSlot(obj, 0);
    if (func
== JSVAL_VOID) { 
return;
}
    jsval
thisObj = JS_GetReservedSlot(obj, 1);
    JSAutoCompartment
ac(cx, obj);
                           if (thisObj
== JSVAL_VOID) {
        JS_CallFunctionValue(cx,
obj, func, 1, &dataVal, &retval);
    else {        assert(!JSVAL_IS_PRIMITIVE(thisObj));        JS_CallFunctionValue(cx,
JSVAL_TO_OBJECT(thisObj), func, 1, &dataVal, &retval);
    }}

再次通过SpiderMonkey的ReservedSlot机制,取回相应的参数,最后通过JS_CallFunctionValue函数完成JS层回调函数的调用。

关于cocos2d-x中回调函数的按键回调是怎样的就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。


新闻名称:如何通过JS_CallFunctionValue函数完成JS层回调函数的调用。
当前地址:http://hbruida.cn/article/jeehop.html