Extending HTML5 Apps with C++

With the MoSync SDK you are not restricted to just coding in HTML5/JavaScript. You can use the Wormhole communication bridge to talk to C/C++ from JavaScript — really useful when you want to extend your HTML5/JavaScript application with custom code written in C++.

If you are not familiar with how to create an HTML5 project, please read the tutorial Developing Apps in HTML5/JavaScript.

Invoke C++ Code from JavaScript

The following example code is based on the HTML5/JS/C++ Hybrid Project template. That template program illustrates how to invoke code you have written in C++ from JavaScript, and has buttons in the HTML user interface for making the device vibrate and beep. Create an app in the Eclipse IDE, using the HTML5/JS/C++ Hybrid Project template, to get the full source code discussed below.

The example uses classes the Wormhole C++ library (WebAppMoblet, MessageStream, MessageStreamJSON) and the JavaScript libraries included in the file wormhole.js (included with the application template).

Two ways for communicating are supported by Wormhole: JSON messages and string stream messages. We will discuss both of them here.

Sending JSON Messages

In JavaScript, the function mosync.bridge.sendJSON() is used to send JSON messages to C++:

mosync.bridge.sendJSON(message, callbackFunction)

This function takes two parameters. The parameter "message" is a dictionary, which contains the message name and optionally other message parameters.

The parameter "callbackFunction" is an optional function that you can call from C++ to return a value to JavaScript. The communication mechanism is asynchronous, which is why a callback function is used to pass the result back from C++ to JavaScript.

Here is some JavaScript code from the file index.html that invokes C++ to make the device vibrate (here we do not need to use any callback function):

/**
 * Vibrate for 1 second.
 */
function vibrate()
{
    // Send message to C++ to make device vibrate.
    // We use the JSON format. The message will
    // be delivered to C++ in a JSON array.
    mosync.bridge.sendJSON(
    {
        "messageName":"Custom",
        "command":"vibrate",
        "duration":1000
    });
}

And here is the C++ code in main.cpp that handles incoming JSON messages:

void handleMessageStreamJSON(WebView* webView, MAHandle data)
{
    // Create the message object. This parses the message data.
    // The message object contains one or more messages.
    JSONMessage message(webView, data);

    // Loop through messages.
    while (message.next())
    {
        // This detects the PhoneGap protocol.
        if (message.is("PhoneGap"))
        {
            mPhoneGapMessageHandler.handlePhoneGapMessage(message);
        }
        // Here we add our own messages. See index.html for
        // the JavaScript code used to send the message.
        else if (message.is("Custom"))
        {
            String command = message.getParam("command");
            if (command == "vibrate")
            {
                int duration = message.getParamInt("duration");
                maVibrate(duration);
            }
        }
    }
}

Wormhole comes pre-package with PhoneGap compative libraries. The messageName parameter is used to distinguish the "protocol" of the messsage. In the example, we have used "Custom" as the protocol for our own code.

To improve efficiency, the JavaScript side can send several messages to C++ in one shot. We therefore need a while-loop in the code to be able to process all incoming messages.

In the C++ code, you get the value of message parameters with the help of the methods getParam (returns a String) and getParamInt (returns an int):

String command = message.getParam("command");
if (command == "vibrate")
{
    int duration = message.getParamInt("duration");
    maVibrate(duration);
}

Sending String Messages

String streams are an alternative way of sending messages from JavaScript to C++, which was developed to make JavaScript Native UI perform well. This method is generally faster than JSON messages (on some platforms 20x faster). Parsing on the C++ side is very efficient, and streams of string messages are concatenated before passed to C++. This makes sending large number of small messages efficient.

The JavaScript function mosync.bridge.send() is used to send string messages to C++:

mosync.bridge.send(stringArray, callbackFunction)

This function takes two parameters. The parameter "messageArray" is an array of strings, and "callbackFunction" is an optional parameter that works in the same way as with mosync.bridge.sendJSON().

