Skip to content

Instantly share code, notes, and snippets.

@diosmosis
Created August 21, 2011 09:40
Show Gist options
  • Save diosmosis/1160394 to your computer and use it in GitHub Desktop.
Save diosmosis/1160394 to your computer and use it in GitHub Desktop.
JNI Facade
#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;
}
#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;
}
#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