Native and Managed Plugins for Windows 8 and Apache Cordova

Native and Managed Plugins for Windows 8 and Apache Cordova

[Update Feb 25, 2014]
After testing some more use cases, it appears that there are still issues with some of the features of this addition. I am currently working through the following :

  • .winmd file needs an accompanying .dll with the actual implementation as .winmd only defines the interface
  • need the ability to target multiple architectures, not all plugins can be written to support ‘all-plaforms’
  • missing capability to add a dependent SDK

Check back here for more updates as I resolve issues. -jm

Up until recently, it was only possible to create JavaScript plugins for Windows 8 in Apache Cordova. This was because at the core, the Cordova app for Windows 8 was a JavaScript app. There was no additional native layer to call into, save what Microsoft wraps your JavaScript app in. This also meant that you could not create a plugin to add a feature if there was not a Windows Store SDK WinJS callable API for it, or if you could not program it in JS directly.

Several use-cases that have come up include :

  • Reuse existing libraries and/or code written in C#/C++
  • Accessing low level APIs, like XAudio2
  • Sharing code between Windows Phone 8 and Windows 8
  • Computationally intense operations that require the performance of C++

Creating a Windows 8 Plugin in JavaScript

I will start by explaining how to make a pure JavaScript plugin, as a lot of this is still relevant and crucial to understanding how to add the native/managed bits.

Windows 8 Cordova plugins are essentially a thin wrapper around existing WinJS provided functions, but assuming you will want to define your JS common interface for multiple devices, you will typically have 1 JS file that provides the API.

// inside file echoplugin.js
var EchoPlugin = {
    // the echo function calls successCallback with the provided text in strInput
    // if strInput is empty, it will call the errorCallback
    echo:function(successCallback, errorCallback, strInput) {
        cordova.exec(successCallback,errorCallback,"EchoPlugin","echo",[strInput]);
    }
}

Inside Cordova exec

The cordova.exec function is defined differently on every platform, this is because each platform has it's own way of communicating between the application js code, and the native wrapper code. But in the case of Windows 8, there is no native wrapper, so the exec call is there for consistency. You could do your js only plugin work directly in EchoPlugin.echo, something like :

// inside file echoplugin.js
var EchoPlugin = {
    echo:function(successCallback,errorCallback,strInput) {
        if(!strInput || !strInput.length) {
            errorCallback("Error, something was wrong with the input string. =>" + strInput);
        }
        else {
            successCallback(strInput + "echo");
        }
    }
}

This would/could work fine, however it means that you will need different versions of echoPlugin.js for different platforms, and possibly you could have issues with inconsistencies in your implementations. As a best practice, we decided to mimic a native API inside cordova.exec on Windows 8, so we could run the same JS code, and not have to rewrite it for the platform, and also take advantage of any parameter checking, or other common code provided by developers who were working on other platforms.

The cordova exec proxy

On Windows 8, cordova provides a proxy that you can use to register an object that will handle all cordova.exec calls to an API.

For example if you wanted to provide the implementation for the Accelerometer API, you would do this :

cordova.commandProxy.add("Accelerometer",{
    start:function(){
        // your code here ...
    }
    // ,
    //  ... and the rest of the API here
});

So in our case, we will assume that the code in echoplugin.js is handling cross platform relevant JavaScript, and we can simply write a proxy for Windows 8

// in file echopluginProxy.js
cordova.commandProxy.add("EchoPlugin",{
    echo:function(successCallback,errorCallback,strInput) {
        if(!strInput || !strInput.length) {
            errorCallback("Error, something was wrong with the input string. =>" + strInput);
        }
        else {
            successCallback(strInput + "echo");
        }
    }
});

The plugin definition

If we want users of our plugin to be able to easily install our plugin, we will need to define it according to how PlugMan defines plugins. More on this in the plugin spec

