wiki.webvm.net/ Howto write native WebVM modules

Intended audience: Native WebVM module developers.

  1. Introduction
    1. C stub generation
    2. Native implementation
    3. Example entry points
    4. Example implementation of WVMModuleFuncs
  2. Class specification
    1. Top-down class design
    2. More method specifications
    3. Method implementations
    4. Property support
  3. From Java interfaces to a JavaScript API using JIDL and WebVM
    1. Java interface
    2. JavaScript representation
    3. Generating native module stubs from Java interfaces

Introduction

Imagine we want to implement the following JavaScript API as a native WebVM module:

var addressbook = webvm.load('addressbook');

var filter = {firstname:"Peter"};

// Finds all Peters in the addressbook and returns all matching contact ids
// as an array of numbers
var contactIDs = addressbook.findContacts(filter);

for(i = 0; i < contactIDs.length; i++) {

    // Retrieves the contact object for the given contact id
    var contact = addressbook.getContactByID(contactIDs[i]);

    // Displays the value of the lastname property of this contact
    // in a popup dialog
    alert(contact.get('lastname'));
}

var newcontact = {};
newcontact.firstname = "Peter";
newcontact.lastname  = "Miller";
newcontact.city      = "York";
newcontact.phone     = "01234567";

// Creates a new contact with the given properties
var id = addressbook.createContact(newcontact);

// Retrieves the contact object for the given id
var contact = addressbook.getContactByID(id);

// Sets the country property on the address object
contact.set('country', 'United Kingdom');

// Deletes the contact again
addressbook.deleteContactByID(id);

The given JavaScript API consists of two classes

Contact

AddressBook

In this example we further require that a contact object supports the following properties:

C stub generation

In order to generate C stubs for the above defined API, Java interfaces can be used in conjunction with the JIDL tool (part of WebVM since version 0.3)

To generate native module implementation stubs for given Java interfaces, it is assumed that the class files of these interfaces are relative to the current directory. The usage is:

java -jar jidl.jar <output directory> <interface> [<interface> [ .. ]]

A simple test to check how it works is running:

java -jar jidl.jar test/ test.TestInterface test.TestInterface2

in the tools directory.

This will generate C files:

<output directory>/<interface>_decl.h
<output directory>/<interface>_decl.c
<output directory>/<interface>_impl.c

To implement a class specification as a native module, all you need is

to include these files into your project just implement the _impl.c file(s).

We’ve also set up a Web interface for JIDL which can be found at jidl.webvm.net.

Native implementation

Each WebVM module is a dynamic library with the following entry points:

WVMStatus WVMAttach(WVM              /* [in]  */  *instance,
                    WVMCallbackFuncs /* [in]  */  *wvcFuncs,
                    WVMModuleFuncs   /* [out] */ **wvmFuncs);

void WVMDetach(WVM /* [in] */ *instance);

WVMAttach

When WebVM loads a native module it calls the WVMAttach entry point. This call supplies two input arguments, an instance handle which can be used to store private data in (see webvm.h for details) and the structure of functions exposed by WebVM which are essential for interacting with WebVM. This call also expects the module function set to be stored in the output argument.

WVMDetach

The WVMDetach entry point is called when WebVM is going to unload the native module. It is the last chance to free any private data of the module stored in the instance handle, because the instance handle will be destroyed when this call returns.

Example entry points

An example implementation of these entry points looks like:

static WVMCallbackFuncs  *wvcf;

WVMStatus WVMAttach(WVM              /* [in]  */  *instance,
            WVMCallbackFuncs /* [in]  */  *wvcFuncs,
            WVMModuleFuncs   /* [out] */ **wvmFuncs)
{
    static WVMModuleFuncs wvmf = {
        sizeof(WVMModuleFuncs),
        { WEBVM_VERSION_MAJOR, WEBVM_VERSION_MINOR },
        init,
        deinit,
        start,
        stop,
        release,
    };

    if(wvcFuncs->version.major < WEBVM_VERSION_MAJOR
    || (wvcFuncs->version.major == WEBVM_VERSION_MAJOR
        && wvcFuncs->version.minor < WEBVM_VERSION_MINOR))
    {
        return Error_Unsupported;
    }
    wvcf               = wvcFuncs;
    *wvmFuncs          = &wvmf;
    return Error_NoError;
}

void WVMDetach(WVM /* [in] */ *instance) {

}

In this example private data is not stored in the instance handle for simplicity. A real world module may need to keep private data that cannot be in static variables in case the module is loaded multiple times by the same WebVM environment. A pointer to such private data can be stored in the mdata field of the instance handle:

