11 12月 2013

[JNI] 在C/C++中透過 SWIG 取得 JNIEnv or JavaVM

在 C/C++ layer 裡,想要 call Java layer 的 function 的話,適必需要 JNIEnv or JavaVM 這兩個 instance pointer,而透過 SWIG 包裝起來的 C/C++ layer 是不能直接接觸到 JNI layer,必須要一些手法才行。



.i 檔,偷埋一些 function

MyNDK.i

%module MyNDK
%{
    #include "MyNDK.h"
}%

%include "MyNDK.h"

%init %{
    JavaVM* g_cached_jvm = NULL;
    JNIEnv* g_cached_env = NULL;
    
    jint JNI_OnLoad(JavaVM *vm, void *reserved)
    {
        g_cached_jvm = vm;
        if (vm->GetEnv((void**)&g_cached_env, JNI_VERSION_1_6) != JNI_OK) {
            return -1;
        }
        return JNI_VERSION_1_6;
    }

    JavaVM* JNI_GetVM()
    {
        return g_cached_jvm;
    }
    
    JNIEnv* JNI_GetEnv()
    {
        return g_cached_env;
    }
%}

MyNDK.h

#ifndef __MY_NDK_H__
#define __MY_NDK_H__
#include <jni.h>
const char* GetLocalStorageDir(jobject activity);
#endif //__MY_NDK_H__


在 .cpp 檔裡,透過剛偷埋的 function ,就能拿到 JNIEnv or JavaVM 了

MyNDK.cpp

#include "MyNDK.h"
#include <string>

extern JNIEnv* JNI_GetEnv();
std::string gCachedLocalStorageDir;

const char* GetLocalStorageDir(jobject activity)
{
    JNIEnv* env = JNI_GetEnv();
    jclass clsContext = env->FindClass("android/content/Context");
    jclass clsFile = env->FindClass("java/io/File");
    jmethodID midGetFilesDir = env->GetMethodID(clsContext, "getFilesDir","()Ljava/io/File;");
    jmethodID midGetPath = env->GetMethodID(clsFile, "getPath","()Ljava/lang/String;");
    jobject objFile = env->CallObjectMethod(activity, midGetFilesDir);
    jstring objPath = (jstring) env->CallObjectMethod(objFile, midGetPath);
    const char* path = env->GetStringUTFChars(objPath, NULL);
    gCachedLocalStorageDir = path;
    env->ReleaseStringUTFChars(objPath, path);
    return gCachedLocalStorageDir.c_str();
}


記住,如果是在 C/C++ layer create 的 pthread 裡,想呼叫 JNI 的 code,一定要 AttachCurrentThread,否則,可能會造成 AP crash!因為 JNIEnv* 只能在同一條 thread 取得與使用。不要試著拿 global 的 JNIEnv* 變數,因為可能是別條 thread 所 created 的,像上面的例子,就是 JNI_OnLoad() 時,拿到的 JNIEnv* ,不能給其他條 thread 所用。

AttachCurrentThread sample code :

bool ProcessJNISafe()
{
    bool isDoAttach = false;
    JavaVM* jvm = JNI_GetVM();
    JNIEnv* env = NULL;
    
    // Get JNIEnv & try Attach
    jint ret = jvm->GetEnv((void **)&env, JNI_VERSION_1_6);
    if( ret == JNI_EDETACHED ) {
        if( jvm->AttachCurrentThread(&env, NULL) < 0 )
            return false;
        isDoAttach = true;
    }
    else if( ret != JNI_OK ) {
        return false;
    }
    
    // process JNI layer here
    {
    }
    
    // Detach
    if(isDoAttach)
    {
        jvm->DetachCurrentThread();
    }
    return true;
}


Reference:

沒有留言: