【cocos2d-x從c++到js】09:JS與C++的交互1——JS代碼調用C++代碼

之前我們講過,在遊戲啓動時,我們要通過SpiderMonkey引擎的註冊接口,向SpiderMonkey註冊相應的從C++到JS的綁定函數,這些函數用於把JS函數調用代碼轉換成對應C++函數調用來執行。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//在AppDelegate::applicationDidFinishLaunching函數中
    ScriptingCore* sc = ScriptingCore::getInstance();
    sc->addRegisterCallback(register_all_cocos2dx);
    sc->addRegisterCallback(register_all_cocos2dx_extension);
    sc->addRegisterCallback(register_cocos2dx_js_extensions);
    sc->addRegisterCallback(register_all_cocos2dx_extension_manual);
    sc->addRegisterCallback(jsb_register_chipmunk);
    sc->addRegisterCallback(JSB_register_opengl);
    sc->addRegisterCallback(jsb_register_system);
    sc->addRegisterCallback(MinXmlHttpRequest::_js_register);
    sc->addRegisterCallback(register_jsb_websocket);
    sc->addRegisterCallback(register_all_cocos2dx_builder);
    sc->addRegisterCallback(register_CCBuilderReader);
    sc->addRegisterCallback(register_all_cocos2dx_gui);
    sc->addRegisterCallback(register_all_cocos2dx_gui_manual);
    sc->addRegisterCallback(register_all_cocos2dx_studio);
    sc->addRegisterCallback(register_all_cocos2dx_studio_manual);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
    sc->addRegisterCallback(register_all_cocos2dx_spine);


可以看到上面導入了Cocos2d-x的各種庫,核心庫,擴展,opengl,物理引擎,websocket,CCB等等等等。


下面我們說JS代碼如何調用C++代碼。

首先,在創建JS對象的時候,也會創建一個對應的C++對象。換句話說,JS對象是和C++對象一一對應的(當然必須是引擎支持的,而且綁定了接口的)。然後,在JS對象執行函數時,發生了什麼呢?SpiderMonkey引擎會通過註冊的接口,找到對應的C++對象,調用該對象上對應的C++函數。


換句話說,如果有下面的JS代碼:

1
2
var node = cc.Node.create();
node.setVisible(false);


那麼經過SpiderMonkey執行後,會調用下面的代碼:

1
2
auto node = CCNode::create();
node->setVisible(false);


當然,SpiderMonkey遠遠還不止幹了這些,還做了很多事,比如綁定和查找JS和C++對象的對應關係,包裝參數爲對應類型,類型安全檢查,返回值包裝等等。要知道他幹了些什麼,直接看引擎代碼是更好的選擇。


在Cocos2d-x 3.0版的引擎中,引擎目錄結構進行了大規模重構。

wKioL1LeN2WTLO7WAABQNtZHSLU725.jpg

兩個腳本語言被放到一個類似的目錄中。其中auto-generated/js-bindings文件夾是gxx-generator工具自動生成的所有C++綁定JS代碼。而javascript/bingdings文件夾是手寫的綁定代碼,因爲工具無法做到完全自動綁定,所以必須有一部分手寫的(腳本語言都是這樣,習慣就好了,謝謝)。


