Search code examples
androidc++macrosandroid-ndkjava-native-interface

when to use JNIEXPORT and JNICALL in Android NDK?


I'm trying to write my own jni sources. Looking at some ndk samples, I found that they often use those macros JNIEXPORT and JNICALL follewed by the name of java package like this

JNIEXPORT void JNICALL Java_com_example_plasma_PlasmaView_renderPlasma(JNIEnv * env, jobject obj, jobject bitmap, jlong time_ms)

I googled it but I can't understand when and how to use these macros


Solution

  • JNIEXPORT and JNICALL are defined in NDK_ROOT/platforms/android-9/arch-arm/usr/include/jni.h. Depending on your setup this path will be different, but mostly similar.

    #define JNIIMPORT
    #define JNIEXPORT  __attribute__ ((visibility ("default")))
    #define JNICALL
    

    JNIEXPORT is used to make native functions appear in the dynamic table of the built binary (*.so file). They can be set to "hidden" or "default" (more info here). If these functions are not in the dynamic table, JNI will not be able to find the functions to call them so the RegisterNatives call will fail at runtime.

    It is worth noting that all functions end up in the dynamic table by default, so anyone could decompile your native code quite easily. Every function call is built into the binary just in case JNI needs to find it. This can be changed using the compiler option -fvisibility. I would recommend everyone sets this to -fvisibility=hidden to keep your code secure, and then use JNIEXPORT to flag functions as having external visibility.

    Using the strip command just removes the debug symbols, the dynamic table is separate. Have a play with objdump to see how much a person could get out of your .so files.

    We recently got tripped up by this, hope this helps someone.

    EDIT: We use a custom build system, so the visibility option may be set by default for other build setups. More information is available in this SO answer.