Created
August 21, 2011 09:40
-
-
Save diosmosis/1160394 to your computer and use it in GitHub Desktop.
JNI Facade
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <jni.h> | |
#include <mapper.h> | |
/* Maps the following fictitious Java classes. | |
class MyEntity { | |
MyEntity(int id, String name); | |
int getId(); | |
void setId(int id); | |
String name(); | |
void setName(String name); | |
} | |
class MyDao { | |
MyDao(); | |
MyEntity createAnEntity(int id, String name); | |
void deleteAnEntity(int id); | |
} | |
*/ | |
Type MyDao = {"com/my/pkg/MyDao"}, | |
MyEntity = {"com/my/pkg/MyEntity"} | |
; | |
/* MyEntity mappings */ | |
void MyEntity_new (Instance * result, jint id, jstring name) { | |
static MethodInfo method_info = {METHOD_TYPE_CONSTRUCTOR, &MyEntity, "<init>", 2, "(ILjava/lang/String;)V"}; | |
result->__type__ = &MyEntity; | |
result->instance = JVALUE_AS_OBJECT (call_method(result, &method_info, id, name)); | |
} | |
jint MyEntity_getId (Instance * self) { | |
static MethodInfo method_info = {METHOD_TYPE_INT, &MyEntity, "getId", 0, "()I"}; | |
return JVALUE_AS_INT (call_method(self, &method_info)); | |
} | |
void MyEntity_setId (Instance * self, jint id) { | |
static MethodInfo method_info = {METHOD_TYPE_VOID, &MyEntity, "setId", 1, "(I)V"}; | |
call_method (self, &method_info, id); | |
} | |
jstring MyEntity_getName (Instance * self) { | |
static MethodInfo method_info = {METHOD_TYPE_OBJECT, &MyEntity, "getName", 0, "()Ljava/lang/String;"}; | |
return (jstring)JVALUE_AS_OBJECT (call_method(self, &method_info)); | |
} | |
void MyEntity_setName (Instance * self, jstring name) { | |
static MethodInfo method_info = {METHOD_TYPE_VOID, &MyEntity, "setString", 1, "(Ljava/lang/String;)V"}; | |
call_method (self, &method_info, name); | |
} | |
/* MyDao bindings */ | |
void MyDao_new (Instance * result) { | |
static MethodInfo method_info = {METHOD_TYPE_CONSTRUCTOR, &MyDao, "<init>", 0, "()V"}; | |
result->__type__ = &MyDao; | |
result->instance = JVALUE_AS_OBJECT (call_method (result, &method_info)); | |
} | |
void MyDao_createAnEntity (Instance * result, Instance * self, jint id, jstring name) { | |
static MethodInfo method_info = {METHOD_TYPE_OBJECT, &MyDao, "createAnEntity", 2, "(ILjava/lang/String;)Lcom/my/pkg/MyEntity;"}; | |
result->__type__ = &MyEntity; | |
result->instance = JVALUE_AS_OBJECT (call_method(self, &method_info, id, name)); | |
} | |
void MyDao_deleteAnEntity (Instance * self, jint id) { | |
static MethodInfo method_info = {METHOD_TYPE_VOID, &MyDao, "deleteAnEntity", 1, "(I)V"}; | |
call_method (self, &method_info, id); | |
} | |
int init_my_types (JNIEnv * env) { | |
if (! init_type (env, &MyDao)) { | |
return 0; | |
} | |
if (! init_type (env, &MyEntity)) { | |
return 0; | |
} | |
return 1; | |
} | |
int main(int argc, char ** argv) { | |
JavaVM * jvm = 0; | |
JNIEnv * env = 0; | |
JavaVMInitArgs args; | |
JavaVMOption options[1]; | |
Instance dao, entity; | |
/* create the JVM */ | |
options[0].optionString = "-Djava.class.path=./example.jar"; | |
args.version = JNI_VERSION_1_6; | |
args.nOptions = 1; | |
args.options = options; | |
args.ignoreUnrecognized = JNI_FALSE; | |
JNI_CreateJavaVM (&jvm, (void **)&env, &args); | |
/* init type metadata */ | |
init_my_types (env); | |
/* create a dao */ | |
MyDao_new (&dao); | |
/* create an entity */ | |
MyDao_createAnEntity (&entity, &dao, 54, (*env)->NewStringUTF (env, "the new entity")); | |
/* delete the entity */ | |
MyDao_deleteAnEntity (&dao, MyEntity_getId (&entity)); | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "mapper.h" | |
static void (*__exception_raiser)(char const* format, va_list args) = 0; | |
void set_exception_raiser( void (*f)(char const* format, va_list args) ) { | |
__exception_raiser = f; | |
} | |
void raise_exception (char const* fmt, ...) { | |
va_list args; | |
va_start (args, fmt); | |
if (__exception_raiser != 0) { | |
__exception_raiser (fmt, args); | |
} else { | |
vprintf (fmt, args); | |
} | |
va_end (args); | |
} | |
/* type/method initing methods */ | |
int init_type (JNIEnv * jenv, Type * type) { | |
type->jenv = jenv; | |
type->__class__ = (*jenv)->FindClass (jenv, type->name); | |
if (! type->__class__) { | |
raise_exception ("Couldn't load the '%s' type.", type->name); | |
return 0; | |
} | |
return 1; | |
} | |
static int init_method (JNIEnv * env, MethodInfo * info) { | |
Type * type = info->type; | |
if (! type->__class__) { | |
if (! init_type (env, type)) { | |
return 0; | |
} | |
} | |
if (info->method_type <= METHOD_TYPE_CONSTRUCTOR) { | |
info->method = (*env)->GetMethodID (env, type->__class__, info->name, info->sig); | |
} else { | |
info->method = (*env)->GetStaticMethodID (env, type->__class__, info->name, info->sig); | |
} | |
if (! info->method) { | |
raise_exception ("Couldn't find the '%s.%s' method.", info->name, info->name); | |
return 0; | |
} | |
return 1; | |
} | |
static void java_exception_reraise (JNIEnv * env, jthrowable exc) { | |
static jclass Throwable = 0; | |
static jmethodID Throwable_getMessage = 0; | |
char const* message_str = 0; | |
jstring message; | |
if (! Throwable_getMessage) { | |
if (! Throwable) { | |
Throwable = (*env)->FindClass (env, "java/lang/Throwable"); | |
if (! Throwable) { | |
raise_exception ("Couldn't get the Throwable class!"); | |
return; | |
} | |
} | |
Throwable_getMessage = (*env)->GetMethodID (env, Throwable, "getMessage", "()Ljava/lang/String;"); | |
if (! Throwable_getMessage) { | |
raise_exception ("Couldn't get the Throwable.getMessage method!"); | |
return; | |
} | |
} | |
message = (*env)->CallObjectMethod (env, exc, Throwable_getMessage); | |
if (message) { | |
message_str = (*env)->GetStringUTFChars (env, message, NULL); | |
} | |
if (message_str) { | |
raise_exception (message_str); | |
(*env)->ReleaseStringUTFChars (env, message, message_str); | |
} else { | |
raise_exception ("Unknown Java exception... (no message)."); | |
} | |
(*env)->ExceptionDescribe (env); | |
(*env)->ExceptionClear (env); | |
} | |
jvalue call_method (Instance * self, MethodInfo * info, ...) { | |
jvalue result; | |
jthrowable exc; | |
JNIEnv * env; | |
va_list vargs; | |
env = self->__type__->jenv; | |
if (! info->method) { | |
if (! init_method (env, info)) { | |
return result; | |
} | |
} | |
va_start (vargs, info); | |
switch (info->method_type) { | |
case METHOD_TYPE_BOOL: | |
JVALUE_AS_BOOL (result) = (*env)->CallBooleanMethodV (env, self->instance, info->method, vargs); | |
break; | |
case METHOD_TYPE_BYTE: | |
JVALUE_AS_BYTE (result) = (*env)->CallByteMethodV (env, self->instance, info->method, vargs); | |
break; | |
case METHOD_TYPE_CHAR: | |
JVALUE_AS_CHAR (result) = (*env)->CallCharMethodV (env, self->instance, info->method, vargs); | |
break; | |
case METHOD_TYPE_SHORT: | |
JVALUE_AS_SHORT (result) = (*env)->CallShortMethodV (env, self->instance, info->method, vargs); | |
break; | |
case METHOD_TYPE_INT: | |
JVALUE_AS_INT (result) = (*env)->CallIntMethodV (env, self->instance, info->method, vargs); | |
break; | |
case METHOD_TYPE_LONG: | |
JVALUE_AS_LONG (result) = (*env)->CallLongMethodV (env, self->instance, info->method, vargs); | |
break; | |
case METHOD_TYPE_FLOAT: | |
JVALUE_AS_FLOAT (result) = (*env)->CallFloatMethodV (env, self->instance, info->method, vargs); | |
break; | |
case METHOD_TYPE_DOUBLE: | |
JVALUE_AS_DOUBLE (result) = (*env)->CallDoubleMethodV (env, self->instance, info->method, vargs); | |
break; | |
case METHOD_TYPE_OBJECT: | |
JVALUE_AS_OBJECT (result) = (*env)->CallObjectMethodV (env, self->instance, info->method, vargs); | |
break; | |
case METHOD_TYPE_VOID: | |
(*env)->CallVoidMethodV (env, self->instance, info->method, vargs); | |
break; | |
case METHOD_TYPE_STATIC_BOOL: | |
JVALUE_AS_BOOL (result) = (*env)->CallStaticBooleanMethodV (env, self->instance, info->method, vargs); | |
break; | |
case METHOD_TYPE_STATIC_BYTE: | |
JVALUE_AS_BYTE (result) = (*env)->CallStaticByteMethodV (env, self->instance, info->method, vargs); | |
break; | |
case METHOD_TYPE_STATIC_CHAR: | |
JVALUE_AS_CHAR (result) = (*env)->CallStaticCharMethodV (env, self->instance, info->method, vargs); | |
break; | |
case METHOD_TYPE_STATIC_SHORT: | |
JVALUE_AS_SHORT (result) = (*env)->CallStaticShortMethodV (env, self->instance, info->method, vargs); | |
break; | |
case METHOD_TYPE_STATIC_INT: | |
JVALUE_AS_INT (result) = (*env)->CallStaticIntMethodV (env, self->instance, info->method, vargs); | |
break; | |
case METHOD_TYPE_STATIC_LONG: | |
JVALUE_AS_LONG (result) = (*env)->CallStaticLongMethodV (env, self->instance, info->method, vargs); | |
break; | |
case METHOD_TYPE_STATIC_FLOAT: | |
JVALUE_AS_FLOAT (result) = (*env)->CallStaticFloatMethodV (env, self->instance, info->method, vargs); | |
break; | |
case METHOD_TYPE_STATIC_DOUBLE: | |
JVALUE_AS_DOUBLE (result) = (*env)->CallStaticDoubleMethodV (env, self->instance, info->method, vargs); | |
break; | |
case METHOD_TYPE_STATIC_OBJECT: | |
JVALUE_AS_OBJECT (result) = (*env)->CallStaticObjectMethodV (env, self->instance, info->method, vargs); | |
break; | |
case METHOD_TYPE_STATIC_VOID: | |
(*env)->CallStaticVoidMethodV (env, self->instance, info->method, vargs); | |
break; | |
case METHOD_TYPE_CONSTRUCTOR: | |
JVALUE_AS_OBJECT (result) = (*env)->NewObjectV (env, self->__type__->__class__, info->method, vargs); | |
break; | |
default: | |
raise_exception ("Unknown method type '%i' in specification.", info->method_type); | |
break; | |
} | |
va_end (vargs); | |
exc = (*env)->ExceptionOccurred (env); | |
if (exc) { | |
java_exception_reraise (env, exc); | |
} | |
return result; | |
} | |
jvalue unbox (JNIEnv * env, jobject obj, unsigned int boxed_type) { | |
static jclass number_class = 0 | |
, char_class = 0 | |
; | |
static jmethodID byteValue_method = 0 | |
, doubleValue_method = 0 | |
, floatValue_method = 0 | |
, intValue_method = 0 | |
, longValue_method = 0 | |
, shortValue_method = 0 | |
, charValue_method = 0 | |
; | |
jclass temp; | |
jvalue result; | |
/* load metadata (class pointers/methods) */ | |
if (! number_class) { | |
temp = (*env)->FindClass (env, "java/lang/Number"); | |
if (! temp) { | |
raise_exception ("Couldn't find the java.lang.Number class."); | |
return result; | |
} | |
if (! byteValue_method) { | |
byteValue_method = (*env)->GetMethodID (env, number_class, "byteValue", "()B"); | |
if (! byteValue_method) { | |
raise_exception ("Couldn't find the java.lang.Number.byteValue method."); | |
return result; | |
} | |
} | |
if (! doubleValue_method) { | |
doubleValue_method = (*env)->GetMethodID (env, number_class, "doubleValue", "()D"); | |
if (! doubleValue_method) { | |
raise_exception ("Couldn't find the java.lang.Number.doubleValue method."); | |
return result; | |
} | |
} | |
if (! floatValue_method) { | |
floatValue_method = (*env)->GetMethodID (env, number_class, "floatValue", "()F"); | |
if (! floatValue_method) { | |
raise_exception ("Couldn't find the java.lang.Number.floatValue method."); | |
return result; | |
} | |
} | |
if (! intValue_method) { | |
intValue_method = (*env)->GetMethodID (env, number_class, "intValue", "()I"); | |
if (! intValue_method) { | |
raise_exception ("Couldn't find the java.lang.Number.intValue method."); | |
return result; | |
} | |
} | |
if (! longValue_method) { | |
longValue_method = (*env)->GetMethodID (env, number_class, "longValue", "()J"); | |
if (! longValue_method) { | |
raise_exception ("Couldn't find the java.lang.Number.longValue method."); | |
return result; | |
} | |
} | |
if (! shortValue_method) { | |
shortValue_method = (*env)->GetMethodID (env, number_class, "shortValue", "()S"); | |
if (! shortValue_method) { | |
raise_exception ("Couldn't find the java.lang.Number.shortValue method."); | |
return result; | |
} | |
} | |
number_class = temp; | |
} | |
if (! char_class) { | |
temp = (*env)->FindClass (env, "java/lang/Character"); | |
if (! temp) { | |
raise_exception ("Couldn't find the java.lang.Character class."); | |
return result; | |
} | |
if (! charValue_method) { | |
charValue_method = (*env)->GetMethodID (env, char_class, "charValue", "()C"); | |
if (! charValue_method) { | |
raise_exception ("Couldn't find the java.lang.Character.charValue method."); | |
return result; | |
} | |
} | |
char_class = temp; | |
} | |
switch (boxed_type) { | |
case BOXED_TYPE_CHAR: | |
JVALUE_AS_CHAR (result) = (*env)->CallCharMethod (env, obj, charValue_method); | |
break; | |
case BOXED_TYPE_BYTE: | |
JVALUE_AS_BYTE (result) = (*env)->CallByteMethod (env, obj, byteValue_method); | |
break; | |
case BOXED_TYPE_DOUBLE: | |
JVALUE_AS_DOUBLE (result) = (*env)->CallDoubleMethod (env, obj, doubleValue_method); | |
break; | |
case BOXED_TYPE_FLOAT: | |
JVALUE_AS_FLOAT (result) = (*env)->CallFloatMethod (env, obj, floatValue_method); | |
break; | |
case BOXED_TYPE_INT: | |
JVALUE_AS_INT (result) = (*env)->CallIntMethod (env, obj, intValue_method); | |
break; | |
case BOXED_TYPE_LONG: | |
JVALUE_AS_LONG (result) = (*env)->CallLongMethod (env, obj, longValue_method); | |
break; | |
case BOXED_TYPE_SHORT: | |
JVALUE_AS_SHORT (result) = (*env)->CallShortMethod (env, obj, shortValue_method); | |
break; | |
default: | |
raise_exception ("Invalid boxed_type parameter '%i' given to unbox function.", boxed_type); | |
break; | |
} | |
return result; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef JNI_FACADE_MAPPER_H | |
#include <stdarg.h> | |
#include <jni.h> | |
/* The following macros make using the jvalue type much clearer. */ | |
#define JVALUE_AS_BOOL(jv) jv.z | |
#define JVALUE_AS_BYTE(jv) jv.b | |
#define JVALUE_AS_CHAR(jv) jv.c | |
#define JVALUE_AS_SHORT(jv) jv.s | |
#define JVALUE_AS_INT(jv) jv.i | |
#define JVALUE_AS_LONG(jv) jv.j | |
#define JVALUE_AS_FLOAT(jv) jv.f | |
#define JVALUE_AS_DOUBLE(jv) jv.d | |
#define JVALUE_AS_OBJECT(jv) jv.l | |
/* Two macros that retrieve the JEnv pointer from Type or Instance pointers. */ | |
#define JENV_FROM_TYPE(type) type->jenv | |
#define JENV_FROM_INSTANCE(self) JENV_FROM_TYPE (self->__type__) | |
/* An enum used to classify mapped Java methods by the scope of the method | |
(static or instance), the return type and whether its a constructor. */ | |
enum | |
{ | |
METHOD_TYPE_BOOL = 0, | |
METHOD_TYPE_BYTE, | |
METHOD_TYPE_CHAR, | |
METHOD_TYPE_SHORT, | |
METHOD_TYPE_INT, | |
METHOD_TYPE_LONG, | |
METHOD_TYPE_FLOAT, | |
METHOD_TYPE_DOUBLE, | |
METHOD_TYPE_OBJECT, | |
METHOD_TYPE_VOID, | |
METHOD_TYPE_CONSTRUCTOR, | |
METHOD_TYPE_STATIC_BOOL, | |
METHOD_TYPE_STATIC_BYTE, | |
METHOD_TYPE_STATIC_CHAR, | |
METHOD_TYPE_STATIC_SHORT, | |
METHOD_TYPE_STATIC_INT, | |
METHOD_TYPE_STATIC_LONG, | |
METHOD_TYPE_STATIC_FLOAT, | |
METHOD_TYPE_STATIC_DOUBLE, | |
METHOD_TYPE_STATIC_OBJECT, | |
METHOD_TYPE_STATIC_VOID | |
}; | |
/* An enum used to describe the value of a boxed object. */ | |
enum | |
{ | |
BOXED_TYPE_CHAR = 0, | |
BOXED_TYPE_BYTE, | |
BOXED_TYPE_DOUBLE, | |
BOXED_TYPE_FLOAT, | |
BOXED_TYPE_INT, | |
BOXED_TYPE_LONG, | |
BOXED_TYPE_SHORT | |
}; | |
/* The MethodInfo struct holds all metadata needed to find a method's | |
jmethodID. */ | |
typedef struct { | |
/** | |
* An integer that classifies the method by scope, return type & whether | |
* it's a constructor or not. See the METHOD_TYPE enum above for possible | |
* values. | |
* | |
* This must be set when initializing. | |
*/ | |
unsigned int method_type; | |
/** | |
* The Type instance that describes the Java class this method belongs | |
* to. | |
* | |
* This must be set when initializng. | |
*/ | |
struct _Type * type; | |
/** | |
* The name of the method. | |
* | |
* This must be set when initializing. | |
*/ | |
char const* name; | |
/** | |
* The number of parameter the method accepts. | |
* | |
* This must be set when initializing. | |
*/ | |
unsigned int arg_n; | |
/** | |
* The JNI signature describing the method. | |
* | |
* This must be set when initializing. | |
*/ | |
char const* sig; | |
/** | |
* The jmethodID used when calling the method. | |
* | |
* This must NOT be initialized. It is set by the framework. | |
*/ | |
jmethodID method; | |
} MethodInfo; | |
/* The Type struct holds all data needed to find the jclass pointer of a Java | |
class. */ | |
typedef struct _Type { | |
/** | |
* The JNI name of the type (ie, java/lang/String). | |
* | |
* This must be set when initializing. | |
*/ | |
char const* name; | |
/** | |
* The jclass used when getting method IDs. | |
* | |
* This is set by the framework and should NOT be initialized. | |
*/ | |
jclass __class__; | |
/** | |
* The JNIEnv instance used to find the jclass. It is stored here to make | |
* calling methods a bit simpler. | |
* | |
* This is set by the framework and should NOT be initialized. | |
*/ | |
JNIEnv * jenv; | |
} Type; | |
/* The Instance struct pairs a jobject pointer with its associated Type | |
metadata, which makes it simpler to call methods on it. */ | |
typedef struct { | |
/** | |
* A pointer to the metadata describing the Java class of this instance. | |
*/ | |
Type * __type__; | |
/** | |
* A pointer to the Java object. | |
*/ | |
jobject instance; | |
} Instance; | |
/** | |
* When an exception occurs in invoked Java code, it is, by default, printed | |
* out and ignored. If you want to do something else, like raise an exception | |
* in another runtime (ie, python), call this function with your own exception | |
* handling function. | |
* | |
* An exception raiser function takes a format string message and a va_list as | |
* parameters. It must be thread safe. | |
*/ | |
void set_exception_raiser( void (*f)(char const* format, va_list args) ); | |
/** | |
* Calls the current exception raiser. See set_exception_raiser for more info. | |
* | |
* Params: | |
* fmt: A format string message describing the error. | |
* ...: The values to bind to fmt. | |
*/ | |
void raise_exception (char const* fmt, ...); | |
/** | |
* Calls the method described by 'info' on 'self'. If an exception is thrown | |
* the set exception raiser will be called. See set_exception_raiser for | |
* details. | |
* | |
* NOTE: Calling methods will work in a multi-threaded environment, but there | |
* might be multiple calls to FindClass & Get(Static)MethodID. Also, if there | |
* are bugs in your mapping code, multiple exceptions might be thrown. This | |
* won't break anything since it means there's a bug in the code, but its | |
* likely to be annoying. | |
* | |
* Params: | |
* self: The instance that the method is being called upon. This must be | |
* non-null, even if calling a constructor. If calling a constructor, | |
* only the __type__field is required to be valid. | |
* info: A pointer to the MethodInfo instance describing the method to call. | |
* ...: The args to pass to the method. | |
* | |
* Returns: | |
* The method result. | |
*/ | |
jvalue call_method (Instance * self, MethodInfo * info, ...); | |
/** | |
* Inits a Type instance which describes a java type. | |
* | |
* Params: | |
* jenv: The Java Environment to use when looking for methods. | |
* type: A pointer to the Type instance that describes a Java class. | |
* | |
* Returns: | |
* 1 upon success and 0 upon failure. | |
*/ | |
int init_type (JNIEnv * jenv, Type * type); | |
/** | |
* Unboxes a boxed value type. | |
* | |
* Params: | |
* env: The Java Environment to use when unboxing. | |
* obj: The boxed object. | |
* boxed_type: An integer describing the boxed value type. See the BOXED_TYPE | |
* enum above. | |
* | |
* Returns: | |
* A jvalue instance that the boxed object holds. | |
*/ | |
jvalue unbox (JNIEnv * env, jobject obj, unsigned int boxed_type); | |
#endif /* #ifndef JNI_FACADE_MAPPER_H */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment