如何將C++綁定至JAVASCRIPT

轉 http://www.cocos2d-x.org/docs/manual/framework/native/wiki/how-to-bind-c++-to-javascript/zh

 

簡介

Cocos2d JS API接口與Cocos2d-x、Cocos2d-iphone及Cocos2d-html5相同。Cocos2d-html5是經過瀏覽器在移動設備上運行,Cocos2d-iphone和Cocos2d-x內的JSB能夠大幅提高引擎性能。全部圖形、渲染及物理庫代碼都會在本地運行,只有遊戲邏輯會在Java腳本中運行。javascript

將遊戲代碼移植到腳本語言不只僅是基於跨平臺角度,還有不少其餘的優點。由於代碼是未編譯的,全部在運行時期能夠替換,從而加快測試周期。php

爲何選擇Javascript?

Javascript腳本優勢以下:html

  • 更快開發速度
  • 很是適合原型
  • 比其餘編譯語言簡單易懂
  • 可移植性

支持JSB綁定的項目:html5

  • Cocos2d-x
  • Chipmunk
  • CocosBuilder Reader

這意味着你可使用CocosBuilder做爲實際誒編輯器建立Cocos2d-x+Chipmunk遊戲。Cocos2D附帶「Watermelon With Me」樣本遊戲,該遊戲用到了cocos2d、chipmunk物理庫及CocosBuilder。java

Javascript性能

Cocos2d-X使用SpiderMonkey和Firefox JS virtual machine (VM)來執行JS代碼。
JS VM可擴展支持全部cocos2d引擎、Chipmunk庫以及CocosBuilder Reader API接口。因此用JS建立「CCSprite」時,其實是在建立C++「CCSprite」。用JS建立動做時,其實是在建立C++動做。在用JS建立粒子系統時,實際是在建立C++粒子系統以此類推。
這種方法的速度要比HTML5快10到20倍,即使HTML5採用「directCanvas」等加速器。python

基本上全部cocos2d、Chipmunk庫或CocosBuilder Reader API接口幾乎都會以本地速度運行。可是應該注意如下狀況:android

  • 當垃圾收集器(garbage collector)運行時性能可能會下降。解決方法:不要建立不少JS對象,儘量重複利用。
  • 若是主循環太複雜可能會下降性能。解決方法:profile(繪出輪廓)你的JS代碼,若是沒法優化代碼,可在C++中編寫昂貴(expensive)的部分,而後爲這些函數建立JSB綁定。

綁定生成器

如何工做

綁定生成器(Bindings-generator)是一個Java腳本綁定生成器,會生成自動綁定spidermonkey目標Java腳本的C/C++代碼。
GitHub資源庫工具下載:https://github.com/cocos2d/bindings-generatorc++

要求

  1. 下載綁定生成器,本機路徑/Users/iven/Dev/bindings-generator
  2. 爲了在Mac OS X中安裝運行MacPort端口,系統必需要已經安裝蘋果的「Command Line Developer Tools」(命令行開發者工具)。Xcode 4及之後版本的用戶首先須要經過啓動或運行Xcode接受Xcode EULA。git

    xcodebuild -license 
  3. 下載安裝MacPort:http://www.macports.org/install.php
    注意:若是是Homebrew用戶,應該先卸載Homebrew。由於Homebrew不兼容MacPort。
    同時執行MacPort的「selfupdate」命令以確保安裝最新的發佈版本。github

    sudo port -v selfupdate 

    當更新完成以後,使用MacPort在命令行中安裝python依賴(dependencies)

    sudo port install python27 py27-yaml py27-cheetah 

    你會看到以下所示:

  4. 下載llvm-3.3:http://llvm.org/releases/download.html#3.3,解壓至$HOME/bin目錄。若是沒有bin目錄,請建立一個bin目錄,將未解壓的ZIP壓縮包重命名爲「clang+llvm-3.3」。
    最後目錄以下所示:/Users/guanghui/bin/clang+llvm-3.3(guanhui是本機的主目錄名字)

  5. 下載Android-NDKR8 http://dl.google.com/android/ndk/android-ndk-r8e-darwin-x86_64.tar.bz2並解壓。

    樣本代碼

    綁定生成器資源庫中包括一個樣本測試用例。打開bindings-generator/test/simple_test文件夾。

