Developers Manual > Cocos2d-x > Modules > Scripting > Javascript Binding > How to bind C++ to Javascript

How to bind C++ to Javascript

Introduction

Cocos2d JS is available with the same api for cocos2d-x, cocos2d-iphone and cocos2d-html5. While cocos2d-html5 will run on mobile devices through the browser, JavaScript bindings have been created for cocos2d-iphone and cocos2d-x to drastically improve the performance. All graphics, rendering and physics code will run natively and only the game logic will run in JavaScript.
Having your game code in a scripting language provides more advantages than just the cross-platform aspect. As the code is not compiled, it can be replaced in run-time which allows much faster testing cycles.

Why JavaScript ?

The benefits of a scripting language are:

  • Faster development time
  • Great for prototyping purposes
  • Easier to learn than compiled languages
  • Portability

JS Bound APIs

These are the projects that have JS bindings:

Cocos2d-x
Chipmunk
CocosBuilder Reader
Cocostudio
etc...

That means that you can create a game cocos2d-x* chipmunk game using also CocosBuilder as the world editor. cocos2d comes with a sample game called Watermelon With Me that uses cocos2d, Chipmunk Physics and CocosBuilder.

Performance of JavaScript

Cocos2d-x uses SpiderMonkey, the Firefox JS virtual machine (VM), to execute JS code.

The JS VM is extended to support all the cocos2d, Chipmunk and CocosBuilder Reader APIs. So, when you create a CCSprite in JS, you are actually creating an C++ CCSprite. When you create an action in JS, you are actually creating an C++ action, when you create a particle system in JS, you are actually creating an C++ particle system… and so on.
This approach is about 10x ~ 20x faster than HTML5 games even when they use accelerators like directCanvas
Basically all the cocos2d, Chipmunk or CocosBuilder Reader APIs are going to perform almost at native speed. But you should pay attention to the following scenarios:
The performance could be slow down while the garbage collector is run. Workaround: Do not create many JS objects. Reuse as many as possible
Having a complex main loop could slow down the performance. Workaround: Profile your JS code, and if you can’t further optimize it, write the expensive parts in C++ and create JS bindings for those functions.

Bindings-generator

How it Works

Bindings-generator is a Javascript bindings generator, it is geared to generating C/C++ code that auto-binds to Javascript for target spidermonkey.
GitHub repository: https://github.com/cocos2d/bindings-generator

Requirements

  1. Download Bindings-generator, my path:/Users/iven/Dev/bindings-generator

  2. In order to install and run MacPorts on Mac OS X, your system must have installations of the Apple’s Command Line Developer Tools.
    Xcode 4 and later users need to first accept the Xcode EULA by either launching Xcode or running:

xcodebuild -license
  1. Download and install MacPorts on your system from http://www.macports.org/install.php

Caution: If you are a Homebrew users, you should uninstall homebrew first. Because homebrew can’t live well with macport.
The MacPorts “selfupdate” command will also be run for you by the installer to ensure you have latest available release version:

sudo port -v selfupdate

When the “self update” finished, Use MacPorts to install the python dependencies on command line:

sudo port install python27 py27-yaml py27-cheetah

You should see something like follow:

  1. Download llvm-3.3 from http://llvm.org/releases/download.html#3.3 and extract it to $HOME/bin directory.

If you don’t have a directory named bin, you should create one and then rename the unpacked zip folder to clang+llvm-3.3.
The final directory looks like this: /Users/guanghui/bin/clang+llvm-3.3 .

  1. Download Android NDK r8e from http://dl.google.com/android/ndk/android-ndk-r8e-darwin-x86_64.tar.bz2 and extract it to some place you like.

Sample Code:

In the bindings-generator repository include a simple test. Lanuch to the bindings-generator/test/simple_test folder.

Configuration

  1. Customize test/userconf.ini and test/user.cfg for your environment.

Note: You should remove .sample suffixs of file user.cfg.sample and file userconf.ini.sample.
Modify userconf.ini as follow:

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

and user.cfg as follow:

PYTHON_BIN=/opt/local/Library/Frameworks/Python.framework/Versions/2.7/bin/python2.7
  1. Run the test :
./test.sh

If your environment is set up correctly you’ll see something as follow:

Errors in parsing headers:
1. ,
details = “argument unusedduring compilation: ‘-nostdinc++’”>

Do not care about this Warning, you have done and the test will create a directory named simple_test_bindings that contains 3 files

  • A .hpp header file for the bindings class
  • A .cpp file implementing the bindings class
  • A .js file that documents how to call the methods the C++ class exposes