instance->mdata = yourdata;

This example WVMAttach implementation uses two global static variables to deal with the WebVM and module function structures.

The most important aspect of WVMAttach is the WVMModuleFuncs initialisation. The module function structure supplied to WebVM is used by WebVM for further calls into the native module. The sample code contains the functions init, deinit, start, stop and getProperty. These functions must be implemented by each native module.

Example implementation of WVMModuleFuncs

static WVMObjectReference scriptableObjectRef;

static WVMStatus init(WVM          /* [in]  */   *instance,
              UInt16       /* [out] */   *classSpecCount,
              WVMClassSpec /* [out] */ ***classSpecs)
{
    *classSpecCount = 2;
    *classSpecs     = classes;
    return Error_NoError;
}

static void deinit(WVM /* [in] */ *instance) {
    /* TODO: cleanup all your resources */
}

static WVMStatus start(WVM /* [in] */ *instance) {
    WVMStatus result;

    result = wvcf->createObjectRef(instance, classes[0], NULL, &scriptableObjectRef);
    if(result == Error_NoError) {
        result = wvcf->setScriptableObject(instance, scriptableObjectRef);
    }
    return result;
}


static WVMStatus stop(WVM /* [in] */ *instance) {
    return wvcf->derefObjectRef(instance, scriptableObjectRef);
}

static WVMStatus getProperty(WVM        /* [in]  */  *instance,
                 const char /* [in]  */  *key,
                 const char /* [out] */ **value)
{
    return Error_Unsupported;
}

init

When WVMAttach returns successfully, WebVM will call the module’s init function. This function has to initialise the native module and to return the class specification it supports.

start

After init returns, WebVM will process the given API class description internally and call the start module function when it is ready.

In this example the native module creates an object reference for the top-level class its API consists of. In the WebVM and browser terminology this is called a scriptable object. WebVM expects the native module to create an object reference for its top-level class during the start function and to use this reference to set its scriptable object. The scriptable object cannot be overwritten at a later point; it is fixed until stop is called.

In the case of an addressbook API the top-level class exported by a native module would be the AddressBook class described above.

Note: Object references cannot be created prior to the start function call by WebVM, because WebVM needs to know the exported classes before any referencing.

stop

Stop is called at any time after start. This tells the module to stop its activity and that no further API calls into the module will be made. WebVM expects the module to destroy the scriptable object reference during this call.

release

The release function is called when the reference count of a resource initially created by a native module reaches 0. The native module must free the resource if release is called.

deinit

This call occurs at any time after stop returned. A native module should clean up its object instance resources here.

Class specification

When WebVM calls the start module function, it expects to retrieve the class specification of a native module. Native WebVM modules only support static APIs described by an OO-like class model which is inspired from the basic principles of Java interfaces. The class model cannot be changed after init has been called.

In the terms of Java interfaces, the addressbook API we want to design looks like:

public interface Contact {

    String get(String key); 

    int set(String key, String value);
}

public interface AddressBook {

    int[] findContacts(Hashtable filter);

    Contact getContactByID(int id);

    int createContact(Hashtable details);

    int deleteContact(id);  

}

webvm.h defines a C data structure to represent this OO class interface design. The module sets up this C data structure to specify the class and its methods and fields.

Note: There are plans to simplify interface description using some kind of IDL description. This work is currently in progress.

Top-down class design

During the init call, the module sets an array of WVMClassSpec pointers, one for each interface being defined. This array and its size are returned to WebVM. To understand the native module class design from a top-down perspective, we start with the WVMClassSpec structure defined in webvm.h:

WVMClassSpec

struct _WVMClassSpec {
    const char      *name;
    UInt16           fieldCount;
    WVMFieldSpec    *fields;
    UInt16           methodCount;
    WVMMethodSpec  **methods;
};

The definition of the Contact and AddressBook classes in a native module would look like:

static WVMClassSpec contactClass = {
    /* name        */ "Contact",
    /* fieldCount  */ 0,
    /* fields      */ NULL,
    /* methodCount */ 5,
    /* methods     */ contactMethods,
};

static WVMClassSpec addressbookClass = {
    /* name        */ "AddressBook",
    /* fieldCount  */ 0,
    /* fields      */ NULL,
    /* methodCount */ 5,
    /* methods     */ addressbookMethods,
};

static WVMClassSpec *classes[] = { &addressbookClass, &contactClass };

Each WVMClassSpecs name must be unique throughout all classes which are exported. To avoid any conflicts with other native modules, it is recommended to introduce Java-style scopes in class names, such as “jp.co.aplix.webvm.bondi.contacts.Contact”. Here “Contact” is used for simplicity.

Each class consists of a fixed number of fields and methods.

Note: Fields are not fully supported in WebVM yet, they will be fully supported soon.

WVMFieldSpec

typedef struct {
    const char *name;
    WVMVariantType type;
} WVMFieldSpec;

In the address book API we do not use fields. In a class with fields, the name of a field must be different to all other fields and methods in the same class.

WVMMethodSpec

typedef struct {
    WVMVariantType type;
    WVMClassSpec  *classSpec;
} WVMArgSpec;

typedef struct {
    UInt16       count;
    WVMArgSpec **inputArgs;
    WVMArgSpec  *returnArg;
} WVMSignature;

typedef struct {
    const char   *name;
    WVMSignature *signature;
    WVMInvokePtr  invoke; /* entrypoint */
} WVMMethodSpec;

A class method specification is a composition of three different types: the method specification container, the method signature, and the argument specifications of the signature.

The findContacts method, for instance, can be specified as follows:

static WVMArgSpec asContactIDArr = { AtaInt, NULL };
static WVMArgSpec asMap          = { AtoMap, NULL };

static WVMArgSpec *addressbookFindContactsInputArgs[] = { &asMap };
static WVMSignature addressbookFindContactsSig = { 1, addressbookFindContactsInputArgs, &asContactIDArr };
static WVMStatus addressbookFindContactsInvoke(WVM *instance, WVMObjectReference ref, WVMVariant *vArgs, WVMVariant *vResultArgs) {
    /* implementation */
    return Error_NoError;
}
static WVMMethodSpec addressbookFindContactsMethod = { "findContacts", &addressbookFindContactsSig, addressbookFindContactsInvoke };

WVMInvokePtr

The method specification of findContacts defines the method name, and contains a pointer to the method’s signature and a function pointer to its implementation. A pointer to any method implementation function matches the following prototype defined in webvm.h:

typedef WVMStatus (*WVMInvokePtr) (WVM                /* [in]  */ *instance,
                   WVMObjectReference /* [in]  */  ref,
                   WVMVariant         /* [in]  */ *vArgs,
                   WVMVariant         /* [out] */ *vResultArgs);

This means that any native method implementation must follow this prototype definition. In the given findContacts example, the addressbookFindContactsInvoke function implements this prototype. We will have a more detailed look to function implementations later.

WVMSignature

The method specification also defines the signature of the exposed method. The given addressbookFindContactsSig signature defines that it takes an input argument of the type asMap and returns an argument of the type asContactIDArr.

Note: Multiple functions with the same signature may share the same signature definition.

WVMArgSpec

An argument specification defines the argument type. Here is a list of the most common argument specs:

static WVMArgSpec asInt    = { VariantType_Int32, NULL };    // defines an integer argument
static WVMArgSpec aslong   = { VariantType_Int64, NULL };    // defines a long argument
static WVMArgSpec asDouble = { VariantType_Double, NULL };     // defines a double argument
static WVMArgSpec asString = { VariantType_String, NULL }; // defines a string argument

If you wanted to specify an argument of the type Contact, representing an object of the Contact class, this is done using the second argument in an WVMArgSpec:

static WVMArgSpec asContact = { VariantType_ObjectRef, &contactClass };

WVMArgSpecs depend on the WVMVariantType enum. For the C data types you only need to use the following WVMVariantType values:

C typeWVMVariantType
voidVariantType_Void
charVariantType_Char
unsigned charVariantType_Byte
shortVariantType_Int32
intVariantType_Int32
long longVariantType_Int64
floatVariantType_Double
doubleVariantType_Double
char *VariantType_String
unsigned char[]VariantType_ByteArray
int[]VariantType_Int32Array
long long[]VariantType_Int64Array
double[]VariantType_DoubleArray
WVMVariant[]VariantType_VariantArray
WVMVariant (as a map)VariantType_Map
WVMErrorVariantType_Error
other object referenceVariantType_ObjectRef

More method specifications

To understand some of the other argument specifications, consider the implementation of the getContactByID and createContact functions of the addressbook class:

static WVMArgSpec asContactID = { VariantType_Int32, NULL };
static WVMArgSpec *addressbookGetContactByIDInputArgs[] = { &asContactID };
static WVMSignature addressbookGetContactByIDSig = { 1, addressbookGetContactByIDInputArgs, &asContact };
static WVMStatus addressbookGetContactByIDInvoke(WVM *instance, WVMObjectReference ref, WVMVariant *vArgs, WVMVariant *vResultArgs) {
    /* implementation */
    return Error_NoError;
}
static WVMMethodSpec addressbookGetContactByIDMethod = { "getContactByID", &addressbookGetContactByIDSig, addressbookGetContactByIDInvoke };

The getContactByID function takes a contact id as input and returns a contact object, which is defined as of type VariantType_ObjectRef the WVMClassSpec pointer of the contactClass.

static WVMArgSpec asMap          = { VariantType_Map, NULL };
static WVMArgSpec *addressbookCreateContactInputArgs[] = { &asMap };
static WVMSignature addressbookCreateContactSig = { 1, addressbookCreateContactInputArgs, &asContactID };
static WVMStatus addressbookCreateContactInvoke(WVM *instance, WVMObjectReference ref, WVMVariant *vArgs, WVMVariant *vResultArgs) {
    /* implementation */
    return Error_NoError;
}
static WVMMethodSpec addressbookCreateContactMethod = { "createContact", &addressbookCreateContactSig, addressbookCreateContactInvoke };

The createContact id takes a map as input and returns the contact id of the newly created contact.

Method implementations

To understand how methods are implemented, the WVMVariant type is crucial, because it is used for the argument processing in native modules. Each WVMVariant is a container of the WVMValue type.

typedef union {
    WVMBool             boolValue;
    Int32               intValue;
    Int64               longValue;
    float               floatValue;
    double              doubleValue;
    WVMObjectReference  refValue;
    WVMVariant         *varValue;
    WVMError           *errValue;
    WVMObject           objValue;

    struct {
        WVMObject handle;
        union {
            char        *utf8Elements;
            byte        *byteElements;
            Int32       *intElements;
            Int64       *longElements;
            double      *doubleElements;
            WVMMapEntry *mapElements;
        } elements;
    } arr;
} WVMValue;

struct _WVMVariant {
    WVMVariantType type;
    UInt32       count;
    WVMValue     value;
    void       (*release)(WVMVariant *);
    void        *userdata;
};

struct _WVMMapEntry {
    WVMAtom     id;
    WVMVariant  varValue;
};

getContactByID

First of all consider the function addressbookGetContactByID again, which is based on a helper function:

typedef struct _Contact Contact;
struct _Contact {
    Int32               id;
    WVMObjectReference  ref;
    Contact            *next;
};

static Contact *getContactByID(WVM *instance, Int32 id) {
    Contact *c;
    IContact *ic;

    for(c = contacts; c; c = c->next) {
        if(c->id == id) {
            return c;
        }
    }
    if((ic = GetAddressBook()->FindContactByID(idmap[id]))) {
        c = (Contact *)malloc(sizeof(Contact));
        c->next = contacts;
        c->id = id;
        wvcf->createObjectRef(instance, &contactClass, c, &c->ref);
        contacts = c;
        return c;
    }
    return NULL;
}

static WVMStatus addressbookGetContactByIDInvoke(WVM *instance, WVMObjectReference ref, WVMVariant *vArgs, WVMVariant *vResultArgs) {
    static WVMError contactNotFoundError = { "Error", "Contact not found.", NULL };
    Contact *c = getContactByID(instance, vArgs[0].value.intValue);

    if(c) {
        vResultArgs->type = VariantType_ObjectRef;
        vResultArgs->value.refValue = c->ref;
        return Error_NoError;
    }
    else {
        vResultArgs->type = VariantType_Error;
        vResultArgs->value.errValue = &contactNotFoundError;
        return Error_Native;
    }
}

In this example the only input argument to addressbookGetContactByIDInvoke is an integer representing the contact id. It is accessed using:

vArgs[0].value.intValue

WebVM ensures that you always retrieve the correct amount of input arguments, so you do not need to check if your input arguments are of the expected form. In this example the first argument is used to retrieve or instantiate a new Contact object, whose object reference (of the specified type VariantType_ObjectRef) is returned.

This makes sure that any subsequent call on this object handle in JavaScript will be directed into the specified Contact class methods, with the ref argument set to the pointer reference used in wvcf->createObjectRef, which, in the example above, is the variable c (of type Contact *). Here is a JavaScript example of the call that creates the object handle, and one that then calls a method on it:

var contact = addressbook.getContactByID(id);
var name = contact.get('name');

If the contact could not be found an error result is returned instead.

Note: Whenever you return an Error_ result, it is expected by WebVM that the result variant points to a proper WVMError struct. An erroneous result will throw an exception in the JavaScript context which retrieves the exception strings from the WVMError structure.

Contact.get

Now consider the following implementation of Contact.get(), which has been specified earlier:

static void releaseString(WVMVariant *v) {
    if(v->type == VariantType_String && v->value.arr.elements.utf8Elements) {
        free(v->value.arr.elements.utf8Elements);
    }
}

static WVMArgSpec *contactGetInputArgs[] = { &asString };
static WVMSignature contactGetSig = { 1, contactGetInputArgs, &asString };
static WVMStatus contactGetInvoke(WVM *instance, WVMObjectReference ref, WVMVariant *vArgs, WVMVariant *vResultArgs) {
    static WVMError propNotFoundError = { "Error", "Property not found.", NULL };
    Contact *c = getContactByRef(ref);
    IContact *ic;
    const Field *f;

    if(c) {
        ic = GetAddressBook()->FindContactByID(idmap[c->id]);
    }
    if(ic && vArgs[0].type == VariantType_String && (f = getFieldByKey(vArgs[0].value.arr.elements.utf8Elements))) {
        vResultArgs->type = VariantType_String;
        vResultArgs->value.arr.elements.utf8Elements = (char *)f->get(ic);
        vResultArgs->count = strlen(vResultArgs->value.arr.elements.utf8Elements);
        vResultArgs->release = releaseString;
        return Error_NoError;
    }
    else {
        vResultArgs->type = VariantType_String;
        vResultArgs->value.errValue = &propNotFoundError;
        return Error_Native;
    }
}
static WVMMethodSpec contactGetMethod = { "get", &contactGetSig, contactGetInvoke };

This method takes a string as argument and returns a string as result. More importantly, note how the ref argument to this method is used. When the underlying contact object has been instantiated (in getContactByID using wvmf->createObjectRef), an object reference has been retrieved. This object reference is supplied as input argument on any call on this object. This means you can use WebVM to keep track of your objects.

Note that you need to release allocated strings if they aren’t static. In this example the

f->get(ic);

call returns the value of the given property as a string duplicate using strdup. That’s why the release callback on the WVMVariant is set to releaseString.

createContact

Now let’s consider the implementation of createContact, which takes a map as argument and has been introduced earlier. This example depends on some preliminary code which needs to be implemented in init:

static const char kfirstname[] = "firstname";
static const char klastname[]  = "lastname";
static const char kphone[]     = "phone";
static const char kemail[]     = "email";
static const char kcellphone[] = "cellphone";
static const char kstreet[]    = "street";
static const char kcity[]      = "city";
static const char kpostalcode[]= "postalcode";
static const char kcountry[]   = "country";

static WVMAtom firstnameAtom;
static WVMAtom lastnameAtom;
static WVMAtom phoneAtom;
static WVMAtom emailAtom;
static WVMAtom cellphoneAtom;
static WVMAtom streetAtom;
static WVMAtom cityAtom;
static WVMAtom postalcodeAtom;
static WVMAtom countryAtom;

static WVMStatus init(WVM          /* [in]  */   *instance,
              UInt16       /* [out] */   *classSpecCount,
              WVMClassSpec /* [out] */ ***classSpecs)
{
    wvcf->getAtomForString(instance, kfirstname, sizeof kfirstname, &firstnameAtom);
    wvcf->getAtomForString(instance, klastname, sizeof klastname, &lastnameAtom);
    wvcf->getAtomForString(instance, kphone, sizeof kphone, &phoneAtom);
    wvcf->getAtomForString(instance, kemail, sizeof kemail, &emailAtom);
    wvcf->getAtomForString(instance, kcellphone, sizeof kcellphone, &cellphoneAtom);
    wvcf->getAtomForString(instance, kstreet, sizeof kstreet, &streetAtom);
    wvcf->getAtomForString(instance, kpostalcode, sizeof kpostalcode, &postalcodeAtom);
    wvcf->getAtomForString(instance, kpostalcode, sizeof kcountry, &countryAtom);
    *classSpecCount = 2;
    *classSpecs     = classes;
    return Error_NoError;
}

In this preliminary code several WVMAtoms are initialized. This is because maps in WebVM are arrays of WVMMapEntry structs:

struct _WVMMapEntry {
    WVMAtom     id;
    WVMVariant  varValue;
};

An atom is an opaque structure for a given string which is unique throughout the WebVM environment. In the given example, firstnameAtom is a unique representation of the string “firstname” (kfirstname).

To understand a map as input to the createContact method, look at the earlier introduced JavaScript code:

var newcontact = {};
newcontact.firstname = "Peter";
newcontact.lastname  = "Miller";
newcontact.city      = "York";
newcontact.phone     = "01234567";

This input argument will be translated by WebVM into a WVMVariant of the type AtoMap and with the following array entries:

WVMVariant.value.arr.mapElements[0].id == firstnameAtom
WVMVariant.value.arr.mapElements[0].value.type == VariantType_String
WVMVariant.value.arr.mapElements[0].value.arr.elements.utf8Elements == "Peter"

WVMVariant.value.arr.mapElements[1].id == lastnameAtom
WVMVariant.value.arr.mapElements[1].value.type == VariantType_String
WVMVariant.value.arr.mapElements[1].value.arr.elements.utf8Elements == "Miller"

WVMVariant.value.arr.mapElements[2].id == cityAtom
WVMVariant.value.arr.mapElements[2].value.type == VariantType_String
WVMVariant.value.arr.mapElements[2].value.arr.elements.utf8Elements == "York"

WVMVariant.value.arr.mapElements[3].id == phoneAtom
WVMVariant.value.arr.mapElements[3].value.type == VariantType_String
WVMVariant.value.arr.mapElements[3].value.arr.elements.utf8Elements == "01234567"

WebVM provides a convenience function for dealing with maps however, which is used in the real implementation as follows:

static WVMStatus addressbookCreateContactInvoke(WVM *instance, WVMObjectReference ref, WVMVariant *vArgs, WVMVariant *vResultArgs) {
    static WVMError couldntCreateError = { "Error", "Could not create contact.", NULL };
    int result;
    IContact *c;
    WVMVariant v;
    WCHAR *wfirstname = NULL;
    WCHAR *wlastname = NULL;
    WCHAR *wphone = NULL;
    WCHAR *wcellphone = NULL;
    WCHAR *wemail = NULL;
    WCHAR *wstreet = NULL;
    WCHAR *wpostalcode = NULL;
    WCHAR *wcity = NULL;
    WCHAR *wcountry = NULL;

    if((result = wvcf->getMapEntry(instance, vArgs, firstnameAtom, VariantType_String, &v)) == Error_NoError) {
        utf82wchar(v.value.arr.elements.utf8Elements, (void **)&wfirstname);
    }
    if((result = wvcf->getMapEntry(instance, vArgs, lastnameAtom, VariantType_String, &v)) == Error_NoError) {
        utf82wchar(v.value.arr.elements.utf8Elements, (void **)&wlastname);
    }
    if((result = wvcf->getMapEntry(instance, vArgs, phoneAtom, VariantType_String, &v)) == Error_NoError) {
        utf82wchar(v.value.arr.elements.utf8Elements, (void **)&wphone);
    }
    if((result = wvcf->getMapEntry(instance, vArgs, cellphoneAtom, VariantType_String, &v)) == Error_NoError) {
        utf82wchar(v.value.arr.elements.utf8Elements, (void **)&wcellphone);
    }
    if((result = wvcf->getMapEntry(instance, vArgs, emailAtom, VariantType_String, &v)) == Error_NoError) {
        utf82wchar(v.value.arr.elements.utf8Elements, (void **)&wemail);
    }
    if((result = wvcf->getMapEntry(instance, vArgs, streetAtom, VariantType_String, &v)) == Error_NoError) {
        utf82wchar(v.value.arr.elements.utf8Elements, (void **)&wstreet);
    }
    if((result = wvcf->getMapEntry(instance, vArgs, postalcodeAtom, VariantType_String, &v)) == Error_NoError) {
        utf82wchar(v.value.arr.elements.utf8Elements, (void **)&wpostalcode);
    }
    if((result = wvcf->getMapEntry(instance, vArgs, cityAtom, VariantType_String, &v)) == Error_NoError) {
        utf82wchar(v.value.arr.elements.utf8Elements, (void **)&wcity);
    }
    if((result = wvcf->getMapEntry(instance, vArgs, countryAtom, VariantType_String, &v)) == Error_NoError) {
        utf82wchar(v.value.arr.elements.utf8Elements, (void **)&wcountry);
    }

    wstring firstname(wfirstname ? wfirstname : L"");
    wstring lastname(wlastname ? wlastname : L"");
    wstring phone(wphone ? wphone : L"");
    wstring cellphone(wcellphone ? wcellphone : L"");
    wstring email(wemail ? wemail : L"");
    wstring street(wstreet ? wstreet : L"");
    wstring postalcode(wpostalcode ? wpostalcode : L"");
    wstring city(wcity ? wcity : L"");
    wstring country(wcountry ? wcountry : L"");

    c = GetAddressBook()->AddContact(firstname, lastname, phone, cellphone, email, street, postalcode, city, country);

    if(wfirstname) free(wfirstname);
    if(wlastname) free(wlastname);
    if(wphone) free(wphone);
    if(wcellphone) free(wcellphone);
    if(wemail) free(wemail);
    if(wstreet) free(wstreet);
    if(wpostalcode) free(wpostalcode);
    if(wcity) free(wcity);
    if(wcountry) free(wcountry);


    if(c) {
        vResultArgs->type = VariantType_Int32Array;
        vResultArgs->value.intValue = getIndexForID(c->GetID());

        return Error_NoError;
    }
    else {
        vResultArgs->type = VariantType_Error;
        vResultArgs->value.errValue = &couldntCreateError;
        return Error_Native;
    }
}
static WVMMethodSpec addressbookCreateContactMethod = { "createContact", &addressbookCreateContactSig, addressbookCreateContactInvoke };

In this implementation the wvcf->getMapEntry function is used, which has been defined in webvm.h as follows:

typedef WVMStatus (*WVCGetMapEntryPtr) (WVM        /* [in]  */ *instance,
                    WVMVariant /* [in]  */ *map,
                    WVMAtom    /* [in]  */  id,
                    WVMVariantType /* [in]  */  type,
                    WVMVariant /* [out] */ *result);

This function takes the map argument, the atom you are looking for, and the type you expect as input, and will return Error_NoError if that map entry exists, and set the result variant accordingly. Otherwise it will return with an Error code. Usually you only need this for existence tests.

A general limitation of maps is that they cannot be specified with type information up front. This is why it is highly recommended to use the WVCGetMapEntryPtr call into WebVM for dealing with maps, because WebVM deals best with argument conversions of the given map handle from JavaScript.

Note that there is no way to guarantee that the expected values of a map are actually supplied by the web developer.

Property support

WebVM has got a new feature to query properties such as the version information of WebVM. Imagine the following example code to query for some default WebVM properties using JavaScript:

var version     = webvm.getProperty("webvm.version");
var vendor      = webvm.getProperty("webvm.vendor");
var description = webvm.getProperty("webvm.description");

This new approach can also be used for querying native module properties. Imagine the simple native module as in the http://example.webvm.net/simple example:

var simpleVersion     = webvm.getProperty("simple.version");
var simpleVendor      = webvm.getProperty("simple.vendor");
var simpleDescription = webvm.getProperty("simple.description"):

We created a new example to demonstrate this property functionality which can be found at

http://example.webvm.net/info

In order to support the new property functionality in native modules, we changed the getProperty module function to be exported as a dynamic loadable symbol called WVMGetProperty. WebVM will load a native module by the supplied property prefix and call this WVMGetProperty function of the module to query for particular properties. The simple example in the SDK has been updated accordingly.

To upgrade your module you need to perform the following two steps:

Replace the old getProperty implementation with something like the following function:

const char *WVMGetProperty(const char /* [in]  */  *key) {
    if(!strcmp(key, "vendor")) {
    return "<YOUR COMPANY>";
    }
    else if(!strcmp(key, "copyright")) {
    return "<YOUR COPYRIGHT>";
    }
    else if(!strcmp(key, "version")) {
    return "<YOUR VERSION>";
    }
    else if(!strcmp(key, "description")) {
    return "<YOUR DESCRIPTION>";
    }
    return NULL;
}

From Java interfaces to a JavaScript API using JIDL and WebVM

JIDL is a tool of the WebVM SDK which generates native module stub code out of Java interfaces in order to expose similiar API into JavaScript by using the WebVM browser plugin. It also generates OpenAjax Metadata Specification files, though it doesn’t process them as input format yet.

Java interface

Imagine the following two Java interfaces:

public interface TestInterface {
    public int getInt();

    public int[] getIntArray();

    public TestInterface2 getTestInterface2();

    public void setTestInterface2(TestInterface2 test2);

    public int add(int x, int y);
}

import java.util.Hashtable;

public interface TestInterface2 {

    public String getString();

