Generates a
Dalvik
EXecutable (dex)
file for execution on Android. Dex files define classes and interfaces,
including their member methods and fields, executable code, and debugging
information. They also define annotations, though this API currently has no
facility to create a dex file that contains annotations.
This library is intended to satisfy two use cases:
- For runtime code generation. By embedding this library
in your Android application, you can dynamically generate and load
executable code. This approach takes advantage of the fact that the
host environment and target environment are both Android.
- For compile time code generation. You may use this
library as a part of a compiler that targets Android. In this scenario
the generated dex file must be installed on an Android device before it
can be executed.
Example: Fibonacci
To illustrate how this API is used, we'll use DexMaker to generate a class
equivalent to the following Java source:
package com.publicobject.fib;
public class Fibonacci {
public static int fib(int i) {
if (i < 2) {
return i;
}
return fib(i - 1) + fib(i - 2);
}
}
We start by creating a TypeId to identify the generated Fibonacci class. DexMaker identifies types by their internal names like
Ljava/lang/Object; rather than their Java identifiers like java.lang.Object.
TypeId<?> fibonacci = TypeId.get("Lcom/google/dexmaker/examples/Fibonacci;");
Next we declare the class. It allows us to specify the type's source file
for stack traces, its modifiers, its superclass, and the interfaces it
implements. In this case, Fibonacci is a public class that extends
from Object:
String fileName = "Fibonacci.generated";
DexMaker dexMaker = new DexMaker();
dexMaker.declare(fibonacci, fileName, Modifier.PUBLIC, TypeId.OBJECT);
It is illegal to declare members of a class without also declaring the class
itself.
To make it easier to go from our Java method to dex instructions, we'll
manually translate it to pseudocode fit for an assembler. We need to replace
control flow like if() blocks and for() loops with labels and
branches. We'll also avoid performing multiple operations in one statement,
using local variables to hold intermediate values as necessary:
int constant1 = 1;
int constant2 = 2;
if (i < constant2) goto baseCase;
int a = i - constant1;
int b = i - constant2;
int c = fib(a);
int d = fib(b);
int result = c + d;
return result;
baseCase:
return i;
We look up the MethodId for the method on the declaring type. This
takes the method's return type (possibly TypeId#VOID), its name and
its parameters types. Next we declare the method, specifying its modifiers by
bitwise ORing constants from java.lang.reflect.Modifier. The declare
call returns a Code object, which we'll use to define the method's
instructions.
MethodId<?, Integer> fib = fibonacci.getMethod(TypeId.INT, "fib", TypeId.INT);
Code code = dexMaker.declare(fib, Modifier.PUBLIC | Modifier.STATIC);
One limitation of DexMaker's API is that it requires all local
variables to be created before any instructions are emitted. Use newLocal() to create a new local variable. The method's
parameters are exposed as locals using getParameter(). For non-static methods the this pointer is exposed
using getThis(). Here we declare all of the local
variables that we'll need for our fib() method:
Local<Integer> i = code.getParameter(0, TypeId.INT);
Local<Integer> constant1 = code.newLocal(TypeId.INT);
Local<Integer> constant2 = code.newLocal(TypeId.INT);
Local<Integer> a = code.newLocal(TypeId.INT);
Local<Integer> b = code.newLocal(TypeId.INT);
Local<Integer> c = code.newLocal(TypeId.INT);
Local<Integer> d = code.newLocal(TypeId.INT);
Local<Integer> result = code.newLocal(TypeId.INT);
Notice that Local has a type parameter of Integer. This is
useful for generating code that works with existing types like String
and Integer, but it can be a hindrance when generating code that
involves new types. For this reason you may prefer to use raw types only and
add @SuppressWarnings("unsafe") on your calling code. This will yield
the same result but you won't get IDE support if you make a type error.
We're ready to start defining our method's instructions. The Code
class catalogs the available instructions and their use.
code.loadConstant(constant1, 1);
code.loadConstant(constant2, 2);
Label baseCase = new Label();
code.compare(Comparison.LT, baseCase, i, constant2);
code.op(BinaryOp.SUBTRACT, a, i, constant1);
code.op(BinaryOp.SUBTRACT, b, i, constant2);
code.invokeStatic(fib, c, a);
code.invokeStatic(fib, d, b);
code.op(BinaryOp.ADD, result, c, d);
code.returnValue(result);
code.mark(baseCase);
code.returnValue(i);
We're done defining the dex file. We just need to write it to the
filesystem or load it into the current process. For this example we'll load
the generated code into the current process. This only works when the current
process is running on Android. We use generateAndLoad() which takes the class loader that will be used as our
generated code's parent class loader. It also requires a directory where
temporary files can be written.
ClassLoader loader = dexMaker.generateAndLoad(
FibonacciMaker.class.getClassLoader(), getDataDirectory());
Finally we'll use reflection to lookup our generated class on its class
loader and invoke its
fib() method:
Class<?> fibonacciClass = loader.loadClass("com.google.dexmaker.examples.Fibonacci");
Method fibMethod = fibonacciClass.getMethod("fib", int.class);
System.out.println(fibMethod.invoke(null, 8));