Run the test

  • Create a JS base Cocos2d-x project.

  • Add both simple_Test folder and simple_test_binding folder in your js project.

  • Modify register function in autogentestbindings.cpp as follow:

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

Note: If you add “ts” to target_namespace variable in test.ini file, then it will automatic generate the up code for you. you don’t need to modify it.

 target_namespace =ts
  • Register in AppDelegate:

Include head file: autogentestbindings.hpp

and register callback:

sc->addRegisterCallback(register_all_autogentestbindings); 
  • In hello.js add following code in right place. I add to init function in my first scene:
 var myClass=new ts.SimpleNativeClass();
 var myStr=myClass.returnsACString();     
 var label = cc.LabelTTF.create(myStr, "Helvetica", 20.0);

Build and Run, is it success? Oh, you have got your own js-binding project right now.

Limitations

We know 2 Limitations about bindings-generator.

  • Variable number of arguments won’t work, so we have to write a manual wrapper.

  • Delegate class won’t work, so we have to bind it manually and we’ll show you how to do it next.

Manually JavaScript Binding

In this tutorial we will use Cocos2d-x 2.14 template to show you how to implement JavaScript Binding in your project. Fisrtly, create a project with cocos2d-js template. Secondly, we’ll show you how to call native function from JS step by step. Finally, you’ll learn how to call JS code from native.

Now, let’s get started! We use Mac OS X as development enviroment.

  • Step1. Create a new project with cocos2dx-js template and new a C*+ class which will bind to js.*

Name the project “JSBinding”, click “Next”, “Create”

New a class and implement it, we will bind it to js later.
Press “command+N” to new a C*+ class and name it “JSBinding”, “OS X\C and C**** Class”

Add the code below to the 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();
    };
}

Now implement your Class in JSBinding.cpp as below:

    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...");
    }
  • Step2. Bindig your C*+ code to JavaScript code

Press “command+N” to new a C*+ class and name it “JSB_AUTO”, “OS X\C and C**** Class”

Add some codes to “JSB_AUTO.h”

#include “jsapi.h”
#include “jsfriendapi.h”
#include “ScriptingCore.h”
#include “JSBinding.h”
void register_all;

Then focus on the implement in “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;
    if {
        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(cobj);
        if (ccobj) {
            ccobj->autorelease();
        }
        TypeTest 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(cx, ret);
                jsret = OBJECT_TO_JSVAL(proxy~~>obj);
            }
            else{
                jsret = JSVAL_NULL;
            }
        } while;
        JS_SET_RVAL;
        return JS\_FALSE;
    }
    JS_ReportError;
    return JS\_FALSE;
}

void js_finalize{
    CCLOGINFO ("JSBindings: finallizing JS object %p JSB", obj);
}

// Binding JSB type
void js_register{
    jsb_class = calloc);
    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 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);
}

Now you’ve finished most of work, but we need register in SpiderMonkey to make it work.
Open AppDelegate.cpp, add blow code:

    ScriptingCore* sc = ScriptingCore::getInstance();
    sc->addRegisterCallback(register_all);                //add this line
  • Step3. Memory Management

Add two new functions before the register_all function

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

Now add below code in register_all function:

    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);
  • Step4. Using C*+ code callback JavaScript code

Before C++ code can callback JavaScript code adding some codes to “hello.js”

var testJSB = new JSB.JSBinding;
testJSB.callback = function{
    log;
};

Then go to JSBinding.cpp and add some codes in “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);

Using executeFunctionWithOwner() to simplify the function call procedure

  • Step5. Binding Test**

Add the codes below in your hello.js.

    var testJSB = new JSB.JSBinding();

    testJSB.retain();
    testJSB.functionTest();
    testJSB.release();
  • Step6. Checking out your project now**

If your binding procedure is right you’ll see follow output in your debug window

Congratulation you just successfully binding JS to native!

You can download the project source from https://github.com/iTyran/Tutorials/tree/master/jsb/JSBTest.

install_python27.png (161.2 kB) iven, 2013-05-16 10:11

jsproject.png (131.6 kB) iven, 2013-05-16 10:12

pc1.png (198.3 kB) iven, 2013-08-12 07:11

pc2.png (212.8 kB) iven, 2013-08-12 07:11

pc3.png (194.6 kB) iven, 2013-08-12 07:11

pc4.png (194.6 kB) iven, 2013-08-12 07:12

pc5.png (26.6 kB) iven, 2013-08-12 07:12

Sign up for our newsletter to keep up with the latest developments, releases and updates for Cocos2d-x.