I am following the Google tips to implement a JNI layer between my Android app and my C++ library. It suggests to use the following code to register native methods when the library is loaded:
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
...
// Register your class' native methods.
static const JNINativeMethod methods[] = {
{"nativeFoo", "()V", reinterpret_cast<void*>(nativeFoo)},
{"nativeBar", "(Ljava/lang/String;I)Z", reinterpret_cast<void*>(nativeBar)},
};
int rc = env->RegisterNatives(c, methods, sizeof(methods)/sizeof(JNINativeMethod));
...
}
I am quite new to C++ so I decided to use clang-tidy to ensure my C++ code is modern and safe. clang-tidy reports:
error: do not use reinterpret_cast [cppcoreguidelines-pro-type-reinterpret-cast,-warnings-as-errors]
According to the clang-tidy documentation:
cppcoreguidelines-pro-type-reinterpret-cast
This check flags all uses of reinterpret_cast in C++ code.
Use of these casts can violate type safety and cause the program to
access a variable that is actually of type X to be accessed as if it
were of an unrelated type Z.
This rule is part of the “Type safety” profile of the C++ Core
Guidelines, see
https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Pro-type-reinterpretcast.
So I have a few options:
Disable this check and risk using reinterpret_cast inappropriately elsewhere
Ignore the check everywhere where I need to use it and create a messy codebase
Find some alternative way of implementing this more safely
I would like to do 3 if it's possible but I'm not quite sure where to start.
It’s not clear why GetEnv wants a void** rather than a JNIEnv** (what other type would you use?), but you can avoid the reinterpret_cast and the concomitant undefined behavior(!) with a temporary variable:
void *venv;
if (vm->GetEnv(&venv, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
const auto env=static_cast<JNIEnv*>(venv);
Note that a static_cast from void* is every bit as dangerous as a reinterpret_cast, but its use here is correct, unavoidable, and shouldn’t produce linter warnings.
In the function pointer case there’s nothing to be done: reinterpret_cast is the only, correct choice (and for void* rather than some placeholder function pointer type like void (*)() its correctness is not guaranteed by C++, although it works widely and POSIX does guarantee it). You can of course hide that conversion in a function (template) so that the linter can be told to ignore only it, but make sure to use a clear name to avoid “hiding” the conversion.
Related
Is there any way to use jniRegisterNativeMethods to map JNI functions in a NDK app? i.e. use a method_table to map native (C/C++) functions via the JNI instead of using ridiculously long JNI method names?
For example, in one exercise I saw, there was a C file added onto the platform it self,
#include "core_jni_helpers.h"
#include "jni.h"
static jlong init_native(JNIEnv *env, jobject clazz)
{
return 0;
}
// ...
static JNINativeMethod method_table[] = {
{ "init_native", "()J", (void*)init_native },
{ "finalize_native", "(J)V", (void*)finalize_native },
// ...
};
int register_android_server_ExampleService(JNIEnv *env)
{
return jniRegisterNativeMethods(env, "com/android/server/OpersysService",
method_table, NELEM(method_table));
};
But then the register_android_server_ExampleService was was manually invoked in services/core/jni/onload.cpp (on the platform)
Is there any way to do this or something similar with the NDK though?
My guess is no, as JNIHelp.h and core_jni_helpers.h aren't available in the NDK, and the Kotlin tools in Android Studio likely wouldn't be able to run a function in order to perform the auto complete. However I thought it was worth asking in the small case I could somehow avoid naming functions like Java_vendor_<name>_<name>_<name>_test_MainActivity_stringFromJNI
One of the ways to register native functions in JNI is by using the function RegisterNatives(). An example (although seemingly with some errors) can be found in the Android documentation here. Here's a code example demonstrating this technique:
#include <jni.h>
void JNICALL test(JNIEnv* const environment, jobject const objectOrClass) {
}
extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* const vm, void* const reserved) {
JNIEnv* environment;
vm->GetEnv(reinterpret_cast<void**>(&environment), JNI_VERSION_1_6);
auto const classRef = environment->FindClass("com/example/test/MainActivity");
JNINativeMethod method;
method.name = "test";
method.signature = "()V";
method.fnPtr = reinterpret_cast<void*>(test);
environment->RegisterNatives(classRef, &method, 1);
return JNI_VERSION_1_6;
}
Using RegisterNatives() requires casting (non-member) function pointers to type void*, as shown above. According to this documentation, this isn't necessarily portable in C++. It may be guaranteed to work in some environments (such as POSIX), but the behavior is otherwise implementation-dependent.
I wouldn't expect this functionality to be exposed this way if it wasn't reliable, but does either JNI or Android offer any guarantees that this can be counted on to work? Is it just assumed that JNI and/or Android will always be running in environments where casting between function pointers and void* is supported?
EDIT:
After further consideration, it seems JNI would almost have to do something non-portable with respect to native functions, irrespective of the cast issue. The only information about the function JNI has (other than the address) is a string describing the argument and return types, so it seems unlikely that the void* will ever be cast back to a valid function pointer and used in the conventional way. Presumably the calls are instead made via low-level platform-specific code.
It still seems like a requirement that function-pointer-to-void* casts be well-behaved, so if anyone knows if or how JNI guarantees that, I'd still be interested to know.
If there is an application that has a .so lib. say "example.so", that has a function like Java_com_domain_demo_exampleFuntion, can you call it from your application which has a different application id?
If the new application ID is com.otherdomain.demo2 then there is an error like
No implementation found for void Java_com_otherdomain_demo2_exampleFuntion()
There is another means of achieving this if you cannot rename your application: write a small .so file that links with example.so and that calls JNI's registerNatives in its JNI_OnLoad function:
The following example is adapted from the Android JNI documentation:
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
// Find your class. JNI_OnLoad is called from the correct class loader context for this to work.
jclass c = env->FindClass("com/markov/App/MyClass");
if (c == nullptr) return JNI_ERR;
// Register your class' native methods.
static const JNINativeMethod methods[] = {
{"myExampleFunction", "()V", reinterpret_cast(Java_com_domain_demo_exampleFuntion)},
};
int rc = env->RegisterNatives(c, methods, sizeof(methods)/sizeof(JNINativeMethod));
if (rc != JNI_OK) return rc;
return JNI_VERSION_1_6;
}
This example binds Java_com_domain_demo_exampleFuntion to com.markov.App.MyClass#myExampleFunction.
You can add more functions to the methods array, of course.
Note that the behavior of the functions you copy may depend on certain fields of the class you bind it to. If those fields are not present on your com.markov.App.MyClass class, the JNI invocation will fail or crash. For example, many Java wrappers use long fields to store pointers to C memory.
You can use to create special module(same package name) which It named like Java_com_domain_demo_exampleFuntion(). And you using this module another app. But, you can't use *.so file other app with different package name.
I'm calling native function from Java:
String pathTemp = Environment.getExternalStorageDirectory().getAbsolutePath()+Const.PATH_TEMP
String pathFiles = Environment.getExternalStorageDirectory().getAbsolutePath()+Const.PATH_FILES
engine.init(someInt, pathTemp, pathFiles);
And I have the native function:
extern "C" JNIEXPORT void Java_com_engine_init(JNIEnv *env, jobject __unused obj, jint someInt, jstring pathTemp, jstring pathFiles) {
const char *pathTemp_ = env->GetStringUTFChars(pathTemp, JNI_FALSE);
const char *pathFiles_ = env->GetStringUTFChars(pathFiles, JNI_FALSE); // <-- CRASH
// More init code
env->ReleaseStringUTFChars(pathTemp, pathTemp_);
env->ReleaseStringUTFChars(pathRecording, pathRecording_);
}
The problem: pathTemp is arriving good, but pathFiles==NULL in native function.
Rechecked, and confirmed - both strings are non NULL in java.
One more strange thing - The problem is on LG-G3 (android 6.0).
On Meizu PRO 5 (android 7.0) - everything works good - both strings are intact.
What is this JNI magic? Any clue?
I had the same problem as this and while I can't guarantee this is the same, I found a better solution than re-ordering the parameters.
tldr; Ensure the code works for 32bit and 64bit platforms as pointers have different sizes. I was running 32bit native code and passed nullptr as a parameter and java expected a long which resulted in all parameters after the nullptr to be invalid.
(JJLjava/lang/String;Z)V -> (final long pCallback, final long pUserPointer, final String id, final boolean b)
pCallback was always set to a valid value (pointer casted to jlong in c++) and pUserPointer was always nullptr. I found this answer and tried switching the order around and it 'just worked' but I knew that fix was never going to be approved.
After looking at the JNI documentation on the Android website again (https://developer.android.com/training/articles/perf-jni) I took note of the "64-bit considerations" section and took a stab at my assumption of the data size. This feature was developed with a 64bit device (Pixel 3) but issues had been reported on a 32bit device (Amazon Fire Phone) so nullptr would be 32bit but the java function still expected a long (64bit).
In my situation the offending parameter was always unused so I could safely remove it and everything "just worked" (including some other parameters which were broken).
An alternative would be to have a define/function/macro for JniLongNullptr which is just 0 casted to jlong.
Not an answer, but workaround. Moved the strings (in parameters) to be before int parameter. Now it's working. I have no idea why is this.
I have integrated two native libraries (.so ) in my application. The libraries compile fine and I can load them in my application too. The first time I invoke a native method of a library it works fine, but if I call the same method again in the Activity the application shuts down.
The problem I am facing is exactly the same as mentioned in here :
http://grokbase.com/t/gg/android-ndk/1226m68ydm/app-exit-on-second-native-call
The solution that works is to invoke the native method in another Activity and shut it down forcefully via System.exit(0). Following the article I tried setting the pointers to NULL of the called method after a successful operation, but this too didn't help me. Also its not possible to unload a library once its loaded by System.loadLibrary().
I want to call the native methods more than once without creating a new Activity. Any ideas how to solve this issue ?
(I FINALLY FOUND A SOLUTION ... HERE IT IS)
Okay, I have finally found a way to resolve this issue. The solution is actually pretty simple. Build another independent native library (utility library) to load and unload the other libraries. What we need to do is use dlopen() and dlclose() in the native method of the utility. We can load the utility library like before via System.loadLibrary().
So in the native method of the utility library what we need to do is:
Use#include <dlfcn.h> // this is required to call dlopen() and dlclose() functions.
Provide handler and function prototype:
void *handle;
typedef int (*func)(int); // define function prototype
func myFunctionName; // some name for the function
Open the library via dlopen() :
handle = dlopen("/data/data/my.package.com/lib/somelibrary.so", RTLD_LAZY);
Get and Call the function of the library:
myFunctionName = (func)dlsym(handle, "actualFunctionNameInLibrary");
myFunctionName(1); // passing parameters if needed in the call
Now that the call is done. Close it via dlclose():
dlclose(handle);
Hope this will help others facing the same issue.
So ... my solution was starting a service that runs the shared library code, this service has a different process name ( you can set it in the Android Manifest ), as it is a different process you can kill it ( Using Process.killProcess(Process.myPid()) when it finishes running, without affecting your application in any way.
Worked very well for me, hope it helps someone else.
As this is the top hit for this issue and as the issue itself still exists, it seems that the approach that ZakiMak shared with us is still the most popular solution.
For others who may want to implement it and would like a little more detail for the latest Android releases, here are some notes I made as I stumbled through this:
Firstly, there is a solution which implements this approach on GitHub now. I have not tried it personally, but I have used it as a reference. It is very useful to see how the Android.mk file is structured and how the library is opened and methods called. Link is here: https://github.com/jhotovy/android-ffmpeg
The path to the native library folder changes over Android releases and it also appears to change every time you run the app (although this may be just in debug mode). Either way, it is best to pass the path in from the calling Java method if possible. For example:
In the Java wrapping class:
import android.content.Context;
import android.util.Log;
public class FfmpegJNIWrapper {
//This class provides a Java wrapper around the exposed JNI ffmpeg functions.
static {
//Load the 'first' or 'outer' JNI library so this activity can use it
System.loadLibrary("ffmpeg_wraper_multi_invoke_jni");
}
public static int call_ffmpegWrapper(Context appContext, String[] ffmpegArgs) {
//Get the native libary path
String nativeLibPath = appContext.getApplicationInfo().nativeLibraryDir;
//Call the method in the first or 'outer' library, passing it the
//native library past as well as the original args
return ffmpegWrapper(nativeLibPath, ffmpegArgs);
}
// Native methods for ffmpeg functions
public static native int ffmpegWrapper(String nativeLibPath, String[] argv);
}
In the 'first' or 'outer' native library:
JNIEXPORT jint JNICALL Java_com_yourpackage_androidffmpegwrapper_FfmpegJNIWrapper_ffmpegWrapper(JNIEnv *pEnv, jobject pObj, jstring nativeLibPath, jobjectArray javaArgv) {
//Get the second or 'inner' native library path
char* nativePathPassedIn = (char *)(*pEnv)->GetStringUTFChars(pEnv, nativeLibPath, NULL);
char ourNativeLibraryPath[256];
snprintf(ourNativeLibraryPath, sizeof (ourNativeLibraryPath), "%s%s", nativePathPassedIn, "/libffmpeg_wraper_jni.so"); //the name of your ffmpeg library
//Open the so library
void *handle;
typedef int (*func)(JNIEnv*, jobject, jobjectArray);
handle = dlopen(ourNativeLibraryPath, RTLD_LAZY);
if (handle == NULL) {
__android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "could not open library: %s", dlerror());
printf("Could not dlopen(\"libbar.so\"): %s\n", dlerror());
return(-1);
}
//Call the ffmpeg wrapper functon in the second or 'inner' library
func reenterable_ffmpegWrapperFunction;
reenterable_ffmpegWrapperFunction = (func)dlsym(handle, "Java_com_yourpackage_androidffmpegwrapper_FfmpegJNIWrapper_ffmpegWrapper");
reenterable_ffmpegWrapperFunction(pEnv, pObj, javaArgv); //the original arguments
//Close the library
dlclose(handle);
// return
return(1);
}
The Android.mk file is a little 'flaky' to put it politely. Because you are building two separate libraries in one Android.mk file, this may be a little more complex that other NDK make files so if you get some strange errors do some searching before you start taking your project apart. For example: https://stackoverflow.com/a/6243727/334402