-
Notifications
You must be signed in to change notification settings - Fork 176
Doppio Developer Guide
NOTE: The Doppio native method interface will be getting a slight overhaul in the next release, currently being developed in the natives_refactor
branch. We will have a guide detailing how to transition any existing natives to this format once that branch merges into master
.
So you want to hack on doppio: maybe to integrate it into your project more closely, or to add support for JVM features that are currently busted, or whatever your heart desires. Start here to save yourself a lot of head-scratching.
Contents:
Native methods allow doppio to run Javascript code from Java classes. They're critical to the execution of the JVM, but they can also be used to provide web functionality to your Java programs. For example, you might write a native method to pop up an alert message, or manipulate DOM elements, or anything else you can do with Javascript.
First, identify the native method that you need to write:
is it in the Java Class Library somewhere, or perhaps in some Java code you wrote?
For this example, we'll assume we wrote the file classes/util/Test.java
like so:
package classes.util;
class Test {
static native int addTwo(int x);
public static void main(String[] args) {
System.out.println(addTwo(5));
}
}
You'll need to know its full package name, class name, and the internal JVM descriptor for the method. The easiest way to get that information is to try running it in doppio and looking for the error message. It'll look something like this:
$ ./doppio Test
Exception in thread "main" java.lang.UnsatisfiedLinkError: native method 'Lclasses/util/Test;::addTwo(I)I' not implemented: please fix or file a bug at https://github.com/int3/doppio/issues
at Test.addTwo(Native Method)
at Test.main(Test.java:4)
Now we know that the method's package/class name is classes/util/Test
and its descriptor is addTwo(I)I
.
(Here's what the descriptor actually says: L
denotes a class type (as opposed to a primitive like int
). This is then followed by the "qualified class name", i.e. the class name with its package name prefixed. The end of the class string is delimited by ;
. The double colon ::
indicates that a method signature is about to follow. The method signature consists of the method name, the parameter types in parentheses (I
indicates int
), and the return type is appended at the end.)
Next, we need to add the native method to the native_methods
object in src/natives.ts
.
The native_methods
object is a nested map, with a sub-object for each name in the package hierarchy.
Each class is a list of objects, which we create using the o
function to take care of the formatting details.
The first argument to o
is the function's descriptor (which we got from step 1), and the second is an anonymous function that runs when the native method is called.
So, in our example from Step 1, we would add code to look something like this:
native_methods = {
classes: {
util: {
Test: [
o('addTwo(I)I', function(rs: runtime.RuntimeState, x: number): number {
return x + 2;
})
]
}
}
}
The anonymous function's first argument (rs
) is the JVM's RuntimeState object, and the rest are any arguments to the native method.
In this example the native method is static, but if it wasn't, the second argument would be this
, the object the method is called on.
doppio's native method handling will automatically handle return values, so we simply return x + 2
and we're done.
For methods with a void return type, doppio ignores any return value.
If the above instructions aren't enough to get your native running, try looking at other native method implementations in natives.ts
.
These contain examples of how to do all sorts of tricky things, like getting/setting fields on objects,
converting between JVM and Javascript strings, working with long
integers, and more.
Our demo frontend certainly isn't perfect, and different applications might need different functionality. doppio was designed to be decoupled from its interface, but it currently needs a bit of care to integrate with a new client application.
TODO: write this