i want to use native library from other project. here is my library.
it is my first time to use ndk in android studio. i succedd to load library .so, but failed when i want to access the file. here is the error
here is my java code that load the library.
here is my main java code
can you solve my problem? thanks
With the link to the example project that you provided in comments, the life is really easy.
You need the file https://github.com/CassieLuoli/react-native-smartconnection/blob/master/android/src/main/java/com/mediatek/demo/smartconnection/JniLoader.java as is in your project. Download it from GitHub without changing the class name or the package and use it in your Java app, like they do in their example.
You are missing the JNI layer in your C part. To let Java native interface GetLibVersion() to be able to find a matching function in C part, you need to define a C function name with Java_ai_widya_mediatekso_JniLoader_GetLibVersion(JNIEnv *env, jobject thiz) as the crash log told you. Don't miss the JNI parameters in the C function.
If you want to have the exact same function name in the C part as the Java part, you can register a new name to JVM. Call below function in your JNI_Onload().
static int registerNativeMethods(JNIEnv* env)
{
jclass clazz;
const char* className = "ai/widya/mediatekso/JniLoader";
clazz = env->FindClass(className);
if (clazz == NULL) {
ALOGE("Native registration unable to find class '%s'", className);
return JNI_FALSE;
}
JNINativeMethod methods[] = {
{"GetLibVersion", "()V", (void*) GetLibVersion },
{"GetProtoVersion", "()V", (void*) GetProtoVersion },
};
if (env->RegisterNatives(clazz, methods, 2) < 0) {
ALOGE("RegisterNatives failed for '%s'", className);
return JNI_FALSE;
}
return JNI_TRUE;
}
And don't forget to add the JNI parameters to in your C functions like this GetLibVersion(JNIEnv *env, jobject thiz).
Related
I'm struggling with reading file stream inside Android environment using C++ library.
I believe I set all the permissions correctly (1st figure) and I'm getting the file path using Android internal library. Would you please give me a snippet to correctly read a file using std::ifstream.getline()?
For instance, I get "/document/1EF7-1509:Download/015_1440.jpg" for the image file existing in the 2nd figure under "Download" folder. This path is a raw value returned by "Intent.getData().getPath()" with "ACTION_GET_CONTENT".
extern "C" JNIEXPORT jstring JNICALL Java_com_example_testpplication_MainActivity_testGeneralEncryption(JNIEnv* env, jobject, jstring myString)
{
const char *nativeString = env->GetStringUTFChars(myString, nullptr);
std::string filePath = std::string(nativeString);
std::string buffer;
std::ifstream fStreamIn(filePath);
if(fStreamIn.is_open())
{
std::getline(fStreamIn, buffer);
}
else
{
bool exists = fStreamIn.good();
if(exists)
{
buffer = "Exists";
}
else
{
buffer = "Non-existing";
}
}
return env->NewStringUTF((buffer + ":" + filePath).c_str());
}
Thanks to #blackapps, I did a google with a different keyword and found below answer. Basically, my question is a duplicate.
Inspired by this answer:
https://stackoverflow.com/a/49221353/1770003
The idea is, Android doesn't directly return the absolute path as other OS reports like Windows. A different approach is needed.
The approach I took is,
Add this in my solution: https://android-arsenal.com/details/1/8142#!package
Edit the project: https://github.com/onimur/handle-path-oz/wiki/Java-Single-Uri
In my case, I moved the caller into onRequestHandPathOz which is add by implementing "HandlePathOzListener.SingleUri".
#Override
public void onRequestHandlePathOz(PathOz pathOz, Throwable throwable)
{
txt_pathShow.setText(testGeneralEncryption(pathOz.getPath()));
}
The method "testGeneralEncryption" is defined in the original question and pathOz.getPath() is passed as an argument to the parameter "jstring myString".
I'm using Android NDK and need access to assets. A requirement for asset access seems to be obtaining an AssetManager reference.
Looking at the NDK samples (https://github.com/android/ndk-samples), the pattern seems to be:
A JNIEnv* is passed into the func when called directly from the JavaVM, along with some jobject
Use these to get AAssetManager* and then use this to open assets
That seems simple enough, except in my case, the functions are being called from Unity so I don't have access to either a JNIEnv* or jobject. Getting the JNIEnv* seems easy enough as I can make use of JNI_OnLoad to get access to a JavaVM* and then use that to get a JNIEnv* via vm->GetEnv. My questions about this are:
1) My understanding is that, an Android app can only have one instance of a Java VM. Am I safe to take the JavaVM* passed into JNI_OnLoad and save it for use in other function calls?
2) What about the JNIEnv*? Can I grab that once during JNI_OnLoad and save it, or should I grab a fresh one every time I need to use assets within a function? Is JNIEnv* something I need to explicitly free? (i.e. what's the lifetime/ownership situation with JNIEnv*?)
3) AAssetManager_fromJava also requires a jobject with the documentation (https://developer.android.com/ndk/reference/group/asset#group___asset_1gadfd6537af41577735bcaee52120127f4) saying: "Note that the caller is responsible for obtaining and holding a VM reference to the jobject to prevent its being garbage collected while the native object is in use.". I've seem some examples that simply pass in an empty (native) string like AAssetManager_fromJava(env, ""); - is that ok? I'd only be using the AssetManager for the lifetime of that call, and I could get a fresh one each time. (Again, is AAssetManager* a resource I need to manage, or am I just getting a reference to something owned elsewhere? The documentation seems to imply the latter.)
4) So given all the above, I'd probably do something like:
JavaVM* g_vm;
JNIEnv* g_env;
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
g_vm = vm;
g_vm->GetEnv((void **)&g_env, JNI_VERSION_1_6); // TODO: error checking
return JNI_VERSION_1_6;
}
void do_asset_stuff() {
AAssetManager* mgr = AAssetManager_fromJava(g_env, "");
// do stuff...
}
Is that reasonable? No memory/resource leak issues? Any issues with multi-threading?
Thanks!
EDIT: Seems like there are some threading considerations with JNIEnv*. See: Unable to get JNIEnv* value in arbitrary context
Point-by point answer to your questions:
Yes, there can be only one VM in Android. You are allowed to store this pointer or use JNI_GetCreatedJavaVMs.
JNIEnv pointers are tightly coupled to the thread they were created on. In your situation you will first have to attach the thread to the VM using AttachCurrentThread. This will fill in a JNIEnv * for you. Don't forget to DetachCurrentThread when you're done.
Also note the caveat about FindClass: you need to look up classes from the main thread or via the classloader of a class you looked up in the main thread.
The implementation of AAssetmanager_fromJava is pretty clear: passing it anything other than an AssetManager object is undefined behavior. This answer shows one approach to grabbing the asset manager, another might be to call your own JNI function with a reference to the AssetManager object. Make sure to keep a global reference so it does not get GCed.
Given the above, it would probably look more like this:
JavaVM* g_vm;
jobject cached_assetmanager;
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
g_vm = vm;
return JNI_VERSION_1_6;
}
void do_asset_stuff() {
JNIEnv *env;
JavaVMAttachArgs args = { JNI_VERSION_1_6, "my cool thread", NULL };
g_vm->AttachCurrentThread((void **)&env, &args);
AAssetManager* mgr = AAssetManager_fromJava(g_env, cached_assetmanager);
// do stuff...
}
// Assuming you call `com.shhhsecret.app.storeassetmanager(mgr)` somewhere.
void Java_com_shhhsecret_app_storeassetmanager(JNIEnv *env, jclass cls, jobject am) {
cached_assetmanager = env->NewGlobalRef(am);
}
I was able to read a json file from Unity c++ plugin.
I had to extend UnityPlayerActivity to get assetManager as jobject.
The tricky part also was to find the correct path to the asset in the plugin:
I placed it into StreamingAssets/data and was able to read using this path 'data/myfile'
see my comment with the code:
unity answers
Seems Botje's answer is precise (pity, I did not have it earlier)
Thought I'd post what I ended up doing in case it's of help to others...
#include <jni.h>
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
JavaVM* g_JavaVM;
jobject g_JavaAssetManager;
bool g_Initialized = false;
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
g_JavaVM = vm;
return JNI_VERSION_1_6;
}
// call this once from the main thread in C# land:
extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API NativeInit() {
if (g_Initialized) { return; }
g_Initialized = true;
JNIEnv* env = nullptr;
jint get_env_result = g_JavaVM->GetEnv((void **)&env, JNI_VERSION_1_6);
if (get_env_result == JNI_EDETACHED) {
jint attach_thread_result = g_JavaVM->AttachCurrentThreadAsDaemon(&env, nullptr);
if (attach_thread_result != 0) {
return;
}
get_env_result = JNI_OK;
}
if (env == nullptr || get_env_result != JNI_OK) {
return;
}
jclass unity_player = env->FindClass("com/unity3d/player/UnityPlayer");
jfieldID static_activity_id = env->GetStaticFieldID(unity_player, "currentActivity","Landroid/app/Activity;");
jobject unity_activity = env->GetStaticObjectField(unity_player, static_activity_id);
jmethodID get_assets_id = env->GetMethodID(env->GetObjectClass(unity_activity), "getAssets", "()Landroid/content/res/AssetManager;");
jobject java_asset_manager = env->CallObjectMethod(unity_activity, get_assets_id);
g_JavaAssetManager = env->NewGlobalRef(java_asset_manager);
}
Now g_JavaAssetManager can be used in any thread to call AAssetManager_fromJava.
I have a.so which defines void a() and b.so which defines void b(). They are both put in the .apk so they are available to the Android application.
Now suppose that I'm calling a() through JNI. Is it possible to call b() from a() while completely bypassing JNI?
Can I do it this way in Android (the code is only for illustration, so it might have some errors)?
void a() {
void *handle = dlopen("b.so", RTLD_LAZY);
void (*b)() = dlsym(handle, "b");
b();
}
Would I need to add the fully qualified path, or is b.so already in LD_LIBRARY_PATH for the app?
You can do it this way on Android, though take care of where the shared library has been put in Android folders. It can change from a version to another.
On api 17 for example, it remains in /data/app-lib/. You can hardwrite it, but the best is to make calls to Java (through JNI) to know where the libraries should be.
We're doing something like this in our project :
JNIEnv* env;
const char* temp;
jobject oActivity = state->activity->clazz;
jclass cActivity = env->GetObjectClass(oActivity);
// get the path to where android extracts native libraries to
jmethodID midActivityGetApplicationInfo = env->GetMethodID(cActivity, "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;");
jobject oApplicationInfo = env->CallObjectMethod(oActivity, midActivityGetApplicationInfo);
jclass cApplicationInfo = env->GetObjectClass(oApplicationInfo);
jfieldID fidApplicationInfoNativeLibraryDir = env->GetFieldID(cApplicationInfo, "nativeLibraryDir", "Ljava/lang/String;");
jstring sNativeLibraryDir = (jstring)env->GetObjectField(oApplicationInfo, fidApplicationInfoNativeLibraryDir);
temp = env->GetStringUTFChars(sNativeLibraryDir, NULL);
strcpy(libpath, temp);
strcat(libpath, "/");
Then you push your dlopen + dlsym combo in the fight and it should work.
As mentioned here : How do I load a shared object in C++?
There are two ways of loading shared objects in C++
For either of these methods you would always need the header file for the object you want to use. The header will contain the definitions of the classes or objects you want to use in your code.
#include "blah.h"
int main()
{
ClassFromBlah a;
a.DoSomething();
}
gcc yourfile.cpp -lblah
Dynamically (In Linux):
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
int main(int argc, char **argv) {
void *handle;
double (*cosine)(double);
char *error;
handle = dlopen ("libm.so", RTLD_LAZY);
if (!handle) {
fprintf (stderr, "%s\n", dlerror());
exit(1);
}
dlerror(); /* Clear any existing error */
cosine = dlsym(handle, "cos");
if ((error = dlerror()) != NULL) {
fprintf (stderr, "%s\n", error);
exit(1);
}
printf ("%f\n", (*cosine)(2.0));
dlclose(handle);
return 0;
}
PS : for the dynamic approach, it depends on platform : on Linux, you use dlopen, on windows, you use LoadLibrary.
I want to call the Java method from Jni code with the int and int[] arguments. for that i have Go-ogled and found the following sample .
Calling a java method from c++ in Android
And it worked fine with String parameter . But While trying with int i got issues .
Please help me .
JNI CODE:
jstring Java_com_calljavafromjni_MainActivity_getJniString( JNIEnv* env, jobject obj){
jstring jstr = (*env)->NewStringUTF(env, "RAJESH TEST from JNI ");
jint sss=1111;
jclass clazz = (*env)->FindClass(env, "com/calljavafromjni/MainActivity");
jmethodID messageMe = (*env)->GetMethodID(env, clazz, "messageMe", "(Ljava/lang/String;)Ljava/lang/Integer;");
jobject result = (*env)->CallObjectMethod(env, obj, messageMe, sss);
const char* str = (*env)->GetStringUTFChars(env,(jstring) result, NULL); // should be released but what a heck, it's a tutorial :)
printf("%s\n", str);
return (*env)->NewStringUTF(env, str);
}
Java code
public String messageMe(Integer text) {
System.out.println( "aaaaaaaaaaaaaaaa "+text);
return "test";
}
I don't see where int[] come into your problem, but with int it should be easy to solve.
You need to look at your GetMethodId() call, specifically the method signature argument (the last one). The JNI Specification provides a list of all its Type Signatures here. That should also help you when you eventually come to pass your int arrays too.
So we can see at the moment your signature is:
String messageMe(Integer text)
but you told JNI it was (Ljava/lang/String;)Ljava/lang/Integer; which translates to something like:
java.lang.Integer messageMe(String text)
The Type Signatures show us that the signature for an int is simply I so your argument for GetMethodId() should be something like this:
jmethodID messageMe = (*env)->GetMethodID(env, clazz, "messageMe", "(I)Ljava/lang/String;");
I hope that helps. As I said before, JNI isn't the easiest thing to get into but the answers really are all in the Specification, you just have to look quite hard.
EDIT: I corrected my signature.
Basically, you were almost there - you just got the arguments and return value the wrong way around in the signature. It should be (<arguments>)<return type>. You also made the easy mistake of specifying the class for Integer, instead of the primitive type.
I'm wondering if there is a way to access R class attribute from native code, I need it to read some generated ID that may change every time i do a clean build of my project and I would prefer not to pass them manually to the native part.
EDIT
As suggested from #trashkalmar here is the solution:
static const char* const strClassName = "your/app/package/R$string";
clazz = env->FindClass(strClassName);
if (clazz == NULL) {
LOGE("Can't find class %s\n", strClassName);
return result;
}
jfieldID field = env->GetStaticFieldID(clazz , "you_string_resource", "I");
jint value = env->GetStaticIntField(clazz, field);
Access your R class and read its fields as any other classes.