鍍金池/ 教程/ C/ JS 與 C++ 的交互 1——JS 代碼調(diào)用 C++ 代碼
cocos2d-x for js 中的繼承
JS 與 C++ 的交互 1——JS 代碼調(diào)用 C++ 代碼
迎接腳本時代的到來
解決在 vs 中修改 js 源文件無效
JS 腳本語言的優(yōu)勢與一些問題
注冊函數(shù)
回調(diào)函數(shù) 2
cxx-generator JS 綁定工具
使用 cocos2d-console 工具轉(zhuǎn)換腳本為字節(jié)碼
hybrid 開發(fā)模式
JS 與 C++ 的交互 2——JS 與 C++ 的“函數(shù)重載”問題
回調(diào)函數(shù)1——按鍵回調(diào)
Google 的繼承寫法解析
John Resiq 的繼承寫法解析
JS 與 C++ 的交互 3——C++ 和 JS 類型轉(zhuǎn)換
傀儡構(gòu)造函數(shù)

JS 與 C++ 的交互 1——JS 代碼調(diào)用 C++ 代碼

之前我們講過,在游戲啟動時,我們要通過 SpiderMonkey 引擎的注冊接口,向 SpiderMonkey 注冊相應的從 C++ 到 JS 的綁定函數(shù),這些函數(shù)用于把 JS 函數(shù)調(diào)用代碼轉(zhuǎn)換成對應 C++ 函數(shù)調(diào)用來執(zhí)行。

//在AppDelegate::applicationDidFinishLaunching函數(shù)中
    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 代碼如何調(diào)用 C++ 代碼。 首先,在創(chuàng)建 JS 對象的時候,也會創(chuàng)建一個對應的 C++ 對象。換句話說,JS 對象是和 C++ 對象一一對應的(當然必須是引擎支持的,而且綁定了接口的)。然后,在 JS 對象執(zhí)行函數(shù)時,發(fā)生了什么呢? SpiderMonkey 引擎會通過注冊的接口,找到對應的 C++ 對象,調(diào)用該對象上對應的 C++ 函數(shù)。

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

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

那么經(jīng)過 SpiderMonkey 執(zhí)行后,會調(diào)用下面的代碼:

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

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

在 Cocos2d-x 3.0 版的引擎中,引擎目錄結(jié)構(gòu)進行了大規(guī)模重構(gòu)。

http://wiki.jikexueyuan.com/project/cocos2d-x-from-cplusplus-js/images/9.jpg" alt="" />

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

好,我們繼續(xù)找剛才說的源代碼。打開 jsb_cocos2dx_auto.cpp

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() 執(zhí)行時,底層 C++ 跑的代碼。所有的通過 JS 調(diào)用 C++ 的代碼都與這個形式非常一致,首先看函數(shù)接口:
第一個參數(shù) JSContext cx 是 JS 的上下文
第二個參數(shù) uint32_t argc 是 JS 代碼中的參數(shù)個數(shù),在這個里argc==0
第三個參數(shù) jsval
v p是 JS 代碼中的具體參數(shù)

繼續(xù)分析

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

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

jsval jsret = JSVAL_NULL;

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

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

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

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

繼續(xù)看下一個函數(shù)

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;
}

這個函數(shù)和前一個函數(shù)的區(qū)別是,這個函數(shù)有參數(shù),并且他是一個類成員函數(shù)(上一個是類靜態(tài)函數(shù)),所以,這里要有 this 指針。

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");

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

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) 只有一個參數(shù),所以先判斷 JS 的參數(shù)個數(shù)為1。JS_ValueToBoolean 完成 JS 對象到 C++ 對象的轉(zhuǎn)換,注意!這是基本類型的轉(zhuǎn)換,和查找對應的對象指針不同。你在 gxx-generator 生成的代碼中會看到大量的這種轉(zhuǎn)換。每次轉(zhuǎn)換都要進行結(jié)果判斷,如果失敗,就打印錯誤信息。后面是直接調(diào)用對應 C++ 對象的 setVisible,以及設(shè)置返回值。

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

后面我們會繼續(xù)講解各種 JS 的綁定代碼。