好,我們繼續找剛纔說的源代碼。打開jsb_cocos2dx_auto.cpp

   
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
JSBool js_cocos2dx_Node_create(JSContext *cx, uint32_t argc, jsval *vp)
{
    if (argc == 0) {
        cocos2d::Node* ret = cocos2d::Node::create();
        jsval jsret = JSVAL_NULL;
        do {
        if (ret) {
            js_proxy_t *proxy = js_get_or_create_proxy<cocos2d::Node>(cx, (cocos2d::Node*)ret);
            jsret = OBJECT_TO_JSVAL(proxy->obj);
        else {
            jsret = JSVAL_NULL;
        }
    while (0);
        JS_SET_RVAL(cx, vp, jsret);
        return JS_TRUE;
    }
    JS_ReportError(cx, "js_cocos2dx_Node_create : wrong number of arguments");
    return JS_FALSE;
}


這就是cc.Node.create()執行時,底層C++跑的代碼。所有的通過JS調用C++的代碼都與這個形式非常一致,首先看函數接口:

第一個參數JSContext *cx是JS的上下文

第二個參數uint32_t argc是JS代碼中的參數個數,在這個裏argc==0

第三個參數jsval *vp是JS代碼中的具體參數


繼續分析

1
cocos2d::Node* ret = cocos2d::Node::create();

這個代碼再熟悉不過了,標準的Cocos2d-x靜態工場生成對象的代碼


1
jsval jsret = JSVAL_NULL;

jsval jsret是這個函數的返回值,這是表示的是一個JS對象


1
2
js_proxy_t *proxy = js_get_or_create_proxy<cocos2d::Node>(cx, (cocos2d::Node*)ret);
jsret = OBJECT_TO_JSVAL(proxy->obj);

注意這個模板函數,get_or_create,這就是把JS對象和C++對象綁到一起的函數。他非常重要,注意JS和C++對象是一一對應關係,理解這個特效,有助於我們利用JS語言的動態性進行更方便的編程。綁完之後,下面那個函數是用於獲得返回值。


最後,函數都要返回一個JSBool,表面這個函數執行是否成功。如果返回JS_FALSE,還會通過JS_ReportError打印一條報錯信息。注意!腳本語言有一個特點,如果函數運行失敗了,則該函數後面的函數(在同一作用域中的)都會跳過執行。


繼續看下一個函數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
JSBool js_cocos2dx_Node_setVisible(JSContext *cx, uint32_t argc, jsval *vp)
{
    jsval *argv = JS_ARGV(cx, vp);
    JSBool ok = JS_TRUE;
    JSObject *obj = JS_THIS_OBJECT(cx, vp);
    js_proxy_t *proxy = jsb_get_js_proxy(obj);
    cocos2d::Node* cobj = (cocos2d::Node *)(proxy ? proxy->ptr : NULL);
    JSB_PRECONDITION2( cobj, cx, JS_FALSE, "js_cocos2dx_Node_setVisible : Invalid Native Object");
    if (argc == 1) {
        JSBool arg0;
        ok &= JS_ValueToBoolean(cx, argv[0], &arg0);
        JSB_PRECONDITION2(ok, cx, JS_FALSE, "js_cocos2dx_Node_setVisible : Error processing arguments");
        cobj->setVisible(arg0);
        JS_SET_RVAL(cx, vp, JSVAL_VOID);
        return JS_TRUE;
    }
    JS_ReportError(cx, "js_cocos2dx_Node_setVisible : wrong number of arguments: %d, was expecting %d", argc, 1);
    return JS_FALSE;
}

這個函數和前一個函數的區別是,這個函數有參數,並且他是一個類成員函數(上一個是類靜態函數),所以,這裏要有this指針。

1
2
3
4
5
6
jsval *argv = JS_ARGV(cx, vp);
JSBool ok = JS_TRUE;
JSObject *obj = JS_THIS_OBJECT(cx, vp);
js_proxy_t *proxy = jsb_get_js_proxy(obj);
cocos2d::Node* cobj = (cocos2d::Node *)(proxy ? proxy->ptr : NULL);
JSB_PRECONDITION2( cobj, cx, JS_FALSE, "js_cocos2dx_Node_setVisible : Invalid Native Object");

這一大段函數都在找那個this指針。注意,這裏面有一個Cocos2d-x引擎經常出現的錯誤提示Invalid Native Object。底層C++對象被回收了,所以找不到了。


1
2
3
4
5
6
7
8
if (argc == 1) {
    JSBool arg0;
    ok &= JS_ValueToBoolean(cx, argv[0], &arg0);
    JSB_PRECONDITION2(ok, cx, JS_FALSE, "js_cocos2dx_Node_setVisible : Error processing arguments");
    cobj->setVisible(arg0);
    JS_SET_RVAL(cx, vp, JSVAL_VOID);
    return JS_TRUE;
}

CCNode::setVisible(xx)只有一個參數,所以先判斷JS的參數個數爲1。JS_ValueToBoolean完成JS對象到C++對象的轉換,注意!這是基本類型的轉換,和查找對應的對象指針不同。你在gxx-generator生成的代碼中會看到大量的這種轉換。每次轉換都要進行結果判斷,如果失敗,就打印錯誤信息。後面是直接調用對應C++對象的setVisible,以及設置返回值。


很繁瑣不是嗎?如果這種代碼全部手寫是不是會死人呢。肯定的吧。所以這些代碼都是用腳本生成器做出來的(絕大部分)。



後面我們會繼續講解各種JS的綁定代碼。



本文出自 “做遊戲的老G” 博客,請務必保留此出處http://goldlion.blog.51cto.com/4127613/1353583

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章