配置

  • 根據本身的環境個性化設置「test/userconf.ini」和「test/user.cfg」文件。
    注意:應該移除後綴爲「.sample」的文件如「user.cfg.sample」和「userconf.ini.sample」

    [DEFAULT]
    androidndkdir=/Users/iven/Dev/android-ndk-r8c clangllvmdir=/Users/iven/Dev/clang+llvm-3.1-x86_64-apple-darwin11 cxxgeneratordir=/Users/iven/Dev/bindings-generator-master 

    user.cfg配置以下所示

    PYTHON_BIN=/opt/local/Library/Frameworks/Python.framework/Versions/2.7/bin/ python2.7 
  • 運行測試樣本

    ./test.sh 

    若是環境設置正確,你會看到以下所示:

    Errors in parsing headers: 1. <severity = Warning, location =<SourceLocation file None, line 0, column 0>, details = "argument unusedduring compilation: '-nostdinc++'"> 

    不用擔憂這個警告,你已完成運行,測試用例會建立一個包含3個文件的「simple_test_bindings」目錄。

  • 一個綁定類的.hpp頭文件

  • 一個實現綁定類的.cpp文件

  • 一個介紹如何(從Java腳本)調用C++類暴露方法的.js文件。

    運行測試

  • Create a JS base Cocos2d-x project. 建立基於JS的Cocos2d-x項目

  • 將「simple_Test folder」文件夾和「simple_test_binding」文件夾添加到項目中

  • 修改「autogentestbindings.cpp」中的註冊函數以下:

    void register_all_autogentestbindings(JSContext* cx, JSObject* obj) { jsval nsval; JSObject *ns; JS_GetProperty(cx, obj, "ts",&nsval); if (nsval == JSVAL_VOID) { ns = JS_NewObject(cx, NULL, NULL, NULL); nsval = OBJECT_TO_JSVAL(ns); JS_SetProperty(cx, obj, "ts",&nsval); } else { JS_ValueToObject(cx,nsval, &ns); } obj = ns; js_register_autogentestbindings_SimpleNativeClass(cx, obj); } 

    注意:若是你將「ts」添加到「test.ini」文件中的「target_namespace」變量裏,便會自動生成代碼。無需修改。

     target_namespace =ts 
  • 在「AppDelegate」中註冊

    包含頭文件「autogentestbindings.hpp」而後註冊回調函數:

    sc->addRegisterCallback(register_all_autogentestbindings); 
  • 在「hello.js」文件適當地方增長如下代碼。本機將「init f」函數放在第一個場景。

    var myClass=new ts.SimpleNativeClass(); var myStr=myClass.returnsACString(); var label = cc.LabelTTF.create(myStr, "Helvetica", 20.0); 

限制

綁定生成器存在如下兩個限制

  • 自變量數字參數沒法工做,因此須要手動編寫包裝器
  • 表明類沒法工做,因此須要手動綁定,詳見下一部分。

手動 JSB 綁定

本指南將介紹利用Cocos2d-x 2.14模板如何在本身的項目中實現JSB綁定。
首先用Cocos2d-js模板建立項目。其次,咱們會介紹如何一步一步從JS調用本地函數。最後你便會學會如何從本地代碼調用JS代碼了。
如今讓咱們開始!本機使用的Mac OS X做爲開發環境。

步驟1. 用Cocos2dx-js模板建立新項目,同時新建一個即將綁定至JS的C++類。

將項目命名爲「JSBinding」而後點擊「Next」、「 Create」


新建一個類而後實現這個類,等會再將其綁定至JS。
按「command+N」新建一個C++類並命名爲「JSBinding」,路徑爲「OS X\C and C++\C++ Class」。

將如下代碼添加到「JSBinding.h」文件中。

#include "cocos2d.h" 
#include "ScriptingCore.h" // Define a namespace to manage your code and make your code clearly namespace JSB { class JSBinding: public cocos2d::CCObject { public: static cocos2d::CCScene* scene(); virtual bool init(); CREATE_FUNC(JSBinding); void functionTest(); }; } 

如今實現JSBinding.cpp中的類。以下所示:

bool JSB::JSBinding::init(){ bool bRef = false; do{ cocos2d::CCLog("JSB init..."); bRef = true; } while (0); return bRef; } void JSB::JSBinding::functionTest(){ cocos2d::CCLog("Function test..."); } 

步驟2. 將C++代碼綁定至Java腳本代碼

按「command+N」新建C++類,而後命名「JSB_AUTO」路徑「OS X\C and C++\C++ Class」。

往「JSB_AUTO.h」文件中增長一些代碼

#include "jsapi.h" 
#include "jsfriendapi.h" #include "ScriptingCore.h" #include "JSBinding.h" void register_all(JSContext* cx, JSObject* obj); 

而後注意「JSB_AUTO.cpp」的實現。

#include "jsapi.h" 
#include "jsfriendapi.h" #include "ScriptingCore.h" #include "JSBinding.h" void register_all(JSContext* cx, JSObject* obj); 

而後注意「JSB_AUTO.cpp」的實現。

#include "cocos2d.h" #include "cocos2d_specifics.hpp" // Binding specific object by defining JSClass JSClass* jsb_class; JSObject* jsb_prototype; // This function is mapping the function 「functionTest」 in 「JSBinding.cpp」 JSBool js_functionTest(JSContext* cx, uint32_t argc, jsval* vp){ JSBool ok = JS_TRUE; JSObject* obj = NULL; JSB::JSBinding* cobj = NULL; obj = JS_THIS_OBJECT(cx, vp); js_proxy_t* proxy = jsb_get_js_proxy(obj); cobj = (JSB::JSBinding* )(proxy ? proxy->ptr : NULL); JSB_PRECONDITION2(cobj, cx, JS_FALSE, "Invalid Native Object"); if (argc == 0) { cobj->functionTest(); JS_SET_RVAL(cx, vp, JSVAL_VOID); return ok; } JS_ReportError(cx, "Wrong number of arguments"); return JS_FALSE; } JSBool js_constructor(JSContext* cx, uint32_t argc, jsval* vp){ cocos2d::CCLog("JS Constructor..."); if (argc == 0) { JSB::JSBinding* cobj = new JSB::JSBinding(); cocos2d::CCObject* ccobj = dynamic_cast<cocos2d::CCObject*>(cobj); if (ccobj) { ccobj->autorelease(); } TypeTest<JSB::JSBinding> t; js_type_class_t* typeClass; uint32_t typeId = t.s_id(); HASH_FIND_INT(_js_global_type_ht, &typeId, typeClass); assert(typeClass); JSObject* obj = JS_NewObject(cx, typeClass->jsclass, typeClass->proto, typeClass->parentProto); JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(obj)); js_proxy_t* p = jsb_new_proxy(cobj, obj); JS_AddNamedObjectRoot(cx, &p->obj, "JSB::JSBinding"); return JS_TRUE; } JS_ReportError(cx, "Wrong number of arguments: %d, was expecting: %d", argc, 0); return JS_FALSE; } // This function is mapping the function 「create」 when using JavaScript code JSBool js_create(JSContext* cx, uint32_t argc, jsval* vp){ cocos2d::CCLog("js is creating..."); if (argc == 0) { JSB::JSBinding* ret = JSB::JSBinding::create(); jsval jsret; do{ if (ret) { js_proxy_t* proxy = js_get_or_create_proxy<JSB::JSBinding>(cx, ret); jsret = OBJECT_TO_JSVAL(proxy->obj); } else{ jsret = JSVAL_NULL; } } while(0); JS_SET_RVAL(cx, vp, jsret); return JS_FALSE; } JS_ReportError(cx, "Wrong number of arguments"); return JS_FALSE; } void js_finalize(JSFreeOp* fop, JSObject* obj){ CCLOGINFO("JSBindings: finallizing JS object %p JSB", obj); } // Binding JSB type void js_register(JSContext* cx, JSObject* global){ jsb_class = (JSClass *)calloc(1, sizeof(JSClass)); jsb_class->name = "JSBinding"; jsb_class->addProperty = JS_PropertyStub; jsb_class->delProperty = JS_PropertyStub; jsb_class->getProperty = JS_PropertyStub; jsb_class->setProperty = JS_StrictPropertyStub; jsb_class->enumerate = JS_EnumerateStub; jsb_class->resolve = JS_ResolveStub; jsb_class->convert = JS_ConvertStub; jsb_class->finalize = js_finalize; jsb_class->flags = JSCLASS_HAS_RESERVED_SLOTS(2); static JSPropertySpec properties[] = { {0, 0, 0, JSOP_NULLWRAPPER, JSOP_NULLWRAPPER} }; // Binding functionTest function static JSFunctionSpec funcs[] = { JS_FN("functionTest", js_functionTest, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FS_END }; // Binding create() function static JSFunctionSpec st_funcs[] = { JS_FN("create", js_create, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FS_END }; // Binding constructor function and prototype jsb_prototype = JS_InitClass( cx, global, NULL, jsb_class, js_constructor, 0, properties, funcs, NULL, st_funcs); JSBool found; JS_SetPropertyAttributes(cx, global, "JSB", JSPROP_ENUMERATE | JSPROP_READONLY, &found); TypeTest<JSB::JSBinding> t; js_type_class_t* p; uint32_t typeId = t.s_id(); HASH_FIND_INT(_js_global_type_ht, &typeId, p); if (!p) { p = (js_type_class_t* )malloc(sizeof(_js_global_type_ht)); p->type = typeId; p->jsclass = jsb_class; p->proto = jsb_prototype; p->parentProto = NULL; HASH_ADD_INT(_js_global_type_ht, type, p); } } // Binding JSB namespace so in JavaScript code JSB namespce can be recognized void register_all(JSContext* cx, JSObject* obj){ jsval nsval; JSObject* ns; JS_GetProperty(cx, obj, "JS", &nsval); if (nsval == JSVAL_VOID) { ns = JS_NewObject(cx, NULL, NULL, NULL); nsval = OBJECT_TO_JSVAL(ns); JS_SetProperty(cx, obj, "JSB", &nsval); } else{ JS_ValueToObject(cx, nsval, &ns); } obj = ns; js_register(cx, obj); } 

