[SOLVED] Calling JavaScript from C++ crashes on Android

My code works fine on iOS but crashes on Android while executing ScriptingCore::getInstance()->executeFunctionWithOwner.

I’m using cocos2d-js v.3.5.

Any guess what could be the problem?

This is my JS caller code:

void AssetManager_js::_callJavaScriptCallback (const std::string& functionName, bool isOK, const std::string& errorMsg)
{
    js_proxy_t* p = jsb_get_native_proxy(this);

   // is the JS callback function registered?
    if (p == nullptr) {
        return;
    }
    
    JSContext* cx = ScriptingCore::getInstance()->getGlobalContext();

    jsval retval;
    jsval v[] = {
        v[0] = BOOLEAN_TO_JSVAL(isOK),
        // <C> this line gives "EXC_BAD_ACCES". Fixed by:
        // <REF> http://discuss.cocos2d-x.org/t/js-callfunctionname-crashed-on-iap-finished-callback/17254
        //v[1] = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, errorMsg.c_str()))
        v[1] = std_string_to_jsval(cx, errorMsg)
    };
    
    ScriptingCore::getInstance()->executeFunctionWithOwner(OBJECT_TO_JSVAL(p->obj), functionName.c_str(), 2, v);        }

Add this code it may helpful for you.

ScriptingCore* sc = ScriptingCore::getInstance();
jsval value = std_string_to_jsval(ScriptingCore::getInstance()->getGlobalContext(), “sampleString”);
ScriptingCore::getInstance()->executeFunctionWithOwner(OBJECT_TO_JSVAL(sc->getGlobalObject()),“functionCallFromAndroid”, 1, &value);

1 Like

My problem was calling the callback on other then CocosGUI thread.
This is my complete solution. Maybe someone comes with better solution not involving the instance variables this->_functionName... and thus allowing multiple calls at the same time!
In you code just remove all the SDEBUG calls.

.h

protected:
    bool isJSCallInProgress;
    string _functionName;
    bool _isOK;
    string _errorMsg;

.cpp

////////////////////////////////////////////////////////////////////////////////////////////
//
//
void AssetManager_js::_callJavaScriptCallback (const std::string& functionName, bool isOK, const std::string& errorMsg)
{
    SDEBUG(LEVEL_DEBUG, "assets-cpp", "CALLED: AssetManager_js::_callJavaScriptCallback(functionName=%s; bool isOK=%s; errorMsg=%s;)",
        functionName.c_str(), BOOLStr(isOK), errorMsg.c_str());

    // <C> C++ to JS calls must be made on CocosUI thread otherwise the variables are not properly set/accessed in JS (you get null for assigned var in another thread)
    // Thus this method is called on CocosUI thread!
    //
    
    // <REF> https://github.com/cocos2d/cocos2d-x/blob/fb55c2cd63ddfec911ef3ffa468edd048b865957/extensions/assets-manager/AssetsManager.cpp
    // <REF> https://github.com/cocos2d/cocos2d-x/pull/4401
    // <REF> http://discuss.cocos2d-x.org/t/proposal-for-v3-0-thread-helper-utilities/8818/11
    //

    // <M><C> performFunctionInCocosThread() is called on another thread and needs call-life-time variables.
    // Such variables are shared thus only 1 JS call can be made at the same time!
    //
    if (this->isJSCallInProgress == true) {
        SDEBUG(LEVEL_DEBUG, "assets-cpp", "QUIT: AssetManager_js::_callJavaScriptCallback(): another JS call is registered. We can serve only 1 at the time. QUITing!", "");
        return;
    }
    
    this->isJSCallInProgress = true;
    this->_functionName = functionName;
    this->_isOK = isOK;
    this->_errorMsg = errorMsg;

    Director::getInstance()->getScheduler()->performFunctionInCocosThread([&, this]{
        this->__callJavaScriptCallback(this->_functionName, this->_isOK, this->_errorMsg);
    });
    
    SDEBUG(LEVEL_DEBUG, "assets-cpp", "EXIT: AssetManager_js::_callJavaScriptCallback()", "");
}