    public void setMap(Hashtable table);
}

JavaScript representation

The above interfaces describe the following JavaScript API:

var ob = webvm.load("TestInterface");

var i = ob.getInt();
var arr[] = ob.getIntArray();

var ob2 = ob.getTestInterface2();

var ob3 = { getString: function() { return "JS String" }, setMap: function(x) { } };

ob.setTestInterface2(ob3);

var str = ob2.getString();

Generating native module stubs from Java interfaces

In order to generate native WebVM module stubs, the JIDL tool from the WebVM SDK should be used. It can be found in the directory

webvm-sdk-dir/tools

To generate native module implementation stubs for given Java interfaces, it is assumed that the class files of these interfaces are relative to the current directory. The first step is compiling class files from Java interface descriptions:

javac TestInterface.java
javac TestInterface2.java

Then JIDL can be used to generate the native module stubs:

mkdir result
java -jar jidl.jar result TestInterface TestInterface2

Afterwards there will be

result/TestInterface_decl.h
result/TestInterface_decl.c
result/TestInterface_impl.c
result/TestInterface2_decl.h
result/TestInterface2_decl.c
result/TestInterface2_impl.c

All files need to be included into your native module project, and only the _impl.c postfixed file needs to be edited in order to implement the interface.

A native module will also need an implementation of the entry points as follows:

#include "result/TestInterface_decl.h"
#include "result/TestInterface2_decl.h"

typedef struct {
    WVMCallbackFuncs  *wvcf;
    WVMObjectReference scriptableObjectRef;
} Instance;

static WVMClassSpec *classes[] = { &TestInterfaceClass, &TestInterface2Class };
static WVMStatus init(WVM          /* [in]  */   *instance,
              UInt16       /* [out] */   *classSpecCount,
              WVMClassSpec /* [out] */ ***classSpecs)
{
    *classSpecCount = 2;
    *classSpecs     = classes;
    return OK;
}


static void deinit(WVM /* [in] */ *instance) {

}

static WVMStatus start(WVM /* [in] */ *instance) {
    WVMStatus result;
    Instance *inst = (Instance *)instance->mData;

    result = inst->wvcf->createObjectRef(instance, classes[0], NULL, &inst->scriptableObjectRef);
    if(result == OK) {
        result = inst->wvcf->setScriptableObject(instance, inst->scriptableObjectRef);
    }
    return result;
}


static WVMStatus stop(WVM /* [in] */ *instance) {
    Instance *inst = (Instance *)instance->mData;
    return inst->wvcf->destroyObjectRef(instance, inst->scriptableObjectRef);
}

/****************************************************************************
 * Module entry/exit functions
 ****************************************************************************/
WVMStatus WVMAttach(WVM              /* [in]  */  *instance,
            WVMCallbackFuncs /* [in]  */  *wvcFuncs,
            WVMModuleFuncs   /* [out] */ **wvmFuncs)
{
    static WVMModuleFuncs wvmf = {
            sizeof(WVMModuleFuncs),
            { WEBVM_VERSION_MAJOR, WEBVM_VERSION_MINOR },
            init,
            deinit,
            start,
            stop,
        };
    Instance *inst = NULL;

    if(wvcFuncs->version.major < WEBVM_VERSION_MAJOR
    || (wvcFuncs->version.major == WEBVM_VERSION_MAJOR
        && wvcFuncs->version.minor < WEBVM_VERSION_MINOR))
    {
        return Error_Unsupported;
    }
    if(!(inst = (Instance *)malloc(sizeof(Instance)))) {
        return Error_Mem;
    }
    inst->wvcf = wvcFuncs;
    *wvmFuncs = &wvmf;
    instance->mData = inst;
    return OK;
}

void WVMDetach(WVM /* [in] */ *instance) { 
    Instance *inst = (Instance *)instance->mData;
    free(inst);
    instance->mData = NULL;
}

const char *WVMGetProperty(const char /* [in]  */  *key) {
    if(!strcmp(key, "vendor")) {
        return "YOUR COMPANY";
    }
    else if(!strcmp(key, "copyright")) {
        return "YOUR COPYRIGHT";
    }
    else if(!strcmp(key, "version")) {
        return "YOUR VERSION";
    }
    else if(!strcmp(key, "description")) {
        return "YOUR DESCRIPTION";
    }
    return NULL;
}

References

The complete implementation can be found here.

Discussion

Please subscribe to dev@webvm.net through sending a mail to dev+subscribe@webvm.net.

See also

How to create a WebVM module