| |
|
|
||||
![]() |
||||||
| |
|
|||||
|
Listing 4. Java Proxy for a C++ object.
package jni; class Proxy { /** Handle to retrieve C++ object, native side. */ private final int handle; /** Native LUT/constructor. */ private static final native int newNative(); /** Constructor, gets/creates a handle to a native object. */ public Proxy() { handle = newNative(); } /** Simplified fieldID. */ private final int SOME_FIELD = 1; /** Accessor hiding the simplified retrieval. */ public final float getSomeField() { return getFloat( handle, SOME_FIELD ); } /** Method that saves us many retrieval() functions. */ private final native float getFloat( int handle, int field ); } // Minimal C++ object, and JNI glue. class NativeObject { public: NativeObject( jobject owner ) { this.owner = owner; } public: jobject getOwner() {return owner;} public: float someField; public: jobject owner; }; #include <jni.h> extern jclass InvalidProxyOwnerException; extern jclass InvalidFieldIndexException; // extern "C" implied JNIEXPORT jint JNICALL Java_jni_Proxy_getFloat ( JNIEnv* env, jobject owner) { return (jint) (new NativeObject(owner)); } JNIEXPORT jfloat JNICALL Java_jni_Proxy_getFloat ( JNIEnv* env, jobject owner, jint handle, jint field ) { // Truly dirty. Trust on blank finals. NativeObject* obj = (NativeObject*)handle; if ( obj->getOwner() != owner ) (*env)->ThrowNew(env, InvalidProxyOwnerException, "access attempted by non-owner"); switch( field ) { // Enums to be kept in sync manually... case 1: return obj->someField; default: (*env)->ThrowNew(env,InvalidFieldIndexException, "access attempted by non-existing field"); } } } Of course, if you want to avoid switch statements in the native method, or you want to wrap C++ accessor methods instead of exposing fields, you could also implement the public Java accessor as a native method. Incidentally, maintaining the same set of enums in Java and native code is one of the problems that does not yet seem to have an elegant solution. If you are using a look-up table to retrieve pointers for handles, the jobject argument might already be sufficient. A native proxy implemented as a C++ object or C struct could cache a global jobject reference along with method IDs and field IDs. Caching actual game data inside native code means that your proxies have to be kept synchronized with the master objects, or you will end up with consistency errors that are very difficult to track down. Alternatively, you could encapsulate the results or take a snapshot of a native object's state in a new Java object created in native code, using the JNI function NewObject() to call a Java constructor. This approach works even better if your native and Java modules communicate by passing event descriptor objects to a queue. |
|
|