When embedding v8, one of the pain points is how to call Java/Kotlin code from Javascript. It is not just a matter of setting a FunctionCallbackInfo<Value>
, but also of dealing with JNI. While there are really impressive exercises for automating JNI code calls, these are only valid suitable when you know your JNI needs upfront, e.g. method signatures, calling objects/classes, etc. More concretely, when you can compile your own v8 code. In my case, all my v8 is primarily embedded in Android, and I share around an AAR file with all needed v8 dependencies. How each project depending on this AAR exposes its own Javascript bindings, is as simple as annotating Java code with @Bridge
.
This post depicts how I built a dynamic bridge between Java and v8, and how methods annotated as @Bridge
are automatically exposed in javascript.
For example:
// Java class Test { @Bridge(returnsJSON = true) String method2() { return "{\"result\":0}"; } String method1(int a, int[] i32, double[] f64) { return ""; } } // Javascript > Test.method2(); > { result: 0 } > Test.method1(1, [1,2,3], new Float64Array([.1, .2, .3])); > "" // Javascript call with wrong Java parameter signature: > Test.method2(1,2); > null > Test.method1(32); > null
As nice as it might sound, it comes with a myriad of limitations:
- Method disambiguation. In Java we can have method overloading, which does not exist in Javascript.
- Javascript to Java and vice versa type conversion. Only
number, string, boolean, null, typed arrays
andarray, or object
of these types can be safely converted between environments. Arrays and Objects are defined recursively. - Java method signatures. In Javascript, you can call a function with an arbitrary number of parameters and types. Exposed Java functions are no exception. Only Javascript calls that match Java method signature (based on previous point types’ conversion) will be executed. By now, this is a reflection based method invocation. Slow, but convenient.
- Java method return types. While a Java method can virtually return anything, I constrained return types to String. Optionally, this string can be JSON parsed before setting the Javascript call return type. If any error has been caught,
null
will be returned instead. - Every Javascript call will pass through a unique JNI entry point.
- Asynchronous calls. These can not be directly modelled with this approach. But they work under other
@Bridge
annotation options. - It tends to be slow: reflection in Java + JNI bridge. Read on my APT articles on how to remove reflection calls.
Are all these limitations worth it ? it totally is for non critical code paths. In my case I use it for things like calling the speech synthesiser, open share dialogs, create gl textures… But definitely not for calling into every OpenGL function per frame for example.
Implementation details in an upcoming article version 2.