Here is my program:
extern "C" {
JNIEXPORT jint Java_android_app_integrity_VerifyIntegrity_checkCrc(JNIEnv *jniEnv,jobject thiz,jstring crcStr) {
jclass clsZipFile = jniEnv->FindClass("java/util/zip/ZipFile");
jmethodID mtdConstruct = jniEnv->GetMethodID(clsZipFile, "<init>", "(Ljava/lang/String;)V");
jmethodID mtdGetEntry = jniEnv->GetMethodID(clsZipFile,"getEntry","(Ljava/lang/String;)Ljava/util/zip/ZipEntry;");
jclass clsZipEntry = jniEnv->FindClass("java/util/zip/ZipEntry");
jmethodID mtdGetCrc = jniEnv->GetMethodID(clsZipEntry,"getCrc","()L");
LOGD("pos2");
jobject objZipFile = jniEnv->NewObject(clsZipFile,mtdConstruct,crcStr);
if (NULL == objZipFile){
LOGD("NULL == objZipFile");
}
LOGD("pos3");
jobject objZipEntry = jniEnv->CallObjectMethod(objZipFile, mtdGetEntry,"classes.dex");
LOGD("pos4");
jlong ret = jniEnv->CallLongMethod(objZipEntry, mtdGetCrc);
LOGD("%ld",(long int)ret);
return 0;
}
};
It only print "pos2". The line below the "LOGD("pos2");" will cause crash!
I can't find the reason. Who can help me? Thx!
Try fixing the following line. It has an invalid signature and will cause an implicit exception to be thrown for MethodNotFound exception and is likely the culprit.
jmethodID mtdGetCrc = jniEnv->GetMethodID(clsZipEntry,"getCrc","()L");
Should be:
jmethodID mtdGetCrc = jniEnv->GetMethodID(clsZipEntry,"getCrc","()J");
However I would second other suggestions to check return values from all FindClass and FindMethod calls as they not only return NULL they also each throw an exception upon failure. Also OutOfMemoryException is thrown when JNI can not allocate a local reference object to return for your jclass lookups.
Related
I am not able to cast back the user self defined class return from C++ jni callback
code snippet as follow:
//Kotlin class
data class class_record(var id:String?,var class_name:String?,var class_type)
// com.example.MainActivity
public native Object Cls(String id);
Vector vec_ classrecord=new Vector();
vec_classrecord=(Vector)Cls("1234");
// c++ jni code
extern "C"
JNIEXPORT jobject JNICALL
Java_com_example_MainActivity_Cls(JNIEnv *env, jobject instance, jstring id,
) {
jclass java_vector_class;
jmethodID java_vector_method;
jobject java_vector_object ;
auto vec_record=// call c++ method that return vector for class record pointer
jstring jni_str;
jclass javaClassRef;
// jni for java.util.Vector
java_vector_class = env->FindClass("java/util/Vector");
java_vector_method_constructor = env->GetMethodID(java_vector_class, "<init>", "()V");
java_vector_object = env->NewObject(java_vector_class, java_vector_method_constructor, "");
for (auto record_it = vec_record.begin(); record_it < vec_record.end(); ++record_it) {
// jni for class_record
jclass java_class = env->FindClass("com/example/class_record");
javaClassRef = (jclass) env->NewGlobalRef(java_class);
jmethodID cls_constructor = env->GetMethodID(javaClassRef, "<init>", "()V");
jobject cls_object = env->NewObject(javaClassRef, cls_constructor, "");
// set id
javaMethodRef = env->GetMethodID(javaClassRef, "setId", "(Ljava/lang/String;)V");
std::string strval = record_it.id;
jni_str = env->NewStringUTF(strval.c_str());
env->CallVoidMethod(cls_object, javaMethodRef, jni_str);
// set class_name
javaMethodRef = env->GetMethodID(javaClassRef, "setClass_name","(Ljava/lang/String;)V");
std::string strval = record_it.class_name;
jni_str = env->NewStringUTF(strval.c_str());
env->CallVoidMethod(cls_object, javaMethodRef, jni_str);
//set class_type
javaMethodRef = env->GetMethodID(javaClassRef, "setClass_type","(Ljava/lang/String;)V");
std::string strval = record_it.class_type;
jni_str = env->NewStringUTF(strval.c_str());
env->CallVoidMethod(cls_object, javaMethodRef, jni_str);
jmethodID java_vector_add = env->GetMethodID(java_vector_class, "addElement","(Ljava/lang/Object;)V");
**env->CallVoidMethod(java_vector_object, java_vector_add, javaClassRef);**
}
return java_vector_object;
}
env->CallVoidMethod(java_vector_object, java_vector_add, cls_object);
Under Kotlin environment , it it much better to express jni c++ callback in ArrayList instead of vector which grow almost double the size with snippet as follow in situation where the returned arraylist is fixed size immuatable.
java_util_class = env->FindClass("java/util/ArrayList");
jmethodID java_add= env->GetMethodID(java_util_class, "add","(Ljava/lang/Object;)Z");
env->CallBooleanMethod(java_object, java_add, cls_object);
sample tutorial
I am using following code to access a java class from native code
JNIEnv *env = nullptr;
JMVEnv::attachCurrentJNIENv(&env);
jclass jXYZClass = env->FindClass("com/xxx/xx/xx/XYZClassName");
This call passes 4-5 times but fails after that. jXYZClass is null after some calls.
So, while compilation class is found and during execution also it was found 4-5 times. I am calling this code from different locations. Can it be some threading issue?
I think it is a multhreading problems... Especially when you say that " it was found 4-5 times. I am calling this code from different locations".
See this post for more details.
Firstly, you can print the thread id of each thread to see if they are the same thread.
You can Findclass in JNI_OnLoad:
jobject g_class;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pjvm, void *reserved) {
JavaVM *gJvm = pjvm; // cache the JavaVM pointer
JNIEnv *env= NULL;
env = gJvm->GetEnv((void**)&env, JNI_VERSION_1_6);
jclass tmp = env->FindClass("com/xxx/xx/xx/XYZClassName");
g_class = env->NewGlobalRef(tmp);
}
whenever you want to use this class:
// extern jobject g_class; Add this Line if this is in another cpp file
jmethodID methodID = env->GetMethodID((jclass)g_class, "<init>", "()V");
jobject new_object = env->NewObject((jclass)g_class, methodID);
I a Library in C that I'm leveraging for an Android application. This library has an audio stream that it occasionally flushes. When this happens it calls a write callback function of my design.
My intent is to have that C callback call a method on a specific Java Object which will handle stuff with the strem.
Currently I have code like so:
methodID compressionHandler=0;
jobject compressionHandlerClass;
int audioBufferChunkSize;
static JavaVM *gJavaVM;
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
gJavaVM = vm;
return JNI_VERSION_1_6;
}
JNIEXPORT void JNICALL
Java_com_my_code_init(JNIEnv* env, jobject obj, /*classpath of the class we want to call against*/jstring compressedAudioHandlerPath, /*class instance we want to call against*/jobject callbackClass) {
......
// this is a global ref as per:
//http://stackoverflow.com/questions/14765776/jni-error-app-bug-accessed-stale-local-reference-0xbc00021-index-8-in-a-tabl
compressionHandlerClass = (*env)->NewGlobalRef(env,callbackClass);
// name of the class
const char *classLocation;
// convert jString to c String
classLocation = (*env)->GetStringUTFChars( env, compressedAudioHandlerPath , NULL ) ;
// tmp variable for holding the class location, relates to the above issue with garbage collection
jclass clazz = (*env)->FindClass(env, classLocation);
// the actual method that we want to call, this gets used in the writeCallback
compressionHandler = (*env)->GetMethodID(env, clazz, "handleCompressedAudio", "([B)V");
......
}
The callback method looks like so:
void writeCallback(const FLAC__StreamEncoder *encoder, const FLAC__byte buffer[], size_t bytes, unsigned samples, unsigned current_frame, void *client_data) {
JNIEnv *env;
int isAttached = 0;
if ((status = (*gJavaVM)->GetEnv(gJavaVM, (void**)&env, JNI_VERSION_1_6)) < 0) {
if ((status = (*gJavaVM)->AttachCurrentThread(gJavaVM, &env, NULL)) < 0) {
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
isAttached = 1;
}
if(*env!=0 && compressionHandler!=0){
jbyteArray arr = (*env)->NewByteArray(env,bytes);
(*env)->SetByteArrayRegion(env,arr, 0, bytes, (jbyte*)buffer);
(*env)->CallVoidMethod(env,compressionHandlerClass, compressionHandler,arr);
free(arr);
free(env);
free(isAttached);
}
}
I'm getting crashes at the CallVoidMethod, that signature of which is an interface implemented by whatever object I pass in:
public interface CompressedAudioHandler {
void handleCompressedAudio(byte[] buff);
}
I suspect that I am improperly attaining/keep references to these objects, but I haven't found a great way to handle that. Any advice on how I can more correctly handle this?
OK, so I have the native code below.
I'm trying to return an array of FilePermissionInfo from it, populated with some data returned by stat().
The problem is that I get the following error when NewObject is called the first time:
06-15 20:25:17.621: W/dalvikvm(2287): Invalid indirect reference
0x40005820 in decodeIndirectRef 06-15 20:25:17.621: E/dalvikvm(2287):
VM aborting
It's odd, because the only reference object I have is the jclass (for FilePermissionInfo) and I turn it to a global reference.
The code is:
JNIEXPORT jobjectArray JNICALL
Java_com_mn_rootscape_utils_NativeMethods_getFilesPermissions( JNIEnv* env, jobject thizz, jobjectArray filePathsArray )
{
jobjectArray result;
int size = (*env)->GetArrayLength(env, filePathsArray);
jboolean isCopy;
jclass filePermInfoCls = (*env)->FindClass(env, kFilePermissionInfoPath);
if(!filePermInfoCls)
{
LOGE("getFilesPermissions: failed to get class reference.");
return NULL;
}
gFilePermInfoClass = (jclass)(*env)->NewGlobalRef(env, filePermInfoCls);
LOGI("got gFilePermInfoClass");
jmethodID filePermInfoClsConstructor = (*env)->GetMethodID(env, gFilePermInfoClass, "<init>", kFilePermInfoConstructorSig);
if(!filePermInfoClsConstructor)
{
LOGE("getFilesPermissions: failed to get method reference.");
return NULL;
}
struct stat sb;
LOGI("starting...");
result = (jobjectArray)(*env)->NewObjectArray(env, size, gFilePermInfoClass, NULL);
for(int i = 0; i != size; ++i)
{
jstring string = (jstring) (*env)->GetObjectArrayElement(env, filePathsArray, i);
const char *rawString = (*env)->GetStringUTFChars(env, string, &isCopy);
if(stat(rawString, &sb) == -1)
{
LOGE("stat error for: %s", rawString);
}
LOGI("%ld %ld %ld %ld %ld %ld %ld %ld", sb.st_dev, sb.st_mode, sb.st_nlink, sb.st_uid, sb.st_gid, sb.st_atime, sb.st_mtime, sb.st_ctime);
jobject permInfo = (*env)->NewObject(env,
gFilePermInfoClass,
filePermInfoClsConstructor,
(long)sb.st_dev,
(long)sb.st_mode,
(long)sb.st_nlink,
(long)sb.st_uid,
(long)sb.st_gid,
(long)sb.st_atime,
(long)sb.st_mtime,
(long)sb.st_ctime,
"",
"",
1,
"");
LOGI("xxx1");
(*env)->SetObjectArrayElement(env, result, i, permInfo);
LOGI("xxx2");
(*env)->ReleaseStringUTFChars(env, string, rawString);
LOGI("xxx3");
}
(*env)->DeleteLocalRef(env, filePermInfoCls);
return result;
}
The Java class constructor signature and path are:
const char* kFilePermissionInfoPath = "com/mn/rootscape/utils/FilePermissionInfo";
const char* kFilePermInfoConstructorSig = "(JJJJJJJJLjava/lang/String;Ljava/lang/String;ZLjava/lang/String;)V";
Please note that if I call NewObject on the default constructor then it works fine.
OK, found it.
It was a problem with the jstring parameters. It turns out you cannot pass empty strings (or even NULL for that matter) as a jstring.
Instead I used (*env)->NewStringUTF(env, NULL) to create a NULL jstring.
Seems to work OK now.
Since this question generated somewhat a high activity, I'm posting the final solution below. Note that the nullString variable is being deallocated at the end of its scope (or when you're done using it):
jstring nullString = (*env)->NewStringUTF(env, NULL);
...
jobject permInfo = (*env)->NewObject(env,
gFilePermInfoClass,
filePermInfoClsConstructor,
(jbyte)permsOwner,
(jbyte)permsGroup,
(jbyte)permsOthers,
(jlong)sb.st_uid,
(jlong)sb.st_gid,
(jlong)sb.st_atime,
(jlong)sb.st_mtime,
(jlong)sb.st_ctime,
nullString,
nullString,
(jboolean)1,
nullString);
...
(*env)->DeleteLocalRef(env, nullString);
I want to call Java API from NDK C++ thread, but env->FindClass() return 0. But when I call Java API in main thread, it works well. I've already call AttachCurrentThread() in the thread, can anyone help me?
Here is the source code:
JAVA CODE:
public class simple_test extends Activity {
...
// This functin will be called in C++
public void PrintNdkLog(String slog) {
Log.e(logTagNDK, slog);
return;
}
}
C++ CODE:
static JavaVM* g_JavaVM = NULL;
jobject getInstance(JNIEnv *env, jclass obj_class)
{
jmethodID c_id = env->GetMethodID(obj_class, "<init>", "()V");
jobject obj = env->NewObject(obj_class, c_id);
return obj;
}
// JNI OnLoad
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
g_JavaVM = jvm;
return JNI_VERSION_1_6;
}
// Call JAVA API "PrintNdkLog" in this function
void PrintNdkLog(char *lpLog)
{
if (g_JavaVM == NULL)
return;
JNIEnv *env = NULL;
g_JavaVM->GetEnv((void**)&env, JNI_VERSION_1_6);
if (env == NULL)
return;
jclass cls = env->FindClass("com/myndk/simple_test");
if (cls != 0) // **cls will be 0 when PrintNdkLog() is called in thread**
{
LOGE("FindClass error %p", cls);
}
else
{
jmethodID mid;
jobject obj;
obj = getInstance(env, cls);
mid = env->GetMethodID(cls, "PrintNdkLog", "(Ljava/lang/String;)V");
if (mid != 0)
{
jstring jstrMSG = env->NewStringUTF(lpLog);
env->CallVoidMethod(obj, mid, jstrMSG);
}
}
}
// Call JAVA API in thread
static void* thread_test(void* ptr)
{
JNIEnv *envLocal;
int status = g_JavaVM->GetEnv((void **) &envLocal, JNI_VERSION_1_6);
if (status == JNI_EDETACHED)
{
status = g_JavaVM->AttachCurrentThread(&envLocal, NULL);
if (status != JNI_OK)
LOGE("AttachCurrentThread failed %d",status);
}
PrintNdkLog("bbb"); // This JAVA callback failed, and printed "FindClass error"
}
// Create thread
int NdkThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam, int nPriority)
{
PrintNdkLog("aaa"); // This JAVA callback runs well
pthread_t pid;
pthread_create(&pid, NULL, thread_test, pParam);
}
I have solved it now.
In NDK native thread, only can call static Java API. If you call env->FindClass(), it would trigger an exception.
http://android.wooyd.org/JNIExample gived the detail info.
Suggest to take a look on AttachCurrentThread.
Here is a sample code to do that:
// Global variable
JavaVM *g_jvm = NULL; //Get g_jvm from jni main thread use env->GetJavaVM(&g_jvm);
jobject g_obj = NULL; //Where the java function exist. (some activity)
//Get env in thread function and attach the env
JNIEnv *env;
if(g_jvm->AttachCurrentThread(&env, NULL) != JNI_OK)
{
LOGD("%s: AttachCurrentThread() failed", __FUNCTION__);
}
const char * fnName ="somFunctionInYourJava"; //which should be "pulic void somFunctionInYourJava(String input);"
jstring retStr = env->NewStringUTF(str);
jclass cls = env->GetObjectClass(thiz);
jmethodID messageMe = env->GetMethodID(cls, fnName, "(Ljava/lang/String;)V");
env->CallVoidMethod(thiz, messageMe, retStr);
//Detach thread and release related resource
if(g_jvm->DetachCurrentThread() != JNI_OK)
{
LOGD("%s: DetachCurrentThread() failed", __FUNCTION__);
}