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
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.
You can find the definition of those macros in the machine-dependent portion of your JNI includes (usually in $JAVA_HOME/include/<arch>/jni-md.h).
In short, JNIEXPORT contains any compiler directives required to ensure that the given function is exported properly. On android (and other linux-based systems), that'll be empty.
JNICALL contains any compiler directives required to ensure that the given function is treated with the proper calling convention. Probably empty on android as well (it's __stdcall on w32).
In general, you should leave them in, even if they're empty #defines.
In simple terms:
- JNIEXPORT if you should use registerNatives family of functions then you should not use JNIEXPORT. Otherwise you MUST use it.
- JNICALL must be used ALWAYS.
JNIEXPORT ensures function is visible in the symbols table. JNICALL ensures function uses the correct calling convention. On Android JNICALL has a different value based on architecture. ARM is empty which might trick you to not include it. But you must use JNICALL.
registerNatives allows you to link the function up programatically on JNI_onLoad or sometime in the future too. registerNatives allows for catching wrong function names earlier and I recommend this route.
Just run 'javah' on your native classes and use whatever it generates. You don't need to know the ins and outs of this when you have a tool that can produce it with 100% reliability.