Android JNI c++ pointers to Java.

When working with Native on Android, it is many times needed to have a Native representation of a Java Object. This native Object should mainly behave like a proxy to the Java object which is mainly straightforward. Since the native support android is extremely limited, the use cases for this are almost infinite. From proper concurrent HttpRequest handling, to send a push notification, etc.
The tricky part is when the Java object should notify back the Native object about an event with origin on the Java side. For such scenario, it is a must to keep the Native reference object in Java, so that Java can notify back the expected Native object that initiated the call. To do so, the pointer to this must be sent from native to Java. A pointer, is just a memory address, and a casting to long will do. With the subtle detail that this will mangle the pointer and your code will break.


// somewhere in your c/c++ file:
// 
// build a Java object to keep as native-to-java proxy.
// We instantiate from a Java constructor with a long parameter.
// The Java long is the c++ pointer object.
JNIEnv*   env=        ...;
jclass    clazz=      env->FindClass( className );
jmethodID constructor=env->GetMethodID( clazz, "", constructorSignature );

/////////// WRONG . SEG_MAPPER courtesy of mangled pointer 
long      thisPtr=    (long)this;
jobject   obj=        env->NewObject( clazz, constructor, thisPtr );

This code will fail. Converting ‘this’ to long won’t work. Instead, use the following:


////////// RIGHT. This will work. The call to 
// jlong(this) will handle endianness and type conversion.
jlong   thisPtr= jlong(this);  
jobject obj=     env->NewObject( clazz, constructor, thisPtr );

Now, there’s the part where we want to notify back from Java to native. Java must have a ‘native’ qualified method, like for example:


// somewhere in your .java file

public class Clazz {
   
   ...
 
   /**
    * ...
    * @param nativePtr the native object pointer got at construction time.
    */
   private static native void onProgress( long nativePtr, float value );
}

And the implementation of this native function could be:


// somewhere in your c/c++ file

void JNICALL Java_fully_qualified_package_CLASSNAME_METHODNAME(
    JNIEnv* env, jobject thiz, jlong nativePtr, jfloat progress) {

    // nativePtr is safe to reinterpret as the original 
    // 'this' promoted to 'jlong' object's memory address.
    reinterpret_cast(nativePtr)->...

}

As a warning note, watch out when calling jlong(this). If the object is temporary, whenever Java calls back to Native you’ll have a guaranteed application crash. It is important to have Object lifecycle so that you can free the native object pointers when it will safe to do so.

Advertisements

Android NDK with multiple pre-built libraries

I am currently playing a bit with the NDK and every step seems to be draining an hour to figure out stuff.
This time, I need to compile a shared library, which depends on pre-built multiple static and shared libraries. The NDK documentation example seems naive enough to be straightforward to have it working, but it took me some time to figure out how to do it.

Here’s a valid Android.mk file which references two other pre-built static libraries. It took me some time to realize that:

  • The LOCAL_STATIC_LIBRARIES reference the name of the LOCAL_MODULE for the pre-built libraries, not the name of the library or anything alike. The name LOCAL_STATIC_LIBRARIES can be a bit misleading, and thus, lpw must be specified instead of the library name.
  • The LOCAL_SRC_FILES for each module can only reference one library, and the path will be relative to the point you invoke the ndk-build command, but absolute paths will work like a charm.
  • The $(TARGET_ARCH_ABI) will reference the valid library for the currently compiling architecture, so it is a must have.

LOCAL_PATH := $(call my-dir)

########################
# prepare lib1
include $(CLEAR_VARS)
LOCAL_MODULE    := lpw
LOCAL_SRC_FILES := libs/$(TARGET_ARCH_ABI)/libPluginWhatever.a
include $(PREBUILT_STATIC_LIBRARY)
########################

########################
# prepare lib2
include $(CLEAR_VARS)
LOCAL_MODULE    := lib2
LOCAL_SRC_FILES := libs/$(TARGET_ARCH_ABI)/lib2.a 
include $(PREBUILT_STATIC_LIBRARY)
########################

... your other stuff

LOCAL_STATIC_LIBRARIES := android_native_app_glue lib2 lpw

include $(BUILD_SHARED_LIBRARY)

$(call import-module,android/native_app_glue)

Normally, when the ndk-build command ends, your libraries will be installed in the libs directory, a folder created in the same directory you invoked the nkd-build in. If you invoke ndk-build manually, Gradle will miserably fail to include the generated library files since it expects the installed libraries to be in the folder jniLibs instead of libs. If have a convenient symbolic link jniLibs->libs for this inconvenience.