Here is JavaScript code from index.html that invokes C++ to make the device beep, using a string message (again, we do not need to use a callback function):

/**
 * Play one beep sound.
 */
function beep()
{
    // Send to custom message beep to C++.
    // Here we used the string stream format.
    mosync.bridge.send(["Custom", "beep"]);
}

Here is the C++ code in main.cpp that handles incoming string messages:

void handleMessageStream(WebView* webView, MAHandle data)
{
    // Create a message stream object. This parses the message data.
    // The message object contains one or more strings.
    MessageStream stream(webView, data);

    // Pointer to a string in the message stream.
    const char* p;

    // Process messages while there are strings left in the stream.
    while (p = stream.getNext())
    {
        if (0 == strcmp(p, "NativeUI"))
        {
            //Forward NativeUI messages to the respective message handler
            mNativeUIMessageHandler.handleMessage(stream);
        }
        else if (0 == strcmp(p, "Resource"))
        {
            //Forward Resource messages to the respective message handler
            mResourceMessageHandler.handleMessage(stream);
        }
        else if (0 == strcmp(p, "close"))
        {
            // Close the application (calls method in class Moblet).
            close();
        }
        // Here we add your own messages. See index.html for
        // the JavaScript code used to send the message.
        else if (0 == strcmp(p, "Custom"))
        {
            const char* command = stream.getNext();
            if (NULL != command && (0 == strcmp(command, "beep")))
            {
                // This is how to play the sound in the resource BEEP_WAV.
                maSoundPlay(BEEP_WAV, 0, maGetDataSize(BEEP_WAV));
            }
        }
    }
}

As with JSON messages, we use a "protocol parameter" to determine which module the message should go to. Again we use the token "Custom" to designate our own protocol.

String streams are processed by obtaining pointers to charater sequences. As long as the method stream.getNext() returns non-NULL we get a pointer to the next string parameter.

You can use standard C library functions such as strcmp to operate on string parameters, as illustrated by this code snippet:

const char* command = stream.getNext();
if (NULL != command && (0 == strcmp(command, "beep")))
{
    // This is how to play the sound in the resource BEEP_WAV.
    maSoundPlay(BEEP_WAV, 0, maGetDataSize(BEEP_WAV));
}

How to Call JavaScript Code from C++

Note that the code below is not part of the code you get with the template application. You can however type it in yourself, to further explore how the code works.

To evaluate JavaScript code in the WebView from C++, you can use the method WebAppMoblet::callJS(). Here is an example:

callJS("alert('Hello World')");

The following snippets illustrate how to invoke C++ from JavaScript and call a JavaScript callback function.

Here is the JavaScript part:

mosync.bridge.sendJSON(
    { "messageName":"Custom", "command":"GetScreenSize" }, 
    function(width, height) {
        alert("Screen size: " + width + " " + height);
    });

And here is the C++ part:

String command = message.getParam("command");
if (command == "GetScreenSize")
{
    // Call JavaScript reply handler for this message.
    int callbackId = message.getParamInt("callbackId");
    char script[512];
    sprintf(
        script,
        "mosync.bridge.reply(%d, %d, %d)",
        callbackId,
        EXTENT_X(maGetScrSize()),
        EXTENT_Y(maGetScrSize()));
    getWebView()->callJS(script);
}

The JavaScript function mosync.bridge.reply always takes the callbackId as the first argument. You can then supply a variable number of arguments (zero to many) that will be passed to the callback function in JavaScript.

Under The Hood

If you wish to dig deeper, you can explore the MoSync source code at GitHub, and you can also take a look at the example program WebViewGeoLocation, which is a low-level example that does not use the Wormhole library. Note that wormhole.js is a generated file, made up of several JavaScript source files (which means it is not found in the GitHub repository).

Items of interest:

Documentation:

 

 

French translation

Hi,

You can find French translation used in MoSync e-Learning here:
http://ymoumen.blogspot.com/2011/12/communication-entre-javascript-et-c....