////////////////////////////////////////////////////////////////////////////////////////////
//
//
void AssetManager_js::__callJavaScriptCallback (const std::string& functionName, bool isOK, const std::string& errorMsg)
{
    SDEBUG(LEVEL_DEBUG, "assets-cpp", "CALLED: AssetManager_js::__callJavaScriptCallback(functionName=%s; bool isOK=%s; errorMsg=%s;)",
        functionName.c_str(), BOOLStr(isOK), errorMsg.c_str());

    // <M><REF> http://discuss.cocos2d-x.org/t/calling-javascript-from-c-crashes-on-android/22805
    //
//    ScriptingCore* sc = ScriptingCore::getInstance();
//    jsval value = std_string_to_jsval(ScriptingCore::getInstance()->getGlobalContext(), errorMsg.c_str());
//    ScriptingCore::getInstance()->executeFunctionWithOwner(OBJECT_TO_JSVAL(sc->getGlobalObject()),"functionTestCallFromAndroid", 1, &value);

    
    //
    // JS call: onPurchaseForProductResponse
    //
    //

    js_proxy_t* p = jsb_get_native_proxy(this);
    SDEBUG(LEVEL_DEBUG, "assets-cpp", "AssetManager_js::__callJavaScriptCallback.jsb_get_native_proxy()", "");

   // is the JS callback function registered?
    if (p == nullptr) {
        // <C> this function may be invoked either from JS BuyScene->onBuy() or from queued processing of pending transactions (no BuyScene thus no JS callback);
        // p is NULL if not called from JS;
        // <M> I still might want to let JS code know about this finished transtion. Then global JS function may be invoked (withdout need of p)
        // See <REF> http://discuss.cocos2d-x.org/t/js-callfunctionname-crashed-on-iap-finished-callback/17254
        //
        SDEBUG(LEVEL_DEBUG, "assets-cpp", "QUIT: AssetManager_js::__callJavaScriptCallback(): Event %s received but NO JS CALLBACK IS REGISTERED. QUITing. isOK=%s; errorMsg=%s;",
            functionName.c_str(), BOOLStr(isOK), errorMsg.c_str());
        this->isJSCallInProgress = false;
        return;
    }

    JSContext* cx = ScriptingCore::getInstance()->getGlobalContext();
    SDEBUG(LEVEL_DEBUG, "assets-cpp", "AssetManager_js::__callJavaScriptCallback.getGlobalContext()", "");

    jsval retval;
    jsval v[] = {
        v[0] = BOOLEAN_TO_JSVAL(isOK),
        // <C> this line gives "EXC_BAD_ACCES". Fixed by:
        // <REF> http://discuss.cocos2d-x.org/t/js-callfunctionname-crashed-on-iap-finished-callback/17254
        //v[1] = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, errorMsg.c_str()))
        v[1] = std_string_to_jsval(cx, errorMsg)
    };
    

    // <REF> https://github.com/cocos2d/cocos2d-x/blob/fb55c2cd63ddfec911ef3ffa468edd048b865957/extensions/assets-manager/AssetsManager.cpp
    // <REF> https://github.com/cocos2d/cocos2d-x/pull/4401
    // <REF> http://discuss.cocos2d-x.org/t/proposal-for-v3-0-thread-helper-utilities/8818/11


    //static std::string _functionName = functionName;
    //SDEBUG(LEVEL_DEBUG, "assets-cpp", "******* BEFORE A CALL OF XXX(%s)", functionName.c_str());
    
    
    // <FIX><UPGRADE>
    // cocos-3.3: bool executeFunctionWithOwner(jsval owner, const char *name, uint32_t argc = 0, jsval* vp = NULL, jsval* retVal = NULL);
    // cocos-3.5: bool executeFunctionWithOwner(jsval owner, const char *name, uint32_t argc,     jsval *vp,        JS::MutableHandleValue retVal);
    //
    // 3.3: ScriptingCore::getInstance()->executeFunctionWithOwner(OBJECT_TO_JSVAL(p->obj), functionName.c_str(), 2, v, &retval);
    //
    SDEBUG(LEVEL_DEBUG, "assets-cpp", "******* CALLING executeFunctionWithOwner(IS_NULL(p->obj):%s; functionName=%s; IS_NULL(v):%s;)",
        p->obj != NULL ? "NO" : "YES", functionName.c_str(),  v != NULL ? "NO" : "YES");
    ScriptingCore::getInstance()->executeFunctionWithOwner(OBJECT_TO_JSVAL(p->obj), functionName.c_str(), 2, v);    // <C> I don't use the retVal anyway
    //    });
    
    
    this->isJSCallInProgress = false;
    
    SDEBUG(LEVEL_DEBUG, "assets-cpp", "EXIT: AssetManager_js::__callJavaScriptCallback()", "");
}