<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
    id="com.risingj.echoplugin"
    version="0.1.0">

    <js-module src="www/echoplugin.js" name="echoplugin">
        <clobbers target="window.echoplugin" />
    </js-module>

    <!-- windows8 -->
    <platform name="windows8">
        <js-module src="src/windows8/echopluginProxy.js" name="EchoProxy">
            <merges target="" />
        </js-module>
    </platform>

    <!-- other platforms -->

</plugin>

This gives us a working Windows 8 JavaScript plugin that uses a common file ( echoplugin.js ) and uses a proxy to provide the Windows 8 only portion of implementation ( echopluginProxy.js ). So how do we add native/managed code to this? Well we are going to start the same, the only difference will be what we do inside in echopluginProxy methods.

How WinJS accesses native/managed code

In Windows 8, WinJS authored apps are able to interact with native code, this inter-op is available for Windows Runtime Components. The details are numerous, and I will only cover the basics, so your homework is here.

When you create your Windows Runtime Component, any class that is defined as 'public ref class sealed' is considered an 'activatable class' and will be callable from JavaScript.

// in your header file .h
namespace EchoRuntimeComponent
{
    public ref class EchoPluginRT sealed 
    {
        public:
        static Platform::String^ Echo(Platform::String^ input);
    }
}

// in the implementation file .cpp
using namespace EchoRuntimeComponent;
using namespace Platform;

Platform::String^ EchoPluginRT::Echo(Platform::String^ input)
{
    if(input->IsEmpty()) 
    {
        return "Error: input string is empty.";
    }
    else
    {
        return input->ToString() + "echo";
    }
}

Now in order for us to call the native code, we use the namespace, classname, and lowerCamelCase the method we are calling.

var res = EchoRuntimeComponent.EchoPluginRT.echo("boom");

Moving this to our echopluginProxy.js file, we get this :

// in file echopluginProxy.js
cordova.commandProxy.add("EchoPlugin",{
    echo:function(successCallback,errorCallback,strInput) {
        var res = EchoRuntimeComponent.EchoPluginRT.echo(strInput);
        if(res.indexOf("Error") == 0) {
            errorCallback(res);
        }
        else {
            successCallback(res);
        }
    }
});

And that's it, we have an end to end C++ backed js callable plugin for use in Apache Cordova Windows 8!

Some technical notes, I should add :

  • the callback is typically async, so calling the callback right away is probably not expected by the caller. In practice, if the call is not async, you should at least use a javascript timeout to force the callback to be called async.
  • Activatable classes can do all kinds of awesome, like event dispatching, async callbacks, passing your own object types, arrays, collections, overloaded methods and much more. I recommend you do your homework.
  • If you stick to common Windows Phone 8 and Windows 8 SDK API calls, you will be able to use the same runtime component ( native or managed bits ) in a Windows Phone 8 Apache Cordova plugin. ~stay tuned for that post.

Defining your plugin

Now that we have a working plugin, we need to revisit the plugin definition from earlier so we can publish it. We can now add the runtime component as a framework.
Note that the output type of a WindowsRuntimeComponent can be either .winmd or .dll

<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
    id="com.risingj.echoplugin"
    version="0.2.0">

    <js-module src="www/echoplugin.js" name="echoplugin">
        <clobbers target="window.echoplugin" />
    </js-module>

    <!-- windows8 -->
    <platform name="windows8">
        <js-module src="src/windows8/echopluginProxy.js" name="EchoProxy">
            <merges target="" />
        </js-module>
        <framework src="src/windows8/EchoRuntimeComponent.winmd" custom="true"/>
    </platform>

    <!-- other platforms -->

</plugin>

That's it, you now have a distributable plugin that you can share with the world!
One thing to note, support for adding frameworks to Windows 8 Cordova project was only recently added so you will need to make sure your PlugMan is at least as recent as this commit aka v0.20.0

I will be posting the code in a complete solution shortly, you can fork away here.

https://github.com/purplecabbage/cordova-runtimecomp-echoplug

  1. sgrebnov
    sgrebnov02-24-2014

    Awesome!

    No Twitter Messages