如今已經完成了大部分工做,可是咱們須要在「SpiderMonkey」進行註冊。
打開「AppDelegate.cpp」增長如下代碼

ScriptingCore* sc = ScriptingCore::getInstance(); sc->addRegisterCallback(register_all); //add this line 

步驟3. 內存管理
在register_all函數前增長兩個新函數。

JSBool JSB_cocos2dx_retain(JSContext* cx, uint32_t argc, jsval *vp){ JSObject* thisObj = JS_THIS_OBJECT(cx, vp); if (thisObj) { js_proxy_t* proxy = jsb_get_js_proxy(thisObj); if (proxy) { ((CCObject* )proxy->ptr)->retain(); CCLog("Retain succeed!"); return JS_TRUE; } } JS_ReportError(cx, "Invaild native object"); return JS_FALSE; } JSBool JSB_cocos2dx_release(JSContext* cx, uint32_t argc, jsval *vp){ JSObject* thisObj = JS_THIS_OBJECT(cx, vp); if (thisObj) { js_proxy_t* proxy = jsb_get_js_proxy(thisObj); if (proxy) { ((CCObject* )proxy->ptr)->release(); CCLog("Release succeed!"); return JS_TRUE; } } JS_ReportError(cx, "Invaild native object"); return JS_FALSE; } 

在register_all函數中增長如下代碼:

JS_DefineFunction(cx, jsb_prototype, "retain", JSB_cocos2dx_retain, 0, JSPROP_READONLY | JSPROP_PERMANENT); JS_DefineFunction(cx, jsb_prototype, "retain", JSB_cocos2dx_release, 0, JSPROP_READONLY | JSPROP_PERMANENT); 

步驟4. 使用C++代碼回調Java腳本代碼
在C++代碼回調Java腳本代碼以前增長一些代碼至「hello.js」文件。

var testJSB = new JSB.JSBinding(); testJSB.callback = function(i, j){ log("JSB Callback" + i + j); }; 

而後打開「JSBinding.cpp」在「functionTest」中增長一些代碼。

js_proxy_t* p = jsb_get_native_proxy(this); jsval retval; jsval v[] = { v[0] = UINT_TO_JSVAL(32), v[1] = UINT_TO_JSVAL(88) }; ScriptingCore::getInstance()->executeFunctionWithOwner(OBJECT_TO_JSVAL(p->obj), "callback", 2, v, &retval); 

使用「executeFunctionWithOwner()」函數簡化函數調用程序
步驟5. 綁定測試
在「hello.js」文件中增長如下代碼。

var testJSB = new JSB.JSBinding(); testJSB.retain(); testJSB.functionTest(); testJSB.release(); 

步驟6. 如今取出(check out)項目
若是你的綁定程序正確無誤,將會在調試窗口看到以下界面:

恭喜你成功將JS綁定至本地代碼!
你能夠從https://github.com/iTyran/Tutorials/tree/master/jsb/JSBTest下載項目源碼。

相關文章
相關標籤/搜索