Setup •
Implementing Methods •
Implementing Fields •
Tips •
TODO •
Credits •
License
FalsoJNI (falso as in fake from Italian) is a simple, zero-dependency fake
JVM/JNI interface written in C.
It is created mainly to make JNI-heavy Android→PSVita ports easier, but probably
could be used for other purposes as well.
Since there are no dependencies, FalsoJNI is not supplied with a Makefile of its
own, so to get started just include in your own Makefile/CMakeLists.txt
all the source files:
FalsoJNI/FalsoJni.c
FalsoJNI/FalsoJni_ImplBridge.c
FalsoJNI/FalsoJni_Logger.c
FalsoJNI/converter.c
Second thing you need to do, is to create your own FalsoJNI_Impl file. You
will use it later to provide implementations for custom JNI Methods (the
ones called with jni->CallVoidMethodV and similars) and Fields.
To do this, from FalsoJNI_ImplSample.h copy the definitions between
COPY STARTING FROM HERE! and COPY UP TO HERE! to your project in any .c
file (you could also split it up into several files if you need to).
After that, you already init FalsoJNI and supply JNIEnv and JavaVM objects
to your client application, like this:
#include "FalsoJNI.h"
int main() {
// ...
jni_init(); // Initializes jvm and jni objects
int (*JNI_OnLoad)(JavaVM* jvm) = (void*)so_symbol(&so_mod,"JNI_OnLoad");
JNI_OnLoad(&jvm);
// ...
}
That’s it for the basic setup. In a theoretical situation where your client
application doesn’t use any Methods or Fields, you’re done here.
Otherwise, read on.
The easiest way to figure out which methods you need to implement is to
run the app as-is and look for FalsoJNI’s errors in logs, particularly with
GetMethodID / GetStaticMethodID functions:
[ERROR][/tmp/soloader/FalsoJNI.c:295][GetMethodID] [JNI] GetMethodID(env, 0x83561570, "SetShiftEnabled", "(Z)V"): not found
[ERROR][/tmp/soloader/FalsoJNI.c:295][GetMethodID] [JNI] GetMethodID(env, 0x83561570, "Shutdown", "()V"): not found
Two important things you get from this log are the method name
("SetShiftEnabled") and the method signature ("(Z)V").
You can learn what each symbol in Java type signature means here.
To cut on the long details, here are a few self-explanatory examples of how
Java method signatures are translated into FalsoJNI-compatible implementations:
// FalsoJNI always passes arguments as a va_list to be able to make single
// function implementation no matter how it is called (i.e. CallMethod,
// CallMethodV, or CallMethodA ).
// "SetShiftEnabled", "(Z)V"
void SetShiftEnabled(jmethodID id, va_list args) { // V (ret type) is a void
jboolean arg = va_arg(args, jboolean); // Z is a boolean
// do something
}
// "GetDisplayOrientationLock", "()I"
jint GetDisplayOrientationLock(jmethodID id, va_list args) { // I (ret type) is an integer
// no arguments here
return 0;
}
// "GetUsername", "(Ljava/lang/String;)Ljava/lang/String;"
jstring GetUsername(jmethodID id, va_list args) { // Ljava/lang/String; (ret type) is a jstring
jstring _email = va_arg(args, jstring);
// If you want to work with Java strings, always use respective JNI methods!
// They are NOT c-strings.
const char * email = jni->GetStringUTFChars(&jni, _email, NULL);
const char * username = MyCoolFunctionToLookupUsername(_email);
jni->ReleaseStringUTFChars(&jni, _email, email);
return jni->NewStringUTF(&jni, username);
}
// "read", "([BII)I"
jint InputStream_read(jmethodID id, va_list args) { // I (ret type) is an integer
jbyteArray _b = va_arg(args, char*); // [B is a byte array.
jint off = va_arg(args, int); // I is an int
jint len = va_arg(args, int); // I is an int
// Before accessing/changing the array elements, we have to do the following:
JavaDynArray * jda = (JavaDynArray *) _b;
if (!jda) {
log_error("[java.io.InputStream.read()] Provided buffer is not a valid JDA.");
return 0;
}
char * b = jda->array; // Now this array we can work with
}
Pay great attention to the last example. Java arrays are notably different
from C arrays by always having the array size information with them, so
FalsoJNI mimics Java arrays behavior with a special struct, JavaDynArray
(or jda in short).
Every time you receive an array of any kind as an argument, you have to get
the “real”, underlying array from it like shown in the example. You can also
use jda_sizeof(JavaDynArr *) function to get the length of the array you
are operating on.
If you need to return an array in Java method implementation, — likewise.
Work with jda->array, return jda.
Also notice the second-to-last example to see how you can work with Java strings.
Now that you have your implementations in place, the only thing left to do
to allow the client application to use them is to fill in the arrays in the
implementation file you copied from FalsoJNI_ImplSample.h earlier.
You just need to figure out the return types for your methods and come up with
any (unique!) method IDs you like. Example of filling the arrays for methods
from Step 1:
NameToMethodID nameToMethodId[] = {
{ 100, "SetShiftEnabled", METHOD_TYPE_VOID },
{ 101, "GetDisplayOrientationLock", METHOD_TYPE_INT },
{ 102, "read", METHOD_TYPE_INT },
};
MethodsVoid methodsVoid[] = {
{ 100, SetShiftEnabled }
};
MethodsInt methodsInt[] = {
{ 101, GetDisplayOrientationLock },
{ 102, InputStream_read }
};
With Fields, it’s basically the same thing. Run your app, look for the errors
in GetFieldID, GetStaticFieldID to figure out the needed Fields names and
signatures (well, just types in this case).
When you know them, fill in the arrays in the same fashion:
NameToFieldID nameToFieldId[] = {
{ 8, "screenWidth", FIELD_TYPE_INT },
{ 9, "screenHeight", FIELD_TYPE_INT },
{ 10, "is_licensed", FIELD_TYPE_BOOLEAN }
};
FieldsBoolean fieldsBoolean[] = {
{ 10, JNI_TRUE }
};
FieldsInt fieldsInt[] = {
{ 8, 960 },
{ 9, 544 },
};
Everything else will be taken care of by FalsoJNI.
- There is a very verbose logging in this lib to debug difficult situations.
Either defineFALSOJNI_DEBUGLEVELor editFalsoJNI.hif you need to change
the verbosity level:
#define FALSOJNI_DEBUG_NO 4
#define FALSOJNI_DEBUG_ERROR 3
#define FALSOJNI_DEBUG_WARN 2
#define FALSOJNI_DEBUG_INFO 1
#define FALSOJNI_DEBUG_ALL 0
#ifndef FALSOJNI_DEBUGLEVEL
#define FALSOJNI_DEBUGLEVEL FALSOJNI_DEBUG_WARN
#endif
-
There are things in JNI that can not be implemented without some terrible
overengineering. If you come across one of them, the library will throw
a warning-level log at you. -
I tried to keep the code as clean and self-explanatory as possible, but
didn’t have time yet to write a proper documentation. As a direction for
further info, look atFalsoJNI_ImplBridge.hheader for common type definitions
and JDA functions. -
Oracle JNI spec is your friend.
- Exception handling. They are completely ignored now.
- GetArrayLength for ObjectField values. (if needed?)
- MonitorEnter/MonitorExit (per-javaobject semaphores).
- DirectByteBuffers.
- Keep track of references and destroy objects when there aren’t any left.
- Dry Run mode that would record methods/fields definitions to
FalsoJNI_Impl.cfor you.
- TheFloW and Rinnegatamante for fake JNI interfaces implementations
in gtasa_vita that
served as inspiration and basis for this lib.
This software may be modified and distributed under the terms of
the MIT license. See the LICENSE file for details.
Contains parts of Dalvik implementation of JNI interfaces,
copyright (C) 2008 The Android Open Source Project,
licensed under the Apache License, Version 2.0.
Includes converter.c and converter.h,
copyright (C) 2015 Jonathan Bennett jon@autoitscript.com,
licensed under the Apache License, Version 2.0.
Leave a Reply