Calling javascript function from native code

Hi,

I’m working on a cocos2d-x (v2.1.3) project using the javascript bindings.
So far, I got it working half the way, i.e. I am able to call native c++ functions from javascript.
But right now, I’m struggling to get it working the other way around.

Basically, I set up a simple structure as described on http://stackoverflow.com/questions/15543840/how-to-call-javascript-function-from-c-in-cocos2d-x
However, when calling the native function from javascript with a javascript object, the spidermonkey api fails to find the proxy object and returns null.

Any advices or suggestions?

Answered at http://stackoverflow.com/questions/15543840/how-to-call-javascript-function-from-c-in-cocos2d-x/17311991#17311991

Thanks for pointing that out. I corrected my response (I don’t even remember writing it anymore, but I’m glad it got someone a little closer to the solution).

Hey,

even though you answered with the same link I provided in my question, it seems to work now.
Maybe sometimes it’s better to use std:: string then good old const char*.

Thanks anyway.

That was probably a little bit too early.
The test case given at stackoverflow works fine for me, as long as i invoke the native function from javascript.

In my case, however, I have an objective c library which calls a callback function on some event.
I now want to invoke a javascript function from that callback. Even though I’m using the same test function, the native code fails then.
Is it even possible to invoke a javascript function from native code without a previous call from javascript?

If you post your code, it’ll be a little easier to see where things may be going awry.

Agree.
Randy Chung wrote:

If you post your code, it’ll be a little easier to see where things may be going awry.

Sure.

Timer.mm

...
- (void) registerListener:(TimerListener*) timerListener
{
    mListener = timerListener;
}
- (void) onTimer:(NSString *) message
{
    mListener->onTimer([message cStringUsingEncoding:NSUTF8StringEncoding]);
}
...

Controller.h

class JSObject;
class Controller : public cocos2d::CCObject, public TimerListener {
  protected:
    JSObject* mTarget;
  public:
    void init(JSObject* target) { Timer.registerListener(this); mTarget = target; };

    // this call does not work!
    void onTimer(std::string msg) { testBindings("invokeJSFun", msg); };

    void testBindings(std::string sel, std::string msg);
}

Controller.cpp

void Controller::testBindings(std::string sel, std::string msg) {
    JSContext* cx = ScriptingCore::getInstance()->getGlobalContext();

    if (!mTarget) return;
    js_proxy_t * p;
    JS_GET_PROXY(p, mTarget);
    if (!p) return;

    jsval data[1];
    data[0] = std_string_to_jsval(cx, msg);

    ScriptingCore::getInstance()->executeFunctionWithOwner(OBJECT_TO_JSVAL(p->obj), selector.c_str(), 1, data); 
}

Demo.js

require("jsb_cocos2d.js");

var Demo = cc.Node.extend({
    ctor : function() {
        this._super();

        this.controller = new Controller();
        this.controller.init(this);

        // this call works!
        this.controller.testBindings("invokeJSFun", "Message");
    },
    invokeJSFun : function(msg) {
        cc.log("Test succeeded: " + msg);
    }

});

Hopefully that’s everything…
This part works fine:
this.controller.testBindings("invokeJSFun", "Message");
It calls a native function which invokes the javascript function given by the selector.
In contrast to this:
void onTimer(std::string msg) { testBindings("invokeJSFun", msg); };
Here, the native function which invokes the js function is called by a timer callback.
My thought was that the JSContext is probably missing… but to be honest, it’s just a guess…
Maybe I just got the whole concept wrong, but I still have an older project where it works like that… (with the old JS bindings)

Can anybody help?

I’m guessing you’re getting an Invalid Native Object error in your console output. This suggests to me that the “this” reference you pass in Demo.js (this.controller.init(this)) has been deallocated, and is thus invalid. If you add your Demo node as a child somewhere (or call retain on it), then I bet your onTimer callback will work. Obviously, you shouldn’t call retain() willy-nilly, but for the purpose of tracking down this problem it may be helpful.

If that’s not correct, post the output of your console?

Just tried that, but I still get the same error.
There is actually no error message output on the console, the code just breaks.
It seems like it is finding the native object, but looking at the stack frame of the thread, it fails to execute
the function std_string_to_jsval(cx, msg)

