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__);
}
Related
When my java class loads, I call this jni method to set "listener" from cpp to java (I record audio using cpp and want to pass its bytes to java) :
MyJava.class
setListener(JNIEnv *env, jclass thiz);
myCpp.cpp
setListener(JNIEnv *env, jclass thiz) {
envMyClass = env;
classMyClass = thiz;
// I read that I need these 2 lines in order to connect the java thread to the cpp thread
env->GetJavaVM(&javavm);
GetJniEnv(javavm, &envCamera);
return 0;
}
bool GetJniEnv(JavaVM *vm, JNIEnv **env) {
bool did_attach_thread = false;
*env = nullptr;
// Check if the current thread is attached to the VM
auto get_env_result = vm->GetEnv((void**)env, JNI_VERSION_1_6);
if (get_env_result == JNI_EDETACHED) {
if (vm->AttachCurrentThread(env, NULL) == JNI_OK) {
did_attach_thread = true;
} else {
// Failed to attach thread. Throw an exception if you want to.
}
} else if (get_env_result == JNI_EVERSION) {
// Unsupported JNI version. Throw an exception if you want to.
}
return did_attach_thread;
}
and the in myCpp.cpp thread I'm trying to call:
if (envMyClass != nullptr && classMyClass != nullptr && javavm != nullptr) {
LOGD("000");
jmethodID javaMethod = envMyClass->GetStaticMethodID(classMyClass, "myJavaFunction", "()V");
LOGD("001");
envMyClass->CallStaticVoidMethod(classMyClass, javaMethod);
}
and it crashes on the line of "jmethodId javaMethod.."
myJavaFunction is a method in MyJava class:
public static void myJavaFunction() {
Log.d("my_log", "jni callback");
}
the crash:
Abort message: 'JNI DETECTED ERROR IN APPLICATION: a thread (tid 12346 is making JNI calls without being attached
in call to GetStaticMethodID'
Any idea how to fix it?
I've noticed a few things:
In setListener, I assume you are assigning jclass thiz to a global variable classMyClass. To do so, you should use classMyClass = env->NewGlobalRef(thiz), otherwise the class reference will not be valid (reference).
You are calling GetJniEnv but you do not check its result and therefore you do not actually know whether the current thread has attached successfully.
I assume you are attaching the current thread in setListener, save the env to a global variable, and then use that variable at another point in your code. Could it be that you are switching thread context somewhere in between? You could do the following to make sure the current thread is really attached:
JNIEnv *thisEnv;
int getEnvStat = jvm->GetEnv((void **)&thisEnv, JNI_VERSION_1_6 /* or whatever your JNI version is*/);
if (getEnvStat == JNI_EDETACHED)
{
if (jvm->AttachCurrentThread(&thisEnv, NULL) != 0)
{
// throw error
}
else
{
jmethodID javaMethod = thisEnv->GetStaticMethodID(classMyClass, "myJavaFunction", "()V");
thisEnv->CallStaticVoidMethod(classMyClass, javaMethod);
}
}
else if (getEnvStat == JNI_OK)
{
jmethodID javaMethod = thisEnv->GetStaticMethodID(classMyClass, "myJavaFunction", "()V");
thisEnv->CallStaticVoidMethod(classMyClass, javaMethod);
}
else if (getEnvStat == JNI_EVERSION)
{
// throw error
}
}
You can make this more efficient if you call jmethodID javaMethod = thisEnv->GetStaticMethodID(classMyClass, "myJavaFunction", "()V"); only once initially and store the jmethodID in a global variable. jmethodIDs are valid across envs.
I am trying to have Java call a C function in Android and some caveats that I am met with is the documentation for callbacks in general seem very unclear and most answers on SO are just talking about theory and posting broken links with no actual code. So please post code in your answer.
I am trying to create a joystick in java and have it send input to the NDK to perform actions.
if I can just have Java call a C function and pass a int to it ,
that would be good. but I think that wont work because C in running
it's own main loop.
perhaps I need to use C to call a java
function to get controller state and execute code from there.
I did manage to find some code Here but it seems the code while it does compile by itself, but doesn't appear to work when moved to modern code .
How should I implement call backs in a Java C relationship on Android? It seems that from the code example above that it is C that is calling Java functions and these need to be used like getters functions.
Here is some code that I have and I appear to be missing somethings because it is unclear to me how I should have these call backs performed. I will mark this code with function name and where is it located, if java or C++ (CPP) .
I think there are two ways that I would like this to work.
My Javafunction that I want to call
public int Cat(){
Log.e("JniHandler", "CAT WAS CALLED!!!");
return 333;
}
Structure information CPP . Not sure if it is needed.
#include <string.h>
#include <inttypes.h>
#include <pthread.h>
#include <jni.h>
#include <android/log.h>
#include <assert.h>
typedef struct tick_context {
JavaVM *javaVM;
jclass jniHelperClz;
jobject jniHelperObj;
jclass mainActivityClz;
jobject mainActivityObj;
pthread_mutex_t lock;
int done;
} TickContext;
TickContext g_ctx;
This section works. It looks that as soon as the JVM is launched, this will initialize.
JNI_OnLoad CPP
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
memset(&g_ctx, 0, sizeof(g_ctx));
SDL_Log(" DEBUG -- JNI INIT!!!");
g_ctx.javaVM = vm;
if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR; // JNI version not supported.
}
jclass clz = env->FindClass("org/foo/bar/mainactivity");
g_ctx.jniHelperClz = (jclass)env->NewGlobalRef(clz); // hack
jmethodID jniHelperCtor = env->GetMethodID(g_ctx.jniHelperClz,"<init>", "()V");
jobject handler = env->NewObject(g_ctx.jniHelperClz, jniHelperCtor);
g_ctx.jniHelperObj = env->NewGlobalRef(handler);
//queryRuntimeInfo(env, g_ctx.jniHelperObj); // This when getting version returns null.
g_ctx.done = 0;
g_ctx.mainActivityObj = NULL;
return JNI_VERSION_1_6;
}
queryRuntimeInfo CPP
void queryRuntimeInfo(JNIEnv *env, jobject instance) {
SDL_Log("DEBUG QUERYRUNTIME INTO");
// Find out which OS we are running on. It does not matter for this app
// just to demo how to call static functions.
// Our java JniHelper class id and instance are initialized when this
// shared lib got loaded, we just directly use them
// static function does not need instance, so we just need to feed
// class and method id to JNI
jmethodID versionFunc = env->GetStaticMethodID(g_ctx.jniHelperClz, "getBuildVersion", "()Ljava/lang/String;"); // This returns null.
if (!versionFunc) {
SDL_Log("DEBUG versionFunc INTO");
//LOGE("Failed to retrieve getBuildVersion() methodID # line %d",__LINE__);
return;
}
SDL_Log("DEBUG BUILD VERSION INTO");
jstring buildVersion = (jstring)env->CallStaticObjectMethod(g_ctx.jniHelperClz, versionFunc); // hack
const char *version = env->GetStringUTFChars(buildVersion, NULL);
if (!version) {
SDL_Log("DEBUG buildVersion INTO");
//LOGE("Unable to get version string # line %d", __LINE__);
return;
}
//LOGI("Android Version - %s", version);
SDL_Log("DEBUG ANDROID VERSION %s", version);
env->ReleaseStringUTFChars(buildVersion, version);
// we are called from JNI_OnLoad, so got to release LocalRef to avoid leaking
env->DeleteLocalRef(buildVersion);
// Query available memory size from a non-static public function
// we need use an instance of JniHelper class to call JNI
jmethodID memFunc = env->GetMethodID(g_ctx.jniHelperClz, "getRuntimeMemorySize", "()J");
if (!memFunc) {
//LOGE("Failed to retrieve getRuntimeMemorySize() methodID # line %d",__LINE__);
SDL_Log("DEBUG Failed to retrieve memory size");
return;
}
jlong result = env->CallLongMethod(instance, memFunc);
//LOGI("Runtime free memory size: %" PRId64, result);
SDL_Log("DEBUG RUN TIME FREE MEMORY SIZE");
(void)result; // silence the compiler warning
}
This function isn't ever called and I am not sure who is the caller. CPP
extern "C" JNIEXPORT void JNICALL InvokeFunction(JNIEnv *env, jobject instance) {
SDL_Log("JNI CALL START TICKET");
pthread_t threadInfo_;
pthread_attr_t threadAttr_;
pthread_attr_init(&threadAttr_);
pthread_attr_setdetachstate(&threadAttr_, PTHREAD_CREATE_DETACHED);
pthread_mutex_init(&g_ctx.lock, NULL);
jclass clz = env->GetObjectClass(instance);
g_ctx.mainActivityClz = (jclass)env->NewGlobalRef(clz); // hack
g_ctx.mainActivityObj = env->NewGlobalRef(instance);
int result = pthread_create( &threadInfo_, &threadAttr_, UpdateTicks, &g_ctx);
assert(result == 0);
pthread_attr_destroy(&threadAttr_);
(void)result;
}
updateticks CPP // This code is never actually called.
void* UpdateTicks(void* context) {
SDL_Log("DEBUG -- UPDATE TICKS IS CALLED");
TickContext *pctx = (TickContext*) context;
JavaVM *javaVM = pctx->javaVM;
JNIEnv *env;
jint res = javaVM->GetEnv((void**)&env, JNI_VERSION_1_6);
if (res != JNI_OK) {
res = javaVM->AttachCurrentThread(&env, NULL);
if (JNI_OK != res) {
//LOGE("Failed to AttachCurrentThread, ErrorCode = %d", res);
SDL_Log("FAILED TO ATTACH THREAD");
return NULL;
}
}
jmethodID statusId = env->GetMethodID(pctx->jniHelperClz, "updateStatus", "(Ljava/lang/String;)V");
//sendJavaMsg(env, pctx->jniHelperObj, statusId, "TickerThread status: initializing...");
// get mainActivity updateTimer function
jmethodID timerId = env->GetMethodID(pctx->mainActivityClz, "updateTimer", "()V");
struct timeval beginTime, curTime, usedTime, leftTime;
const struct timeval kOneSecond = {
(__kernel_time_t)1,
(__kernel_suseconds_t) 0
};
//sendJavaMsg(env, pctx->jniHelperObj, statusId, "TickerThread status: start ticking ...");
while(1) {
gettimeofday(&beginTime, NULL);
pthread_mutex_lock(&pctx->lock);
int done = pctx->done;
if (pctx->done) {
pctx->done = 0;
}
pthread_mutex_unlock(&pctx->lock);
if (done) {
break;
}
env->CallVoidMethod(pctx->mainActivityObj, timerId);
gettimeofday(&curTime, NULL);
timersub(&curTime, &beginTime, &usedTime);
timersub(&kOneSecond, &usedTime, &leftTime);
struct timespec sleepTime;
sleepTime.tv_sec = leftTime.tv_sec;
sleepTime.tv_nsec = leftTime.tv_usec * 1000;
if (sleepTime.tv_sec <= 1) {
nanosleep(&sleepTime, NULL);
} else {
//sendJavaMsg(env, pctx->jniHelperObj, statusId, "TickerThread error: processing too long!");
}
}
//sendJavaMsg(env, pctx->jniHelperObj, statusId, "TickerThread status: ticking stopped");
javaVM->DetachCurrentThread();
return context;
}
I'm trying to build a callback class to make a call to a Java method from different threads in my native code, on Android. I have read a lot about how to that, and as long as I'm on the same thread, it all works. But from a different thread I can't retrieve the JNIEnv properly, and I can't figure out what I'm doing wrong.
I'm not very experienced with C++ and the JNI so it's quite possible this is some beginner's problem...but I've spetd days on it and can't see what it is.
This is my Callback class, .h and .cpp file:
class AudioCallback {
public:
explicit AudioCallback(JavaVM&, jobject);
void playBackProgress(int progressPercentage);
private:
JavaVM& g_jvm;
jobject g_object;
};
jclass target = NULL;
jmethodID id = NULL;
AudioCallback::AudioCallback(JavaVM &jvm, jobject object) : g_jvm(jvm), g_object(object) {
JNIEnv *g_env;
int getEnvStat = g_jvm.GetEnv((void **) &g_env, JNI_VERSION_1_6);
if (g_env != NULL) {
target = g_env->GetObjectClass(g_object);
id = g_env->GetMethodID(target, "integerCallback", "(I)V");
//This is a test call to see if I can call my java method. It works.
g_env->CallVoidMethod(g_object, id, (jint) 103);
}
}
// this method is calles from other threads, so I want to attach to the current thread once I got my JNIEnv, but I can't since it's null...
void AudioCallback::playBackProgress(int progressPercentage) {
JNIEnv *g_env;
// This is null and I don't know why!
int getEnvStat = g_jvm.GetEnv((void **) &g_env, JNI_VERSION_1_6);
if (g_env == NULL) {
LOGE("JNIEnv in callback method is null");
} else {
LOGD("Env Stat: %d", getEnvStat);
JavaVMAttachArgs vmAttachArgs;
if (getEnvStat == JNI_EDETACHED) {
LOGD("GetEnv: not attached - attaching");
if (g_jvm.AttachCurrentThread(&g_env, &vmAttachArgs) != 0) {
LOGD("GetEnv: Failed to attach");
}
} else if (getEnvStat == JNI_OK) {
LOGD("GetEnv: JNI_OK");
} else if (getEnvStat == JNI_EVERSION) {
LOGD("GetEnv: version not supported");
}
g_env->CallVoidMethod(g_object, id, (jint) progressPercentage);
//thread gets detached elsewhere
}
}
This is my native_lib, where I get the JavaVM and instantiate the callback class:
std::unique_ptr<AudioEngine> audioEngine;
std::unique_ptr<AudioCallback> callback;
JavaVM *g_jvm = nullptr;
static jobject myJNIClass;
jint JNI_OnLoad(JavaVM *pJvm, void *reserved) {
g_Jvm = pJvm;
return JNI_VERSION_1_6;
}
JNIEXPORT void JNICALL
Java_com_my_appy_common_jni_JniBridge_playFromJNI(JNIEnv *env, jobject instance,jstring URI) {
myJNIClass = env->NewGlobalRef(instance);
callback = std::make_unique<AudioCallback>(*gJvm, myJNIClass);
// this test call to my callback works
callback->playBackProgress(104);
const char *uri = env->GetStringUTFChars(URI, NULL);
//... urelated code is left out here ...
//audioEngine gets the callback and uses it from threads it creates
audioEngine = std::make_unique<AudioEngine>(*extractor, *callback);
audioEngine->setFileName(uri);
audioEngine->start();
}
I've shortened the code and removed all unrelated/unnecessary parts. If something crucial is missing, please comment and I'll add it.
Solution: As per the suggestions #Michael made in his answer, I made these edits to the playbackProgressmethod in my callback class to make it work:
void AudioCallback::playBackProgress(int progressPercentage) {
JNIEnv *g_env;
int getEnvStat = g_jvm.GetEnv((void **) &g_env, JNI_VERSION_1_6);
if (getEnvStat == JNI_EDETACHED) {
LOGD("GetEnv: not attached - attaching");
if (g_jvm.AttachCurrentThread(&g_env, NULL) != 0) {
LOGD("GetEnv: Failed to attach");
}
} else if (getEnvStat == JNI_OK) {
LOGD("GetEnv: JNI_OK");
} else if (getEnvStat == JNI_EVERSION) {
LOGD("GetEnv: version not supported");
}
g_env->CallVoidMethod(g_object, id, (jint) progressPercentage);
// mJvm.DetachCurrentThread();
}
Now it checks for the value of getEnvStat directly, the null check of g_env before was wrong. I also had to replace the JavaVMAttachArgswith NULLto make it work.
Your logic in playBackProgress wrong.
The only time your try to attach the current thread is when g_env is non-NULL. But if g_env is non-NULL then GetEnv probably succeeded (you should of course also check that getEnvStat == JNI_OK) and AttachCurrentThread isn't necessary.
The case in which you need to call AttachCurrentThread is when g_env is NULL and getEnvStat is JNI_EDETACHED.
You also need to keep track of whether you did call AttachCurrentThread, since you should call DetachCurrentThread at some point in those cases. See this answer for more info on that.
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);