The problem seems to be this line
JSString* str = JS_NewUCStringCopyN(cx, strUTF16, utf16_size);
in function
jsval c_string_to_jsval(JSContext* cx, const char* v, size_t length)
of the ScriptingCore.cpp.
Might the conversion of the NSString to std::string be a problem?

Is your msg null in the stack? Your string conversion looks a bit suspicious to me. cStringUsingEncoding returns a const char*; you probably need to convert that to a std::string using something like http://stackoverflow.com/questions/8126498/how-to-convert-a-const-char-to-stdstring.

Nope. The msg is alright. Conversion to utf16 works fine as well. (At least I can debug that far)
I used that kind of conversion as it was working with the older version of cocos2d-x with the same obj-c library.
So the only thing I can imagine now is some kind of wrong JSContext*… ???

Yes, I agree with Randy, your Controller maybe deallocated. Please check whether the destructor of Controller was called before js function was invoked.

Randy Chung wrote:

I’m guessing you’re getting an Invalid Native Object error in your console output. This suggests to me that the “this” reference you pass in Demo.js (this.controller.init(this)) has been deallocated, and is thus invalid. If you add your Demo node as a child somewhere (or call retain on it), then I bet your onTimer callback will work. Obviously, you shouldn’t call retain() willy-nilly, but for the purpose of tracking down this problem it may be helpful.
>
If that’s not correct, post the output of your console?

Or a demo to reproduce this issue will be better for us to find out what the problem is.

Thanks for your help.
I checked the destructor of the Controller and also added a retain to the Demo node/added it to the rootNode.
Still the same error.

If I find the time, I’ll provide a little demo to reproduce the issue.
For now I have to live with an ugly workaround:
Using a synchronized queue to save the data provided by the objc callback and adding a schedule function on the js side to fetch the data from the queue.
Pretty nasty, but works for now.

Even though it doesn’t fit into the topic - is it possible to run a scheduler on an orphan node (in this case on the Demo node)? I somehow can’t get the node into “running” state without attaching it to a rootNode.
I basically just need a global timer (and I like to use the scheduler) which is not part of a scene.

is it possible to run a scheduler on an orphan node (in this case on the Demo node)? I somehow can’t get the node into “running” state without attaching it to a rootNode.

Sure, it’s supported. Please use cc.Scheduler.schedule() method.

Thanks, that was just way too easy :slight_smile:

— Edited —
Hmm… should have tried that before answering…
it sounded so convenient, but it actually doesn’t work.
The JSB only registers the set/getTimeScale functions for the scheduler…

static JSFunctionSpec funcs[] = {
      JS_FN("setTimeScale", js_cocos2dx_CCScheduler_setTimeScale, 1, JSPROP_PERMANENT | JSPROP_ENUMERATE),
      JS_FN("getTimeScale", js_cocos2dx_CCScheduler_getTimeScale, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE),
      JS_FN("ctor", js_cocos2dx_CCScheduler_ctor, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE),
      JS_FS_END
};

So it won’t find the schedule function…
Any solution for that?

— Edited —
Just found the definition of the JSB in “cocos2d_specifics.cpp”.
But the problem remains the same: TypeError: cc.Scheduler.schedule is not a function

Just for your interest:
The cc.Scheduler.schedule function does exist, but gets renamed in “cocos2d_specifics.cpp”.

JS_DefineFunction(cx, jsb_CCScheduler_prototype, "scheduleCallbackForTarget", js_CCScheduler_schedule, 1, JSPROP_READONLY | JSPROP_PERMANENT);

The scheduler has to be retrieved by the director (if you have an orphan node).
The repeat parameter has manually be set to “Infinity” as the default value(–1) doesn’t work and calls the scheduler only once (tested on iPad Mini).

cc.Director.getInstance().getScheduler().scheduleCallbackForTarget(target, selector, interval, Infinity);

— Edit
If you are working with the simulator, the default value does actually work.
Couldn’t find a useful solution for both cases, so I defined my own max value=2^31-1=2147483647 which is the max int32 value.