JNI学习

JNI回调是指在c/c++代码中调用java函数,当在c/c++的线程中执行回调函数时,会导致回调失败。

其中一种在Android系统的解决方案是:

把c/c++中所有线程的创建,由pthread_create函数替换为由Java层的创建线程的函数AndroidRuntime::createJavaThread。

所谓的报错,无非就是JNIEnv是线程保存的,c/c++线程是没有保存JNIEnv的。
当你用的是第三方c库,线程是人家创建好的,就没办法解决了。
用g_Jvm->AttachCurrentThread(&env,NULL);能把当前c线程转为java线程。

当在一个线程里面调用AttachCurrentThread后,如果不需要用的时候一定要DetachCurrentThread,否则线程无法正常退出。

  1. static JNIEnv *Adapter_GetEnv()  
  2. {  
  3.     int status;  
  4.     JNIEnv *envnow = NULL;  
  5.     status = (*g_JavaVM)->GetEnv(g_JavaVM,(void **) &envnow, JNI_VERSION_1_4);  
  6.     if(status < 0)  
  7.     {  
  8.         status = (*g_JavaVM)->AttachCurrentThread(g_JavaVM,&envnow, NULL);  
  9.         if(status < 0)  
  10.         {  
  11.             return NULL;  
  12.         }  
  13.         g_bAttatedT = TRUE;  
  14.     }  
  15.     return envnow;  
  16. }  
  17.   
  18. static void DetachCurrent()  
  19. {  
  20.     if(g_bAttatedT)  
  21.     {  
  22.         (*g_JavaVM)->DetachCurrentThread(g_JavaVM);  
  23.     }  



假设有c++函数:

  1. void *thread_entry(void *args)  
  2. {  
  3.     while(1)  
  4.     {  
  5.         printf("thread running...\n");  
  6.         sleep(1);  
  7.     }  
  8.       
  9.       
  10. }  
  11.   
  12. void init()  
  13. {     
  14.     pthread_t thread;  
  15.     pthread_create(&thread,NULL,thread_entry,(void *)NULL);  
  16. }  

init()函数创建一个线程,需要在该线程中调用java类Test的回调函数Receive:

  1. public void Receive(char buffer[],int length){  
  2.         String msg = new String(buffer);  
  3.         msg = "received from jni callback:" + msg;  
  4.         Log.d("Test", msg);  
  5. }  


首先在c++中定义回调函数指针:

  1. //test.h  
  2. #include <pthread.h>  
  3. //function type for receiving data from native  
  4. typedef void (*ReceiveCallback)(unsigned char *buf, int len);  
  5.   
  6. /** Callback for creating a thread that can call into the Java framework code. 
  7.  *  This must be used to create any threads that report events up to the framework. 
  8.  */  
  9. typedef pthread_t (* CreateThreadCallback)(const char* name, void (*start)(void *), void* arg);  
  10.   
  11. typedef struct{  
  12.     ReceiveCallback recv_cb;  
  13.     CreateThreadCallback create_thread_cb;  
  14. }Callback;  

再修改c++中的init和thread_entry函数:

  1. //test.c  
  2. #include <stdio.h>  
  3. #include <stdlib.h>  
  4. #include <pthread.h>  
  5. #include <sys/wait.h>  
  6. #include <unistd.h>  
  7. #include "test.h"  
  8.   
  9. void *thread_entry(void *args)  
  10. {  
  11.     char *str = "i'm happy now";  
  12.     Callback cb = NULL;  
  13.     int len;  
  14.     if(args != NULL){  
  15.         cb = (Callback *)args;    
  16.     }  
  17.       
  18.     len = strlen(str);  
  19.     while(1)  
  20.     {  
  21.         printf("thread running...\n");  
  22.         //invoke callback method to java  
  23.         if(cb != NULL && cb->recv_cb != NULL){  
  24.             cb->recv_cb((unsigned char*)str, len);  
  25.         }  
  26.         sleep(1);  
  27.     }  
  28.       
  29.       
  30. }  
  31.   
  32. void init(Callback *cb)  
  33. {     
  34.     pthread_t thread;  
  35.     //pthread_create(&thread,NULL,thread_entry,(void *)NULL);  
  36.     if(cb != NULL && cb->create_thread_cb != NULL)  
  37.     {  
  38.         cb->create_thread_cb("thread",thread_entry,(void *)cb);  
  39.     }  
  40. }  


然后在jni中实现回调函数,以及其他实现:

  1. //jni_test.c  
  2. #include <stdlib.h>  
  3. #include <malloc.h>  
  4. #include <jni.h>  
  5. #include <JNIHelp.h>  
  6. #include "android_runtime/AndroidRuntime.h"  
  7.   
  8. #include "test.h"  
  9. #define RADIO_PROVIDER_CLASS_NAME "com/tonny/Test"  
  10.   
  11.   
  12. using namespace android;  
  13.   
  14.   
  15. static jobject mCallbacksObj = NULL;  
  16. static jmethodID method_receive;  
  17.   
  18.   
  19.   
  20. static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {  
  21.     if (env->ExceptionCheck()) {  
  22.         LOGE("An exception was thrown by callback '%s'.", methodName);  
  23.         LOGE_EX(env);  
  24.         env->ExceptionClear();  
  25.     }  
  26. }  
  27.   
  28.   
  29.   
  30. static void receive_callback(unsigned char *buf, int len)  
  31. {  
  32.     int i;  
  33.     JNIEnv* env = AndroidRuntime::getJNIEnv();  
  34.     jcharArray array = env->NewCharArray(len);  
  35.     jchar *pArray ;  
  36.       
  37.     if(array == NULL){  
  38.         LOGE("receive_callback: NewCharArray error.");  
  39.         return;   
  40.     }  
  41.   
  42.     pArray = (jchar*)calloc(len, sizeof(jchar));  
  43.     if(pArray == NULL){  
  44.         LOGE("receive_callback: calloc error.");  
  45.         return;   
  46.     }  
  47.   
  48.     //copy buffer to jchar array  
  49.     for(i = 0; i < len; i++)  
  50.     {  
  51.         *(pArray + i) = *(buf + i);  
  52.     }  
  53.     //copy buffer to jcharArray  
  54.     env->SetCharArrayRegion(array,0,len,pArray);  
  55.     //invoke java callback method  
  56.     env->CallVoidMethod(mCallbacksObj, method_receive,array,len);  
  57.     //release resource  
  58.     env->DeleteLocalRef(array);  
  59.     free(pArray);  
  60.     pArray = NULL;  
  61.       
  62.     checkAndClearExceptionFromCallback(env, __FUNCTION__);  
  63. }  
  64.   
  65. static pthread_t create_thread_callback(const char* name, void (*start)(void *), void* arg)  
  66. {  
  67.     return (pthread_t)AndroidRuntime::createJavaThread(name, start, arg);  
  68. }  
  69.   
  70. static Callback mCallbacks = {  
  71.     receive_callback,  
  72.     create_thread_callback  
  73. };  
  74.   
  75.   
  76.   
  77. static void jni_class_init_native  
  78. (JNIEnv* env, jclass clazz)  
  79. {  
  80.     method_receive = env->GetMethodID(clazz, "Receive""([CI)V");  
  81. }  
  82.   
  83. static int jni_init  
  84. (JNIEnv *env, jobject obj)  
  85. {  
  86.   
  87.       
  88.     if (!mCallbacksObj)  
  89.         mCallbacksObj = env->NewGlobalRef(obj);  
  90.       
  91.     return init(&mCallbacks);  
  92. }  
  93.   
  94. static const JNINativeMethod gMethods[] = {    
  95.     { "class_init_native",          "()V",          (void *)jni_class_init_native },  
  96.     { "native_init",                "()I",          (void *)jni_init },  
  97. };    
  98.   
  99.   
  100.   
  101. static int registerMethods(JNIEnv* env) {    
  102.   
  103.   
  104.     const charconst kClassName = RADIO_PROVIDER_CLASS_NAME;  
  105.     jclass clazz;     
  106.     /* look up the class */    
  107.     clazz = env->FindClass(kClassName);    
  108.     if (clazz == NULL) {    
  109.         LOGE("Can't find class %s/n", kClassName);    
  110.         return -1;    
  111.     }    
  112.     /* register all the methods */    
  113.     if (env->RegisterNatives(clazz,gMethods,sizeof(gMethods)/sizeof(gMethods[0])) != JNI_OK)    
  114.     {    
  115.         LOGE("Failed registering methods for %s/n", kClassName);    
  116.         return -1;    
  117.     }    
  118.     /* fill out the rest of the ID cache */    
  119.     return 0;    
  120. }     
  121.   
  122.   
  123. jint JNI_OnLoad(JavaVM* vm, void* reserved) {   
  124.     JNIEnv* env = NULL;    
  125.     jint result = -1;    
  126.     LOGI("Radio JNI_OnLoad");    
  127.         if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {    
  128.         LOGE("ERROR: GetEnv failed/n");    
  129.         goto fail;    
  130.     }    
  131.   
  132.     if(env == NULL){  
  133.         goto fail;  
  134.     }  
  135.     if (registerMethods(env) != 0) {   
  136.         LOGE("ERROR: PlatformLibrary native registration failed/n");    
  137.         goto fail;    
  138.     }    
  139.     /* success -- return valid version number */        
  140.     result = JNI_VERSION_1_4;    
  141. fail:    
  142.     return result;    
  143. }   

jni的Android.mk文件中共享库设置为:

  1. LOCAL_SHARED_LIBRARIES := liblog libcutils libandroid_runtime libnativehelper  

最后再实现Java中的Test类:

  1. //com.tonny.Test.java  
  2.   
  3. public class Test {  
  4.   
  5.     static{  
  6.         try {  
  7.             System.loadLibrary("test");  
  8.             class_init_native();  
  9.               
  10.         } catch(UnsatisfiedLinkError ule){  
  11.             System.err.println("WARNING: Could not load library libtest.so!");  
  12.         }  
  13.           
  14.     }  
  15.       
  16.       
  17.   
  18.     public int initialize() {  
  19.         return native_radio_init();  
  20.     }  
  21.   
  22.     public void Receive(char buffer[],int length){  
  23.         String msg = new String(buffer);  
  24.         msg = "received from jni callback" + msg;  
  25.         Log.d("Test", msg);  
  26.     }  
  27.       
  28.       
  29.       
  30.     protected  static native void class_init_native();  
  31.       
  32.     protected  native int native_init();  
  33.   




/*
 * Returns a new java.io.FileDescriptor for the given int fd.
 */
jobject jniCreateFileDescriptor(C_JNIEnv* env, int fd);

/*
 * Returns the int fd from a java.io.FileDescriptor.
 */
int jniGetFDFromFileDescriptor(C_JNIEnv* env, jobject fileDescriptor);

/*
 * Sets the int fd in a java.io.FileDescriptor.
 */
void jniSetFileDescriptorOfFD(C_JNIEnv* env, jobject fileDescriptor, int value);

JNIHelp.h    dalvik\libnativehelper\include\Nativehelper 

jobject jniCreateFileDescriptor(C_JNIEnv* env, int fd) {
    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
    jobject fileDescriptor = (*env)->NewObject(e,
            gCachedFields.fileDescriptorClass, gCachedFields.fileDescriptorCtor);
    jniSetFileDescriptorOfFD(env, fileDescriptor, fd);
    return fileDescriptor;
}

int jniGetFDFromFileDescriptor(C_JNIEnv* env, jobject fileDescriptor) {
    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
    return (*env)->GetIntField(e, fileDescriptor, gCachedFields.descriptorField);
}

void jniSetFileDescriptorOfFD(C_JNIEnv* env, jobject fileDescriptor, int value) {
    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
    (*env)->SetIntField(e, fileDescriptor, gCachedFields.descriptorField, value);
}

1、 用来获取数组元素的JNI函数集

GetBooleanArrayElements,

GetByteArrayElements,

GetCharArrayElements,

GetShortArrayElements,

GetIntArrayElements,

GetLongArrayElements,

GetFloatArrayElements,

GetDoubleArrayElements;

2、 用来释放数组数据的函数集

ReleaseBooleanArrayElements,

ReleaseByteArrayElements,

ReleaseCharArrayElements,

ReleaseShortArrayElements,

ReleaseIntArrayElements,

ReleaseLongArrayElements,

ReleaseFloatArrayElements,

ReleaseDoubleArrayElements;

    5.4.4 JNI中得主要方法

1、 用来处理字符串对象的函数组合

GetStringChars                                                获取字符串中的字符

GetStringLength                                             获取字符串的长度

GetStringUTFChars                                       获取字符串中得UTF字符

GetStringUTFLength                                     获取字符串UTF字符长度

NewString                                                        创建新的字符串

NewStringUTF                                                创建新的UTF字符串

ReleaseStringChars                                        释放字符串字符

ReleaseStringUTFChars                                释放字符串UTF字符

2、 用来处理数组对象的函数集合

GetArrayLength                                              获取数组的长度

Get<type>ArrayElements                            获取相应类型的数组元素

Release<type>ArrayElements                    释放相应类型的数组元素

Get<type>ArrayRegion                                 获取相应类型的数组的区域元素

Set<type>ArrayRegion                                设置相应类型的数组的区域元素

GetObjectArrayElement                              获取对象类型的数组元素

SetObjectArrayElement                              设置对象类型的数组元素

3、 用来处理方法的函数集合

GetObjectClass                                              获取对象类

GetMethodID                                                 获取方法ID

GetStaticMethodID                                      获取静态方法ID

Call<type>Method                                       调用返回值为<type>型的方法

CallStatic<returntype>Method                 调用相应返回值类型的静态方法

4、 用来处理成员变量的函数集合

GetFieldID                                                     获取数据域标志

GetStaticFieldID                                           获取静态数据域标志

Get<type>Field                                             获取<type>数据域

Set<type>Field                                             设置相应类型的数据域

GetStatic<type>Field                                  获取相应类型的静态数据域

SetStatic<type>Field                                   设置相应类型的静态数据域

5、 用来处理异常的函数集合

ExceptionClear                                              异常清除

ExceptionDescribe                                        输出异常调试信息

ExceptionOccurred                                        捕获异常

6、 用来处理引用的函数集合

NewGlobalRef                                               创建一个全局引用

DeleteGlobalRef                                            删除一个全局引用

DeleteLocalRef                                              删除一个局部引用

7、 用来处理线程同步的函数集合

MonitorEnter                                                 监视线程进入同步块

MonitorExit                                                    监视线程退出同步块


JNI 的某些数组和字符串类型转换

jbytearray转c++byte数组

C代码 收藏代码

    jbyte * arrayBody = env->GetByteArrayElements(data,0);   
    jsize theArrayLengthJ = env->GetArrayLength(data);   
    BYTE * starter = (BYTE *)arrayBody;   

jbyteArray 转 c++中的BYTE[]

C代码 收藏代码

    //jbytearray strIn
    jbyte * olddata = (jbyte*)env->GetByteArrayElements(strIn, 0);  
    jsize  oldsize = env->GetArrayLength(strIn);  
    BYTE* bytearr = (BYTE*)olddata;  
    int len = (int)oldsize;  

C++中的BYTE[]转jbyteArray

C代码 收藏代码

    //nOutSize是BYTE数组的长度 BYTE pData[]
    jbyte *by = (jbyte*)pData;  
    jbyteArray jarray = env->NewByteArray(nOutSize);  
    env->SetByteArrayRegin(jarray, 0, nOutSize, by);  

jbyteArray 转 char *

C代码 收藏代码

    char* data = (char*)env->GetByteArrayElements(strIn, 0);  

char* 转jstring

C代码 收藏代码

    jstring WindowsTojstring(JNIEnv* env, char* str_tmp)  
    {  
    jstring rtn=0;  
    int slen = (int)strlen(str_tmp);  
    unsigned short* buffer=0;  
    if(slen == 0)  
    {  
      rtn = env->NewStringUTF(str_tmp);  
    }  
    else
    {  
    int length = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)str_tmp, slen, NULL, 0);  
      buffer = (unsigned short*)malloc(length*2+1);  
    if(MultiByteToWideChar(CP_ACP, 0, (LPCSTR)str_tmp, slen, (LPWSTR)buffer, length) > 0)  
      {  
       rtn = env->NewString((jchar*)buffer, length);  
      }  
    }  
    if(buffer)  
    {  
      free(buffer);  
    }  
    return rtn;  
    }  

下面这个没有用过,刚看到,也写进来,以后如果遇到可以验证下看。
jstring 转 char* 或者 const char*

C代码 收藏代码

    // jstring str
    const char *key = env->GetStringUTFChars(str, 0);  
    //jboolean isOffer
    jsClient->modify(key, isOffer);  
    env->ReleaseStringUTFChars(str, key);  

JNI 返回 jbyteArray

C代码 收藏代码

    JNIEXPORT jbyteArray JNICALL Java_Test_getByteArray(JNIEnv *env, jobject obj)  
    {  
        jbyteArray firstMacArray = env->NewByteArray( 6 );  
        ......  
        jbyte *bytes = env->GetByteArrayElements( firstMacArray, 0);  
    for ( int i = 0; i < sizeof( pAdapterInfo->Address ); i++ )  
        {  
           bytes[ i ] = pAdapterInfo->Address[ i ];  
        }  
        env->SetByteArrayRegion(firstMacArray, 0, 6, bytes );  
    return firstMacArray;  
    }  

//jstring to char*

C代码 收藏代码

    char* jstringTostring(JNIEnv* env, jstring jstr)  
    {          
    char* rtn = NULL;  
      jclass clsstring = env->FindClass("java/lang/String");  
      jstring strencode = env->NewStringUTF("utf-8");  
      jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");  
      jbyteArray barr= (jbyteArray)env->CallObjectMethod(jstr, mid, strencode);  
      jsize alen = env->GetArrayLength(barr);  
      jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);  
    if (alen > 0)  
      {  
        rtn = (char*)malloc(alen + 1);  
        memcpy(rtn, ba, alen);  
        rtn[alen] = 0;  
      }  
      env->ReleaseByteArrayElements(barr, ba, 0);  
    return rtn;  
    }  

//char* to jstring

C代码 收藏代码

    jstring stoJstring(JNIEnv* env, const char* pat)  
    {  
    jclass strClass = env->FindClass("Ljava/lang/String;");  
    jmethodID ctorID = env->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");  
    jbyteArray bytes = env->NewByteArray(strlen(pat));  
    env->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*)pat);  
    jstring encoding = env->NewStringUTF("utf-8");  
    return (jstring)env->NewObject(strClass, ctorID, bytes, encoding);  
    }   

//将jstring类型转换成windows类型

C代码 收藏代码

    char* jstringToWindows( JNIEnv *env, jstring jstr )  
    {  
    int length = (env)->GetStringLength(jstr );  
    const jchar* jcstr = (env)->GetStringChars(jstr, 0 );  
    char* rtn = (char*)malloc( length*2+1 );  
    int size = 0;  
    size = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR)jcstr, length, rtn,(length*2+1), NULL, NULL );  
    if( size <= 0 )  
    return NULL;  
    (env)->ReleaseStringChars(jstr, jcstr );  
    rtn[size] = 0;  
    return rtn;  
    }  

//将windows类型转换成jstring类型

C代码 收藏代码

    jstring WindowsTojstring( JNIEnv* env, char* str )  
    {  
    jstring rtn = 0;  
    int slen = strlen(str);  
    unsigned short * buffer = 0;  
    if( slen == 0 )  
    rtn = (env)->NewStringUTF(str );  
    else
    {  
    int length = MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, NULL, 0 );  
    buffer = (unsigned short *)malloc( length*2 + 1 );  
    if( MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, (LPWSTR)buffer, length ) >0 )  
    rtn = (env)->NewString( (jchar*)buffer, length );  
    }  
    if( buffer )  
    free( buffer );  
    return rtn;  
    }  
    /*JNIEXPORT jstring JNICALL Java_test_cs_web_SWIFTAlianceCASmfTest_strcal
    (JNIEnv *env, jclass obj, jstring jstr1, jstring jstr2)
    {
    jbyteArray bytes = 0;
    jthrowable exc;
    char *pszResult = NULL;    
    char *pszSTR1 = NULL;
    char *pszSTR2 = NULL;
    pszSTR1 = jstringTostring(env, jstr1);
    pszSTR2 = jstringTostring(env, jstr2);
    int nlen = sizeof(char)*(strlen(pszSTR1)+strlen(pszSTR2));
    pszResult = (char*)malloc(nlen);
    strcpy(pszResult, pszSTR1);
    strcat(pszResult, pszSTR2);
    jstring jstrRe = stoJstring(env, pszResult);
    free(pszSTR1);
    free(pszSTR2);
    free(pszResult);
    return(jstrRe);
    }
    */

jni object的使用
每一个jni格式的dll中的object对应该java里面的一个类。
如下例有一个 ObjData类,类中有成员bData ,Len
public class ObjData {
  public byte[]  bData;
  public int Len;       
}
//------------------------jni获得传过来的Object类型的变量objDataIn--------
jclass clazz =(env)->FindClass("ObjData");
//从传进来的对象中取出byte[]

C代码 收藏代码

    jfieldID byteData = (env)->GetFieldID(clazz,"bData","[B");  
    jbyteArray pDataIn = (jbyteArray) (env)->GetObjectField(objDataIn, byteData);  
    jsize theArrayLeng = env->GetArrayLength(pDataIn);  

//byte[]转为BYTE[]

C代码 收藏代码

    jbyte * arrayBody = env->GetByteArrayElements(pDataIn,0);   
    BYTE * jDataIn = (BYTE *)arrayBody;   

//将BYTE数组转为jarray

C代码 收藏代码

    jbyte* byte = (jbyte*)jDataOut;      
    jbyteArray jarray = env->NewByteArray(theArrayLeng);  
    env->SetByteArrayRegion(jarray, 0, theArrayLeng, byte);  

//给每一个实例的变量付值

C代码 收藏代码

    (env)->SetObjectField(objDataIn,byteData,jarray);  
    (env)->SetIntField(objDataIn,pDataInLen,jDataInLen);  
    (env)->ReleaseByteArrayElements(pDataIn, arrayBody, 0);   

其他参考:
Java 通过JNI调用C或者CPP代码
http://blog.csdn.net/kenera/archive/2009/02/16/3895343.aspx
http://apps.hi.baidu.com/share/detail/15732549
http://dniit.blog.163.com/blog/static/28012894200842810332491/
http://hi.baidu.com/liangwind/blog/item/7dcce2c9729d1d1e7f3e6f49.html

from:http://www.cnblogs.com/yaozhongxiao/archive/2012/03/06/2382374.html

Java 的出现给大家开发带来的极大的方便。但是,如果我们有大量原有的经过广泛测试的非 Java 代码,将它们全部用 Java 来重写,恐怕会带来巨大的工作量和长期的测试;如果我们的应用中需要访问到特定的设备,甚至是仅符合公司内部信息交互规范的设备,或某个特定的操作系统才有的特性,Java 就显得有些力不从心了。面对这些问题,Sun 公司在 JDK1.0 中就定义了 JNI 规范,它规定了 Java 应用程序对本地方法的调用规则。

实现步骤及相关函数使用

本文将一步步说明在 Linux 平台下如何实现本地共享库与 Java 协同工作。Hello World 程序是目前标准的入门第一步,那么,我也以类似的应用最为样例。


jbyte   *arr   =   env-> GetByteArrayElements(jarr,   0);
只有C++时才用
在C中的写法应该是
jbyte   *   arr   =   (*env)-> GetByteArrayElements(env,jarr,   NULL);
---------------------------------------------------------------
在C的定义中,env是一个两级指针,而在C++的定义中,env是个一级指针



第一步,定义一个 Java 类 -- Hello. 它提供 SayHello 方法:

此时应注意两点:

1. 为要使用的每个本地方法编写本地方法声明,其声明方式与普通 Java 方法接口没什么不同,只是必须指定 native 关键字,如下所示:

public native void SayHello(String strName);

在这个函数中,我们将根据传进的人名,向某人问好。

2. 必须显式地加载本地代码库。我们需在类的一个静态块中加载这个库:

 static 
    { 
    System.loadLibrary("hello"); 
    } 
	

再加上必要的异常处理就生成如下源文件 Hello.java:

 public class Hello 
 { 
	 static 
	 { 
		 try 
		 { 
 // 此处即为本地方法所在链接库名
			 System.loadLibrary("hello"); 
		 } 
		 catch(UnsatisfiedLinkError e) 
		 { 
			 System.err.println( "Cannot load hello library:\n " + 
                                e.toString() ); 
		 } 
	 } 
	 public Hello() 
	 { 
	 } 
 // 声明的本地方法
		 public native void SayHello(String strName); 
 } 

编译后生成 Hello.class 文件。

第二步,生成本地链接库。具体过程如下:

1. 要为以上定义的类生成 Java 本地接口头文件,需使用 javah,Java 编译器的 javah 功能将根据 Hello 类生成必要的声明,此命令将生成 Hello.h 文件,我们在共享库的代码中要包含它,javah 不使默认内部命令,需要指明路径,它在 JDK 的 bin 目录下,在我的 Linux 环境下命令如下:

     

      这里所谓的bin目录要特别注意,是指workspace中 编译 ******.java 所生产 ****.class 所在的bin目录

      如上图所示  bin目录

 

/home/jbuilder/jdk1.3.1/bin/javah Hello

生成的 Hello.h 文件 内容如下所示:

 /* DO NOT EDIT THIS FILE - it is machine generated */ 
 #include <jni.h> 
 /* Header for class Hello */ 
 #ifndef _Included_Hello 
 #define _Included_Hello 
 #ifdef __cplusplus 
 extern "C" { 
 #endif 
 /* 
 * Class:     Hello 
 * Method:    SayHello 
 * Signature: (Ljava/lang/String;)V 
 */ 
 JNIEXPORT void JNICALL Java_Hello_SayHello 
  (JNIEnv *, jobject, jstring); 
 #ifdef __cplusplus 
 } 
 #endif 
 #endif 

2. 在与 Hello.h 相同的路径下创建一个 CPP 文件 Hello.cpp。内容如下:

 #include "Hello.h"
 #include <stdio.h> 
 // 与 Hello.h 中函数声明相同
 JNIEXPORT void JNICALL Java_Hello_SayHello  (JNIEnv * env, jobject arg, jstring instring) 
 { 
   // 从 instring 字符串取得指向字符串 UTF 编码的指针
 const jbyte *str = 
        (const jbyte *)env->GetStringUTFChars( instring, JNI_FALSE ); 
    printf("Hello,%s\n",str); 
	 // 通知虚拟机本地代码不再需要通过 str 访问 Java 字符串。
    env->ReleaseStringUTFChars( instring, (const char *)str ); 
    return; 
 } 

所有的 JNI 调用都使用了 JNIEnv * 类型的指针,习惯上在 CPP 文件中将这个变量定义为 evn,它是任意一个本地方法的第一个参数。env 指针指向一个函数指针表,在 VC 中可以直接用"->"操作符访问其中的函数。

jobject 指向在此 Java 代码中实例化的 Java 对象 LocalFunction 的一个句柄,相当于 this 指针。

后续的参数就是本地调用中有 Java 程序传进的参数,本例中只有一个 String 型参数。 对于字符串型参数,因为在本地代码中不能直接读取 Java 字符串,而必须将其转换为 C /C++ 字符串或 Unicode。以下是三个我们经常会用到的字符串类型处理的函数:

const char* GetStringUTFChars(jstring string,jboolean* isCopy)

返回指向字符串 UTF 编码的指针,如果不能创建这个字符数组,返回 null。这个指针在调用 ReleaseStringUTFChar() 函数之前一直有效。

参数:string 		 Java 字符串对象
 isCopy 		如果进行拷贝,指向以 JNI_TRUE 填充的 jboolean, 否则指向以 JNI_FALSE 填充的 jboolean。
 void ReleaseStringUTFChars(jstring str, const char* chars) 
通知虚拟机本地代码不再需要通过 chars 访问 Java 字符串。

参数:string 		 Java 字符串对象
 chars 		由 GetStringChars 返回的指针
 jstring NewStringUTF(const char *utf) 
返回一个新的 Java 字符串并将 utf 内容拷贝入新串,如果不能创建字符串对象,
返回 null。通常在反值类型为 string 型时用到。

参数:utf 		 UTF 编码的字符串指针
对于数值型参数,在 C/C++ 中可直接使用,其字节宽度如下所示:
Java C/C++ 字节数
boolean jboolean 1
byte jbyte 1
char jchar 2
short jshort 2
int jint 4
long jlong 8
float jfloat 4
double jdouble 8

对于数组型参数,

Java C/C++
boolean[ ] JbooleanArray
byte[ ] JbyteArray
char[ ] JcharArray
short[ ] JshortArray
int[ ] JintArray
long[ ] JlongArray
float[ ] JfloatArray
double[ ] JdoubleArray

对于上述类型数组,有一组函数与其对应。以下函数中 Xxx 为对应类型。 
xxx * GetXxxArrayElements(xxxArray array, jboolean *isCopy) 
产生一个指向 Java 数组元素的 C 指针。不再需要时,需将此指针传给 ReleaseXxxArrayElemes。

参数:array 			数组对象
 isCopy 		如果进行拷贝,指向以 JNI_TRUE 填充的 jboolean, 否则指向以 JNI_FALSE 填充的 jboolean。
例如: jboolean * GetBooleanArrayElements(jbooleanArray array, jboolean *isCopy) 
 void ReleaseXxxArrayElements(xxxArray array,xxx *elems, jint mode) 
通知虚拟机不再需要从 GetXxxArrayElements 得到的指针。
参数:array 			数组对象
 elems 		不再需要的指向数组元素的指针
 mode 		 0 =在更新数组元素后释放 elems 缓冲器
 JNI_COMMIT =在更新数组元素后不释放 elems 缓冲器
 JNI_ABORT =不更新数组元素释放 elems 缓冲器
例如:void ReleaseBooleanArrayElements(jbooleanArray array,jboolean *elems,     jint mode) 
 xxxArray NewXxxArray(jsize len) 
产生一个新的数组,通常在反值类型为数组型时用到
参数:len 		数组中元素的个数。
例如:jbooleanArray NewBooleanArray(jsize len)

3 .编译生成共享库。

使用 GCC 时 , 必须通知编译器在何处查找此 Java 本地方法的支持文件,并且显式通知编译器生成位置无关的代码,在我的环境中按如下过程编译:

gcc -I/home/jbuilder/jdk1.3.1/include 
    -I/home/jbuilder/jdk1.3.1/include/linux -fPIC -c Hello.c

生成 Hello.o

 gcc -shared -Wl,-soname,libhello.so.1 -o libhello.so.1.0 Hello.o 

生成 libhello.so.1.0

接下来将生成的共享库拷贝为标准文件名

cp libhello.so.1.0 libhello.so

最后通知动态链接程序此共享文件的路径。

export LD_LIBRARY_PATH=`pwd`:$LD_LIBRARY_PATH

4 .编写一个简单的 Java 程序来测试我们的本地方法。

将如下源码存为 ToSay.java:

 import Hello; 
 import java.util.*; 
 public class ToSay 
 { 
	 public static void main(String argv[]) 
	 { 
		 ToSay say = new ToSay(); 
	 } 
	 public ToSay() 
	 { 
		 Hello h = new Hello(); 
		 // 调用本地方法向 John 问好
		 h.SayHello("John"); 			
	 } 
 } 

用 javac 编译 ToSay.java,生成 ToSay.class 
向执行普通 Java 程序一样使用 java ToSay,我们会看到在屏幕上出现 Hello,John。 
到这里我们就将整个的本地调用编写过程大概浏览了一遍。

应用中注意事项

1 . 如果可以通过 TCP/IP 实现 Java 代码与本地 C/C++ 代码的交互工作,那么最好不使用以上提到的 JNI 的方式,因为一次 JNI 调用非常耗时,大概要花 0.5 ~ 1 个毫秒。

2 . 在一个 Applet 应用中,不要使用 JNI。因为在 applet 中可能引发安全异常。

3 . 将所有本地方法都封装在单个类中,这个类调用单个 DLL。对于每种目标操作系统,都可以用特定于适当平台的版本替换这个 DLL。这样就可以将本地代码的影响减至最小,并有助于将以后所需的移植问题包含在内。

4 . 本地方法要简单。尽量将生成的 DLL 对任何第三方运行时 DLL 的依赖减到最小。使本地方法尽量独立,以将加载 DLL 和应用程序所需的开销减到最小。如果必须要运行时 DLL,则应随应用程序一起提供它们。

5 . 本地代码运行时,没有有效地防数组越界错误、错误指针引用带来的间接错误等。所以必须保证保证本地代码的稳定性,因为,丝毫的错误都可能导致 Java 虚拟机崩溃。

javah生成.h文件时提示“找不到类”的解决方法  

当用javah为.clsaa文件生成.h文件的时候提示错误: 

 

 

 

 

 

 

 

 

解决方法:

      进入bin目录执行:   javah -classpath . -jni package_name.class_name

 


JNI是Java Native Interface的缩写,JNI是一种机制,有了它就可以在java程序中调用其他native代码,或者使native代码调用java层的代码。也就是说,有了JNI我们可以使Android项目中,java层与native层各自发挥所长并相互配合。

                                                          JAVA

                                                             | |

                                                            JNI

                                                             | |

                                                         NATIVE

      JNI相对与native层来说是一个接口,java层的程序想访问native层,必须通过JNI,反过来也一样。

 

1,如何告诉VM(虚拟机)java层需要调用native层的哪些libs?

        我们知道java程序是运行在VM上的,而Native层的libs则不然。所以为了让java层能访问native层的libs,必须得告诉VM要使用哪些native层的libs。下面看一段代码

[java]   view plain copy
  1. public class MediaPlayer    
  2.  {    
  3.      ...    
  4.      
  5.      static {    
  6.          System.loadLibrary("media_jni");    
  7.          native_init();    
  8.      }    
  9.      
  10.       ...    
  11.      
  12.      private native final void native_setup(Object mediaplayer_this);    
  13.           
  14.       ...    
  15.   }  

       可以看到上面的代码中,在MediaPlayer类中有一段static块包围起来的代码,其中System.loadLibrary("media_jni")就是告诉VM去加载libmedia_jni.so这个动态库,那么这个动态库什么时候被加载呢?因为static语句块的原因,所以在MediaPlayer第一次实例化的时候就会被加载了。这段代码中,我们还看到了一个函数native_init(),该函数被申明为native型,就是告诉VM该函数由native层来实现。

 

2,如何做到java层到native层的映射。

        事实上我想表达的意思是,如何完成java层的代码到native层代码的映射,例如上面的代码中有一个native函数native_init(),那么如何使这个函数映射到一个由C/C++(或者其他语言)实现的具体函数呢?

       当VM执行到System.loadLibrary()的时候就会去执行native libs中的JNI_OnLoad(JavaVM* vm, void* reserved)函数,因为JNI_OnLoad函数是从java层进入native层第一个调用的方法,所以可以在JNI_OnLoad函数中完成一些native层组件的初始化工作,同时更加重要的是,通常在JNI_jint JNI_OnLoad(JavaVM* vm, void* reserved)函数中会注册java层的native方法。下面看一段代码:

 

[java]   view plain copy
  1. jint JNI_OnLoad(JavaVM* vm, void* reserved)  
  2. {  
  3.     JNIEnv* env = NULL;  
  4.     jint result = -1;  
  5.     //判断一下JNI的版本   
  6.     if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {  
  7.         LOGE("ERROR: GetEnv failed\n");  
  8.         goto bail;  
  9.     }  
  10.     assert(env != NULL);  
  11.   
  12.     if (register_android_media_MediaPlayer(env) < 0) {  
  13.         LOGE("ERROR: MediaPlayer native registration failed\n");  
  14.         goto bail;  
  15.     }  
  16.   
  17.     if (register_android_media_MediaRecorder(env) < 0) {  
  18.         LOGE("ERROR: MediaRecorder native registration failed\n");  
  19.         goto bail;  
  20.     }  
  21.   
  22.     if (register<span style="font-size:16px;">_android_media_MediaScanner(env) < 0) {  
  23.         LOGE("ERROR: MediaScanner native registration failed\n");  
  24.         goto bail;  
  25.     }</span>  
  26.   
  27.     if (register_android_media_MediaMetadataRetriever(env) < 0) {  
  28.         LOGE("ERROR: MediaMetadataRetriever native registration failed\n");  
  29.         goto bail;  
  30.     }  
  31.   
  32.     if (register_android_media_AmrInputStream(env) < 0) {  
  33.         LOGE("ERROR: AmrInputStream native registration failed\n");  
  34.         goto bail;  
  35.     }  
  36.   
  37.     if (register_android_media_ResampleInputStream(env) < 0) {  
  38.         LOGE("ERROR: ResampleInputStream native registration failed\n");  
  39.         goto bail;  
  40.     }  
  41.   
  42.     if (register_android_media_MediaProfiles(env) < 0) {  
  43.         LOGE("ERROR: MediaProfiles native registration failed");  
  44.         goto bail;  
  45.     }  
  46.   
  47.     /* success -- return valid version number */  
  48.     result = JNI_VERSION_1_4;  
  49.   
  50. bail:  
  51.     return result;  
  52. }  

   

         上面这段代码的JNI_OnLoad(JavaVM* vm, void* reserved)函数实现与libmedia_jni.so库中。上面的代码中调用了一些形如register_android_media_MediaPlayer(env)的函数,这些函数的作用是注册native method。我们来看看函数register_android_media_MediaPlayer(env)的实现。

[java]   view plain copy
  1. // This function only registers the native methods  
  2. static int register_android_media_MediaPlayer(JNIEnv *env)  
  3. {  
  4.     return AndroidRuntime::registerNativeMethods(env,  
  5.                 "android/media/MediaPlayer", gMethods, NELEM(gMethods));  

 

[java]   view plain copy
  1. /* 
  2.  * Register native methods using JNI. 
  3.  */  
  4. /*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,  
  5.     const char* className, const JNINativeMethod* gMethods, int numMethods)  
  6. {  
  7.     return jniRegisterNativeMethods(env, className, gMethods, numMethods);  
  8. }  

          最终jniRegisterNativeMethods函数完成java标准的native函数的映射工作。下面我们来具体的看看上面这个函数中各个参数的意义。

a,JNIEnv* env

     JNI defines two key data structures, "JavaVM" and "JNIEnv". Both of these are essentiallypointers to pointers to function tables. (In the C++ version, they're classes with apointer to a function table and a member function for each JNI function that indirects throughthe table.) The JavaVM provides the "invocation interface" functions,which allow you to create and destroy a JavaVM. In theory you can have multiple JavaVMs per process,but Android only allows one.

The JNIEnv provides most of the JNI functions. Your native functions all receive a JNIEnv asthe first argument.

The JNIEnv is used for thread-local storage. For this reason, you cannot share a JNIEnv between threads.If a piece of code has no other way to get its JNIEnv, you should sharethe JavaVM, and useGetEnv to discover the thread's JNIEnv. (Assuming it has one; see AttachCurrentThread below.)

      b,char* className,这个没什么好说的,java空间中类名,其中包含了包名。

      c,JNINativeMethod* gMethods,传递进去的是一个JNINativeMethod类型的指针gMethodsgMethods指向一个JNINativeMethod数组,我们先看看JNINativeMethod这个结构体。

[java]   view plain copy
  1. typedef struct {  
  2.  const char* name; /*Java 中函数的名字*/  
  3.  const char* signature; /*描述了函数的参数和返回值*/  
  4.  void* fnPtr; /*函数指针,指向 C 函数*/  
  5.  } JNINativeMethod;  

再来看看gMethods数组

[java]   view plain copy
  1. static JNINativeMethod gMethods[] = {  
  2.     {"setDataSource",       "(Ljava/lang/String;)V",            (void *)android_media_MediaPlayer_setDataSource},  
  3.     。。。  
  4.     {"setAuxEffectSendLevel""(F)V",                           (void *)android_media_MediaPlayer_setAuxEffectSendLevel},  
  5.     {"attachAuxEffect",     "(I)V",                             (void *)android_media_MediaPlayer_attachAuxEffect},  
  6.     {"getOrganDBIndex",     "(II)I",                            (void *)android_media_MediaPlayer_getOrganDBIndex},  
  7. };  

       当VM载入libxxx_jni.so这个库时,就会呼叫JNI_OnLoad()函数。在JNI_OnLoad()中注册本地函数,继续调用到AndroidRuntime::registerNativeMethods(),该函数向VM(即AndroidRuntime)注册gMethods[]数组中包含的本地函数了。AndroidRuntime::registerNativeMethods()起到了以下两个作用:
         1,registerNativeMethods()函数使得java空间中的Native函数更加容易的找到对应的本地函数。(通过gMethods[]中的函数指针)
         2,可以在执行期间进行本地函数的替换。因为gMethods[]数组是一个<java中函数名字,本地函数指针>的对应表,所以可以在程序的执行过程中,多次呼叫registerNativeMethods()函数来更换本地函数的指针,提高程序的弹性。

 

 

函数签名:

       在JNINativeMethod的结构体中,有一个描述函数的参数和返回值的签名字段,它是java中对应函数的签名信息,由参数类型和返回值类型共同组成。这个函数签名信息的作用是什么呢?

       由于java支持函数重载,也就是说,可以定义同名但不同参数的函数。然而仅仅根据函数名是没法找到具体函数的。为了解决这个问题,JNI技术中就将参数类型和返回值类型的组合作为一个函数的签名信息,有了签名信息和函数名,就能顺利的找到java中的函数了。

        JNI规范定义的函数签名信息格式如下:

        (参数1类型标示参数2类型标示......参数n类型标示)返回值类型标示

           实际上这些字符是与函数的参数类型一一对应的。
        “()” 中的字符表示参数,后面的则代表返回值。例如”()V” 就表示 void Func();
         “(II)V” 表示 void Func(int, int);
          值得注意的一点是,当参数类型是引用数据类型时,

           其格式是“L包名;”其中包名中的“.”换成“/”

           所以在上面的例子中(Ljava/lang/String;Ljava/lang/String;)V 表示 void Func(String,String);

           如果 JAVA 函数位于一个嵌入类,则用$作为类名间的分隔符。

           例如 “(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z”

 

V

void

void

Z

jboolean

boolean

I

jint

int

J

jlong

long

D

jdouble

double

F

jfloat

float

B

jbyte

byte

C

jchar

char

S

jshort

short

 

[I

jintArray

int[]

[F

jfloatArray

float[]

[B

jbyteArray

byte[]

[C

jcharArray

char[]

[S

jshortArray

short[]

[D

jdoubleArray

double[]

[J

jlongArray

long[]

[Z

jbooleanArray

Boolean[]

数据类型转换:

       在java层调用native函数传递到JNI层的参数,JNI层会做一些特殊处理,我们知道java数据类型分为基本数据类型和引用数据类型两种,JNI层也是区别对待的。下表示出了java数据类型—>native类型的转换。

       其中在java数据类型中,除了java中基本数据类型和数组,Class,String,Throwable,其余所有的java对象的数据类型在JNI中用jobject表示。下面来看一段代码

[java]   view plain copy
  1. {"native_invoke",       "(Landroid/os/Parcel;Landroid/os/Parcel;)I",(void *)android_media_MediaPlayer_invoke},  
[java]   view plain copy
  1. //java层native_invoke函数有两个参数都是Parcel  
  2. private native final int native_invoke(Parcel request, Parcel reply);  
  3.   
  4. //JNI层对应的函数android_media_MediaPlayer_invoke的最后两个参数与native_invoke的参数对应  
  5. android_media_MediaPlayer_invoke(JNIEnv *env, jobject thiz,  
  6.                                  jobject java_request, jobject java_reply)  


        从上面的代码可以看出来,java中的数据类型Parcel在JNI层对应的数据类型为jobejct,在JNI层的对应函数中,我们看到相对java层的native函数来说,多了两个参数JNIEnv *env ,jobject this。第二个参数jobject代表了java层的MediaPlayer对象,它表示在哪个MediaPlayer对象上调用的native_invoke。如果java层是static函数,那么这个参数将是jclass,表示是在调用那个java Class的静态函数。

        还记得前面我们说过,java层和JNI层应该是可以互相交互,我们通过java层中的native函数可以进入到JNI层,那么JNI层的代码能不能操作java层中函数呢?当然可以,通过JNIEnv


JNIEnv再度解析

         先来看看两个函数原型

[java]   view plain copy
  1. <span style="color:#000000;">jfieldID GetFieldID(jclass clazz,const char *name,const char *sig );  
  2. jmethodID GetMethodID(jclass clazz,const char *name,const char *sig);</span>  

 

         结合前面的知识来看,JNIEnv是一个与线程相关的代表JNI环境的结构体。JNIEnv实际上提供了一些JNI系统函数。通过这些系统函数可以调用java层中的函数或者操作jobect。下面我看一段函数

[java]   view plain copy
  1. class MyMediaScannerClient : public MediaScannerClient  
  2. {  
  3. public:  
  4.     MyMediaScannerClient(JNIEnv *env, jobject client)  
  5.         :   mEnv(env),  
  6.             mClient(env->NewGlobalRef(client)),  
  7.             mScanFileMethodID(0),  
  8.             mHandleStringTagMethodID(0),  
  9.             mSetMimeTypeMethodID(0)  
  10.     {  
  11.         jclass mediaScannerClientInterface = env->FindClass("android/media/MediaScannerClient");  
  12.         if (mediaScannerClientInterface == NULL) {  
  13.             fprintf(stderr, "android/media/MediaScannerClient not found\n");  
  14.         }  
  15.         else {  
  16.             mScanFileMethodID = env->GetMethodID(mediaScannerClientInterface, "scanFile",  
  17.                                                      "(Ljava/lang/String;JJ)V");  
  18.             mHandleStringTagMethodID = env->GetMethodID(mediaScannerClientInterface, "handleStringTag",  
  19.                                                      "(Ljava/lang/String;Ljava/lang/String;)V");  
  20.             mSetMimeTypeMethodID = env->GetMethodID(mediaScannerClientInterface, "setMimeType",  
  21.                                                      "(Ljava/lang/String;)V");  
  22.             mAddNoMediaFolderMethodID = env->GetMethodID(mediaScannerClientInterface, "addNoMediaFolder",  
  23.                                                      "(Ljava/lang/String;)V");  
  24.         }  
  25.     }  
  26. ...  
  27.   
  28. // returns true if it succeeded, false if an exception occured in the Java code  
  29.     virtual bool scanFile(const char* path, long long lastModified, long long fileSize)  
  30.     {  
  31.         jstring pathStr;  
  32.         if ((pathStr = mEnv->NewStringUTF(path)) == NULL) return false;  
  33.   
  34.         mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize);  
  35.   
  36.         mEnv->DeleteLocalRef(pathStr);  
  37.         return (!mEnv->ExceptionCheck());  
  38.     }  

          可以看到上面的代码中,先找到java层中MediaScannerClinet类在JNI层中对应的jclass实例(通过FindClass)然后拿到MediaScannerclient类中所需要用到函数的函数函数id(通过GetMethodID)。接着通过JNIEnv调用CallXXXMethod函数并且把对应的jobject,jMethodID还有对应的参数传递进去,这样的通过CallXXXMethod就完成了JNI层向java层的调用。这里要注意一点的是这里JNI层中调用的方法实际上是java中对象的成员函数,如果要调用static函数可以使用CallStaticXXXMethod。这种机制有利于native层回调java代码完成相应操作。

               上面讲述了如下在JNI层中去调用java层的代码,那么理所当然的应该可以在JNI层中访问或者修改java层中某对象的成员变量的值。我们通过JNIEnv中的GetFieldID()函数来得到java中对象的某个域的id。看下面的具体代码

[java]   view plain copy
  1. int register_android_backup_BackupHelperDispatcher(JNIEnv* env)  
  2. {  
  3.     jclass clazz;  
  4.   
  5.     clazz = env->FindClass("java/io/FileDescriptor");  
  6.     LOG_FATAL_IF(clazz == NULL, "Unable to find class java.io.FileDescriptor");  
  7.     s_descriptorField = env->GetFieldID(clazz, "descriptor""I");  
  8.     LOG_FATAL_IF(s_descriptorField == NULL,  
  9.             "Unable to find descriptor field in java.io.FileDescriptor");  
  10.       
  11.     clazz = env->FindClass("android/app/backup/BackupHelperDispatcher$Header");  
  12.     LOG_FATAL_IF(clazz == NULL,  
  13.             "Unable to find class android.app.backup.BackupHelperDispatcher.Header");  
  14.     s_chunkSizeField = env->GetFieldID(clazz, "chunkSize""I");  
  15.     LOG_FATAL_IF(s_chunkSizeField == NULL,  
  16.             "Unable to find chunkSize field in android.app.backup.BackupHelperDispatcher.Header");  
  17.     s_keyPrefixField = env->GetFieldID(clazz, "keyPrefix""Ljava/lang/String;");  
  18.     LOG_FATAL_IF(s_keyPrefixField == NULL,  
  19.             "Unable to find keyPrefix field in android.app.backup.BackupHelperDispatcher.Header");  
  20.       
  21.     return AndroidRuntime::registerNativeMethods(env, "android/app/backup/BackupHelperDispatcher",  
  22.             g_methods, NELEM(g_methods));  
  23. }  

获得jfieldID之后呢,我们就可以在JNI层之间来访问和操作java层的field的值了,方法如下

 

[java]   view plain copy
  1. NativeType Get<type>Field(JNIEnv *env,jobject object,jfieldID fieldID)  
  2.   
  3. void Set<type>Field(JNIEnv *env,jobject object ,jfieldID fieldID,NativeType value)  

注意这里的NativeType值得是jobject,jboolean等等。

 

        现在我们看到有了JNIEnv,我们可以很轻松的操作jobject所代表的java层中的实际的对象了。

 

jstring介绍

         之所以要把jstring单独拿出来说正是由于它的特殊性。java中String类型也是一个引用类型,但是JNI中并没有用jobject来与之对应,JNI中单独创建了一个jstring类型来表示java中的String类型。显然java中的String不能和C++中的String等同起来,那么怎么操作jstring呢?方法很多下面看几个简单的方法

         1,调用JNIEnv的NewStringUTF将根据Native的一个UTF-8字符串得到一个jstring对象。只有这样才能让一个C++中String在JNI中使用。

         2,调用JNIEnv的GetStringChars函数(将得到一个Unicode字符串)和GetStringUTFChars函数(将得到一个UTF-8字符串),他们可以将java String对象转换诚本地字符串。下面我们来看段事例代码。

[java]   view plain copy
  1. virtual bool scanFile(const char* path, long long lastModified, long long fileSize)  
  2.     {  
  3.         jstring pathStr;  
  4.         //将char*数组字符串转换诚jstring类型  
  5.         if ((pathStr = mEnv->NewStringUTF(path)) == NULL) return false;  
  6.   
  7.         mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize);  
  8.   
  9.         mEnv->DeleteLocalRef(pathStr);  
  10.         return (!mEnv->ExceptionCheck());  
  11.     }  
  12.   
  13.       ....  
  14.       ....  
  15. while (env->CallBooleanMethod(iter, hasNext)) {  
  16.             jobject entry = env->CallObjectMethod(iter, next);  
  17.             jstring key = (jstring) env->CallObjectMethod(entry, getKey);  
  18.             jstring value = (jstring) env->CallObjectMethod(entry, getValue);  
  19.   
  20.             const char* keyStr = env->GetStringUTFChars(key, NULL);  
  21.             ...  
  22.             ...  

        GetStringUTFChars()函数将jstring类型转换成一个UTF-8本地字符串,另外如果代码中调用了上面的几个函数,则在做完相关工作后,要调用ReleaseStringChars函数或者ReleaseStringUTFChars函数来释放资源。看下面的代码

 

[java]   view plain copy
  1.             ...  
  2.             ...  
  3.             jstring key = (jstring) env->CallObjectMethod(entry, getKey);  
  4.             jstring value = (jstring) env->CallObjectMethod(entry, getValue);  
  5.   
  6.             const char* keyStr = env->GetStringUTFChars(key, NULL);  
  7.             if (!keyStr) {  // Out of memory  
  8.                 jniThrowException(  
  9.                         env, "java/lang/RuntimeException""Out of memory");  
  10.                 return;  
  11.             }  
  12.   
  13.             const char* valueStr = env->GetStringUTFChars(value, NULL);  
  14.             if (!valueStr) {  // Out of memory  
  15.                 jniThrowException(  
  16.                         env, "java/lang/RuntimeException""Out of memory");  
  17.                 return;  
  18.             }  
  19.   
  20.             headersVector.add(String8(keyStr), String8(valueStr));  
  21.   
  22.             env->DeleteLocalRef(entry);  
  23.             env->ReleaseStringUTFChars(key, keyStr);  
  24.             env->DeleteLocalRef(key);  
  25.             ...  
  26.             ...  

       可以看到GetStringUTFChars与下面的ReleaseStringUTFChars对应。

 

 

参考:  http://blog.csdn.net/mci2004/article/details/7211678

http://blog.csdn.net/mci2004/article/details/7219140

 


声明:本文转载自android开发网,http://www.android123.com.cn/androidkaifa/683.html
   有关Android JNI开发中比较强大和有用的功能就是从JNI层创建、构造Java的类或执行Java层的方法获取属性等操作。

    一、类的相关操作

    1. jclass FindClass(JNIEnv *env, const char *name);  查找类

    该函数可能做过Java开发的不会陌生,这个是JNI层的实现,需要注意的是第二个参数为const char*类型的,我们如果从Java从层传入unicode编码的jstring类型需要使用GetStringUTFChars函数转换成utf8的const char*,如果成功返回这个Java类的对象jclass,相关的异常可能有

     (1. ClassFormatError 类的数据格式无效
     (2. ClassCircularityError 该类或接口是自身的超类或超接口
     (3. NoClassDefFoundError 没有找到指定名称的类或接口  
     (4. OOM内存不足错误,即OutOfMemoryError  

     2. jclass GetSuperclass(JNIEnv *env, jclass clazz);  获取父类或者说超类   

     该函数的第二个参数为jclass类,我们调用时传入的是子类,否则返回将是NULL

     3. jboolean IsAssignableFrom(JNIEnv *env, jclass clazz1,jclass clazz2);  判断class1对象能否安全的强制转换为class2对象  

     如果可以将返回 JNI_TRUE,JNI_TRUE的定义值为1,否则返回JNI_FALSE即0 ,这里Android123详细说明下哪些情况可能返回真:

     (1  这两个类参数引用同一个 Java 类
     (2  第一个类是第二个类的子类
     (3  第二个类是第一个类的某个接口

     4.  jclass GetObjectClass(JNIEnv *env, jobject obj); 通过对象获取这个类

     该函数比较简单,唯一注意的是对象不能为NULL,否则获取的class肯定返回也为NULL。

    5.  jboolean IsInstanceOf(JNIEnv *env, jobject obj,jclass clazz); 判断对象是否为某个类的实例

   这个函数是JNI层的实现,相信大家都不陌生,Android开发网提醒大家需要注意的是返回值可能产生异议,就是如果传入的第二个参数为NULL对象,NULL对象可以强制转换为各种类,所以这种情况也将会返回JNI_TRUE,所以一定判断传入的对象是否为空。

   6. jboolean IsSameObject(JNIEnv *env, jobject ref1,jobject ref2);  判断两个对象是否引用同一个类

   需要注意的是如果两个对象均为空,返回的值也会是JNI_TRUE所以使用时判断对象为空。

   二、调用Java方法

   首先说下有关签名sig相关的比如 "Ljava/lang/String;"

   1. jmethodID GetMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig);  获取一个Java方法的ID

     这个函数将返回非静态类或接口实例方法的方法 ID。这个方法可以是某个clazz 的超类中定义,也可从clazz 继承,最后一个参数为签名,最后两个参数是const char*类型,是utf8类型。需要注意的是Android123提醒大家执行GetMethodID()函数将导致未初始化的类初始化,如果要获得构造函数的方法ID,使用 <init> 作为方法名,同时将 void (V) 作为返回类型,如果找不到指定的ID将返回NULL,同时异常可能有:

    (1  NoSuchMethodError 找不到指定的Java方法。
    (2  ExceptionInInitializerError 如果由于异常而导致类初始化程序失败
    (3  OutOfMemoryError 内存不足

  2 . NativeType CallXXXMethod (JNIEnv *env, jobject obj,jmethodID methodID, va_list args); 调用XXX类型的Java方法

     执行Java类中某个方法,需要注意的是这个里的java类是非静态的,由于Java的方法的类型比较多,所以该函数可能有以下几种形式,如CallObjectMethod,CallBooleanMethod,CallByteMethod,CallCharMethod,CallShortMethod,CallIntMethod,CallLongMethod,CallFloatMethod,CallDoubleMethod和CallVoidMethod,需要注意的是,该函数的第三个参数为通过GetMethodID函数获取的方法ID,最后一个参数为这个方法的参数表,最后的va_list宏可以通过搜索获取具体的使用方法,这里Android开发网不再赘述。

  3.NativeType CallNonvirtualXXXMethod (JNIEnv *env, jobject obj,jclass clazz, jmethodID methodID, jvalue *args);

    CallNonvirtualXXXMethod函数和上面的CallXXXMethod 不同之处是多了一个jclass参数,CallXXXMethod是根据对象来调用方法,而CallNonvirtualXXXMethod是根据类的实例调用,区别在这点。

   上面的三个均为非静态类的获取,执行调用,需要实例化这个类才可以执行,下面的为静态调用。

   4.  jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig);

   5.  NativeType CallStaticXXXMethod(JNIEnv *env, jclass clazz,jmethodID methodID, ...);    

   三、访问Java对象的域

    Java对象的域或者说字段、属性(Field) 类似方法的执行

    1. jfieldID GetFieldID(JNIEnv *env, jclass clazz,const char *name, const char *sig);  获取实例对象的域ID

    需要注意的是,非静态的实例化后的对象,可能产生的异常有

   (1 NoSuchFieldError  找不到指定的域
   (2 ExceptionInInitializerError 因为异常而导致类初始化失败
   (3 OutOfMemoryError内存不足。

    2. NativeType GetXXXField(JNIEnv *env, jobject obj,jfieldID fieldID);

    类似GetXXXMethod函数,可能有的类型有 GetObjectField,GetBooleanField,GetByteField,GetCharField,GetShortField,GetIntField,GetLongField,GetFloatField,GetDoubleField。

   3. void SetXXXField(JNIEnv *env, jobject obj, jfieldID fieldID,NativeType value);

    Java的域可以赋值的,可能有的类型有 SetObjectField,SetBooleanField,SetByteField,SetCharField,SetShortField,SetIntField,SetLongField,SetFloatField,SetDoubleField。

    上面3种情况均为非静态对象的域,对于不需要实例化对象的域,可以直接使用下面的。

   4. jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz,const char *name, const char *sig);

   5. NativeType GetStaticXXXField(JNIEnv *env, jclass clazz,jfieldID fieldID);

   6. void SetStaticXXXField(JNIEnv *env, jclass clazz,jfieldID fieldID, NativeType value);

  四、实例代码,Android123给网友准备了一个例子,帮助大家实战Android JNI开发,大家可以移植到Android NDK环境中执行,网友可以访问 Android JNI开发代码 (Android JNI实例代码(一))

  最后有关Android JNI最后的终极内容,Android开发网主要说明下JVM和JNI的全局引用相关内容,比如本地全局引用LocalGlobalRef,弱全局引用WeakGlobalRef,JNI中线程处理的高级方法比如AttachCurrentThread,以及JNI中的NIO的相关特性将在明天继续讲解,更多的有关Android平台NDK开发内容可以查看我们 Android NDK开发技巧系列文章。

from:https://thenewcircle.com/s/post/1292/jni_reference_example

Just when I thought that the world had finally accepted "pure" Java, along came Android, with its nifty Dalvik VM and a lot of dependencies onJNI, adding native code back into the mix.

For those that don't know, Java Native Interface (JNI) enables code written in Java to access (bind to) code written in C/C++ (and vice-versa) thereby allowing developers to gain access to low-level OS APIs, reuse legacy code, and possibly even boost the execution performance.

Since we talk quite a bit about JNI in our Android Internals training course, I thought I should share some of our material focusing on this topic. I hope you find it useful.

JNI Overview

In this module, we’ll explore the following topics:

  • JNI Components

  • JNI By Example

  • Native Method Arguments

  • Primitive Type Mapping

  • Reference Type Mapping

  • Global and Local References

  • Using Strings

  • Arrays

  • Reflection

  • Registering Native Methods On Load

  • Exceptions

JNI: What it is and why you’d care

JNI is an interface that allows Java to interact with code written in another language

Motivation for JNI:

  • Access low-level OS-specific features (e.g. ioctl, poll, etc.)
  • Reuse existing/legacy code written in C/C++ from Java
  • Improve the performance by writing CPU-bound code natively - but modern just-in-time (JIT) compilers make this a moot point
NoteJNI code is not portable!
Note

JNI can also be used to invoke Java code from within natively-written applications - such as those written in C/C++.

In fact, the java command-line utility is an example of one such application, that launches Java code in a Java Virtual Machine.

JNI Components

  • javah - JDK tool that builds C-style header files from a given Java class that includesnative methods

    • Adapts Java method signatures to native function prototypes

  • jni.h - C/C+ header file included with the JDK that maps Java types to their native counterparts

    • javah automatically includes this file in the application header files

JNI By Example

  1. We start by creating a Java class with one or more native methods

    src/com/marakana/jniexamples/Hello.java:
    package com.marakana.jniexamples;
    
    public class Hello {
    
        public static native void sayHi(String who, int times); // 1
    
        static {
            System.loadLibrary("hello"); // 2
        }
    
        public static void main(String[] args) {
            sayHi(args[0], Integer.parseInt(args[1])); // 3
        }
    }
    1The method sayHi(String, int) will be implemented in C/C+ in separate files, which will be compiled into a shared library.
    2Load the shared library by its logical name. The actual name is system-dependent:libhello.so (on Linux/Unix),hello.dll (on Windows), andlibhello.jnilib (Mac OSX).
    3Here we call our native method as a regular Java method.
  2. Compile the Java code

    $ mkdir -p bin
    $ javac -d bin/ src/com/marakana/jniexamples/Hello.java
  3. Using the javah tool, we generate the C header file from the compiledcom.marakana.jniexamples.Hello class:

    $ mkdir -p jni
    $ javah -jni -classpath bin -d jni com.marakana.jniexamples.Hello
  4. Observe the generated C header file:

    jni/com_marakana_jniexamples_Hello.h:
    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class com_marakana_jniexamples_Hello */
    
    #ifndef _Included_com_marakana_jniexamples_Hello
    #define _Included_com_marakana_jniexamples_Hello
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     com_marakana_jniexamples_Hello
     * Method:    sayHi
     * Signature: (Ljava/lang/String;I)V
     */
    JNIEXPORT void JNICALL Java_com_marakana_jniexamples_Hello_sayHi
      (JNIEnv *, jclass, jstring, jint);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    NoteMethod names resolve to C functions based on a pre-defined naming strategy:the prefixJava_, followed by a mangled fully-qualified class name, followed by an underscore ("_") separator, followed by a mangled method name. For overloaded native methods, two underscores ("__") followed by the mangled argument signature.
  5. Provide the C implementation:

    jni/com_marakana_jniexamples_Hello.c:
    #include "com_marakana_jniexamples_Hello.h"
    
    JNIEXPORT void JNICALL Java_com_marakana_jniexamples_Hello_sayHi
      (JNIEnv *env, jclass clazz, jstring who, jint times) {
      const char *name = (*env)->GetStringUTFChars(env, who, NULL);
      if (name != NULL) {
        jint i;
        for (i = 0; i < times; i++) {
          printf("Hello %s\n", name);
        }
        (*env)->ReleaseStringUTFChars(env, who, name);
      }
    }
    NoteMost of the time, we cannot just use Java data types directly in C. For example, we have to convertjava.lang.String tochar * before we can effectively use it in C.
    NoteThis code assumes: #define NULL ((void *) 0)
  6. Compile the shared library

    $ mkdir -p libs
    $ gcc -o libs/libhello.jnilib -lc -shared \
        -I/System/Library/Frameworks/JavaVM.framework/Headers \
        jni/com_marakana_jniexamples_Hello.c
    $ file libs/libhello.jnilib
    libs/libhello.jnilib: Mach-O 64-bit dynamically linked shared library x86_64
    NoteOn Unix/Linux, compile as:
    gcc -o libs/libhello.so -lc -shared -fPIC -I$JAVA_HOME/include jni/com_marakana_jniexamples_Hello.c
  7. Run our code

    $ java -Djava.library.path=libs -classpath bin com.marakana.jniexamples.Hello Student 5
    Hello Student
    Hello Student
    Hello Student
    Hello Student
    Hello Student
    NoteInstead of specifying -Djava.library.path=libs, we could have preceded ourjava command withexport LD_LIBRARY_PATH=libs.
    TipCommon mistakes resulting in java.lang.UnsatisfiedLinkError usually come from incorrect naming of the shared library (O/S-dependent), the library not being in the search path, or wrong library being loaded by Java code.

Native Method Arguments

  • A C/C++ implementation of a native Java method accepts the following function arguments:

    • JNIEnv *env, an interface pointer (pointer to a pointer) to afunction table, where each entry in the table is a pointer to a function that enables Java-to-C/C++ integration (e.g. type conversion)

    • The second argument varies depending on whether the native method is a static method or an instance (i.e. non-static) method:

      • In case of instance methods, the second argument is a jobject obj, which is a reference to the object on which the method is invoked

      • In case of static methods, the second is a jclass clazz, which is a reference to the class in which the method is defined

    • The remaining arguments correspond to regular Java method arguments (subject to the mapping described below)

  • The native function can pass its result back to Java via the return value (or returnvoid)

Primitive Type Mapping

  • Java primitives have well-known size and are signed by default

  • C/C++ primitives vary in size, depending on the platform (e.g. the size of thelong depends on the size of the word)

  • To provide inter-operability with C/C++, jni.h defines the following mappings:

    Java Language TypeNative TypeDescriptiontypedef (C99)typedef (otherwise)

    boolean

    jboolean

    unsigned 8 bits

    uint8_t

    unsigned char

    byte

    jbyte

    signed 8 bits

    int8_t

    signed char

    char

    jchar

    unsigned 16 bits

    uint16_t

    unsigned short

    short

    jshort

    signed 16 bits

    int16_t

    short

    int

    jint

    signed 32 bits

    int32_t

    int

    long

    jlong

    signed 64 bits

    int64_t

    long long

    float

    jfloat

    32-bit IEEE 754

    float

    float

    double

    jdouble

    64-bit IEEE 754

    double

    double

    void

    void

    N/A

    N/A

    N/A

    N/A

    jsize

    used to describe cardinal indices and sizes

    jint

    jint

  • Because booleans are treated as unsigned bytes, the following definitions are also useful

    Java Boolean ValueNative Boolean TypeDefinition

    false

    JNI_FALSE

    #define JNI_FALSE 0

    true

    JNI_TRUE

    #define JNI_TRUE 1

  • Primitive data types are always copied between Java and native code

Reference Type Mapping

  • The JNI includes a number of pre-defined reference types (passed as opaque references in C) that correspond to different kinds of Java object types:

    Java Language TypeNative Typetypedef in C

    java.lang.Object

    jobject

    void*

    java.lang.Class

    jclass

    jobject

    java.lang.Throwable

    jthrowable

    jobject

    java.lang.String

    jstring

    jobject

    java.lang.ref.WeakReference

    jweak

    jobject

    N/A

    jarray

    jobject

    java.lang.Object[]

    jobjectArray

    jarray

    boolean[]

    jbooleanArray

    jarray

    byte[]

    jbyteArray

    jarray

    char[]

    jcharArray

    jarray

    short[]

    jshortArray

    jarray

    int[]

    jintArray

    jarray

    long[]

    jlongArray

    jarray

    float[]

    jfloatArray

    jarray

    double[]

    jdoubleArray

    jarray

    Note
    A note about reference types in C++

    These reference types in C++ are defined as proper classes:

    class _jobject {};
    class _jclass : public _jobject {};
    class _jstring : public _jobject {};
    class _jarray : public _jobject {};
    class _jobjectArray : public _jarray {};
    class _jbooleanArray : public _jarray {};
    class _jbyteArray : public _jarray {};
    class _jcharArray : public _jarray {};
    class _jshortArray : public _jarray {};
    class _jintArray : public _jarray {};
    class _jlongArray : public _jarray {};
    class _jfloatArray : public _jarray {};
    class _jdoubleArray : public _jarray {};
    class _jthrowable : public _jobject {};
    
    typedef _jobject*       jobject;
    typedef _jclass*        jclass;
    typedef _jstring*       jstring;
    typedef _jarray*        jarray;
    typedef _jobjectArray*  jobjectArray;
    typedef _jbooleanArray* jbooleanArray;
    typedef _jbyteArray*    jbyteArray;
    typedef _jcharArray*    jcharArray;
    typedef _jshortArray*   jshortArray;
    typedef _jintArray*     jintArray;
    typedef _jlongArray*    jlongArray;
    typedef _jfloatArray*   jfloatArray;
    typedef _jdoubleArray*  jdoubleArray;
    typedef _jthrowable*    jthrowable;
    typedef _jobject*       jweak;
    Tip
    A tip about NULL in Android NDK

    Android’s native development kit (NDK) does not define NULL in its jni.h, so the following definition can be useful when working with pointers and reference types:

    #define NULL ((void *) 0)
    WarningOpaque references are C pointer types that refer to internal data structures in the JVM. It is an error to try to dereference opaque references and try to use them directly.

Global and Local References

  • Unlike primitives, arbitrary Java objects are always passed by reference

  • The VM must keep track of all objects that have been passed to the native code, so that these objects are not freed by the GC (i.e. these references are considered objectroots while in use)

    • Consequently, the native code must have a way to tell the VM that it no longer needs references to objects

    • Additionally, the GC must be able to relocate an object referred to by native code (in the case of a copying GC), which means that these references must not bedereferenced by the native code

    • We will access the fields/methods on these references via JNI accessor functions

  • All objects passed to native code and returned from JNI functions are considered local references

    • Automatically "freed" after the native function call returns

      • The VM creates a table of references before every function call, and frees it when the call completes

      • Consequently, the native function calls are more expensive than regular method calls

    • Also possible to explicitly free using:

      void DeleteLocalRef(JNIEnv *env, jobject localRef);
      NoteIn practice, we only really want to use DeleteLocalRef(JNIEnv *, jobject) if we know we are not going to need the reference in the rest of the function body, say before we start executing code that may take a long time. This allows the referenced memory to be freed by GC, assuming nobody else is using it.
    • Only valid in the thread in which they are created

    • If we end up using more than 16 local references (default) within a native function scope, we can ask the VM for more:

      jint EnsureLocalCapacity(JNIEnv *env, jint capacity);
      jint PushLocalFrame(JNIEnv *env, jint capacity);
      jobject PopLocalFrame(JNIEnv *env, jobject result);
  • To hold on to an object beyond a single native function call, we can convert a local reference to aglobal reference

    jobject NewGlobalRef(JNIEnv *env, jobject obj);
    NoteNow we could save the result of NewGlobalRef(JNIEnv *, jobect) to a global/static variable and use it later, in another funciton call.
    • Becomes our responsibility to explicitly delete the reference when no longer in use:

      void DeleteGlobalRef(JNIEnv *env, jobject globalRef);
    • Also supported are the weak global references

      jweak NewWeakGlobalRef(JNIEnv *env, jobject obj);
      void DeleteWeakGlobalRef(JNIEnv *env, jweak obj);
  • JNI accessor functions do not make a distinction between the different reference types

Using Strings

  • In C, a string is a pointer to a \0-terminated character array where each character is one byte

  • In Java, an instance of java.lang.String is an immutable object which wraps a character array, which itself is an object (i.e. it knows its length so it is not\0-terminated), and each character is represented in UTF16 astwo bytes

  • String Operations are provided byJNIEnv:

    /* On Unicode (UTF-16) Characters */
    jstring NewString(JNIEnv *env, const jchar *unicodeChars, jsize len);
    jsize GetStringLength(JNIEnv *env, jstring string);
    const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy);
    void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars);
    void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize len, jchar *buf);
    
    /* On (modified) UTF-8 Characters */
    jstring NewStringUTF(JNIEnv *env, const char *bytes);
    jsize GetStringUTFLength(JNIEnv *env, jstring string);
    const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);
    void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf);
    void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize len, char *buf);
    Note
    A note about GetString[UTF]Chars(…) functions

    The pointer resulting from GetString[UTF]Chars(…) is valid untilReleaseString[UTF]Chars(…) is called.

    If isCopy is not NULL, then*isCopy is set toJNI_TRUE if a copy is made. When*isCopy == JNI_FALSE, the returned string is adirect pointer to the characters in the originaljava.lang.String instance, which is then pinned in memory. The native code must ensurenot to modify the contents of the returned string, otherwise, it would be modifying the private data of theimmutablejava.lang.String object!

    Regardless of isCopy, we have to call ReleaseString[UTF]Chars(…) when we are done using the character array, either to free the memory (when*isCopy == JNI_TRUE) or to un-pin the original string in memory (when*isCopy == JNI_FALSE).

    Note
    A note about modified UTF-8 strings:

    JNI’s UTF string functions (that work with char *) return/assume\0-terminated character arrays that are encoded as UTF-8 character sequences, except that if the stringcontains a \u0000 character, it is represented by a pair of bytes0xc0 0x80 (1100000010000000) instead of0x00. When working with regular ASCII characters, each character is represented by exactly one byte.

  • Examples:

    • What NOT to do:

      #include <stdio.h>
      #include "com_marakana_jniexamples_HelloName.h"
      
      JNIEXPORT void JNICALL Java_com_marakana_jniexamples_HelloName_sayHelloName(JNIEnv *env, jclass clazz, jstring name) {
          printf("Hello %s", name); /* 1  */
      }
      1This example would not work (would likely crash the VM) since the jstring type represents strings in the Java virtual machine. This is different from the C string type (char *).
    • Convert Java string to C string:

      #include <stdio.h>
      #include "com_marakana_jniexamples_HelloName.h"
      
      JNIEXPORT void JNICALL Java_com_marakana_jniexamples_HelloName_sayHelloName(JNIEnv *env, jclass clazz, jstring name){
          const char *cName = (*env)->GetStringUTFChars(env, name, NULL); /* 1 */
          if (cName == NULL) {
              return; /* OutOfMemoryError already thrown */
          } else {
              printf("Hello %s\n", cName);
              (*env)->ReleaseStringUTFChars(env, name, cName); /* 2 */
          }
      }
      1This returns a pointer to an array of bytes representing the string in modified UTF-8 encoding; or NULL if we ran out of memory, in which casejava.lang.OutOfMemoryError would also be thrown (upon returning back to the Java runtime).
      2Calling ReleaseStringUTFChars indicates that the native method no longer needs the UTF-8 string returned byGetStringUTFChars, thus the memory taken by the UTF-8 string can be freed. Failure to do this would result in a memory leak, which could ultimately lead to memory exhaustion.
    • Convert C string to Java string:

      #include <stdio.h>
      #include "com_marakana_jniexamples_GetName.h"
      
      JNIEXPORT jstring JNICALL Java_com_marakana_jniexamples_ReturnName_GetName(JNIEnv *env, jclass class) {
          char buf[20];
          fgets(buf, sizeof(buf), stdin);
          return (*env)->NewStringUTF(env, buf);
      }
    • Print each character of a Java string

      #include <stdio.h>
      #include "com_marakana_jniexamples_HelloName.h"
      
      JNIEXPORT void JNICALL Java_com_marakana_jniexamples_HelloName_sayHelloName(JNIEnv *env, jclass clazz, jstring name) {
          char buf[4];
          jint i;
          jsize len = (*env)->GetStringUTFLength(env, name);
          fputs("Hello ", stdout);
          for (i = 0; i < len; i++) {
             (*env)->GetStringUTFRegion(env, name, i, 1, buf);
             putc(buf[0], stdout); /* assumes ASCII */
          }
          putc('\n', stdout);
      }

Arrays

  • As with Strings, Java arrays are different than C arrays; the former are objects (whichknow theirlength) whereas the latter are just pointers to memory addresses

    • So, int[] in Java is not the same as jint[] in C/C++

    • Instead, int[] in Java, becomes jintArray in C/C++

  • JNI provides functions for

    • Creating arrays:

      jobjectArray  NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement);
      jbooleanArray NewBooleanArray(JNIEnv *env, jsize length);
      jbyteArray    NewByteArray(JNIEnv *env, jsize length);
      jcharArray    NewCharArray(JNIEnv *env, jsize length);
      jshortArray   NewShortArray(JNIEnv *env, jsize length);
      jintArray     NewIntArray(JNIEnv *env, jsize length);
      jlongArray    NewLongArray(JNIEnv *env, jsize length);
      jfloatArray   NewFloatArray(JNIEnv *env, jsize length);
      jdoubleArray  NewDoubleArray(JNIEnv *env, jsize length);
    • Getting the length of an array

      jsize GetArrayLength(JNIEnv *env, jarray array);
    • Getting/Setting individual elements of object arrays

      jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index);
      void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value);
    • Converting Java arrays of primitives to C/C++ arrays:

      jboolean* GetBooleanArrayElements(JNIEnv *env, jbooleanArray array, jboolean *isCopy);
      jbyte*    GetByteArrayElements(JNIEnv *env, jbyteArray array, jboolean *isCopy);
      jchar*    GetCharArrayElements(JNIEnv *env, jcharArray array, jboolean *isCopy);
      jshort*   GetShortArrayElements(JNIEnv *env, jshortArray array, jboolean *isCopy);
      jint*     GetIntArrayElements(JNIEnv *env, jintArray array, jboolean *isCopy);
      jlong*    GetLongArrayElements(JNIEnv *env, jlongArray array, jboolean *isCopy);
      jfloat*   GetFloatArrayElements(JNIEnv *env, jfloatArray array, jboolean *isCopy);
      jdouble*  GetDoubleArrayElements(JNIEnv *env, jdoubleArray array, jboolean *isCopy);
      
      void      ReleaseBooleanArrayElements(JNIEnv *env, jbooleanArray array, jboolean *elems, jint mode);
      void      ReleaseByteArrayElements(JNIEnv *env, jbyteArray array, jbyte *elems, jint mode);
      void      ReleaseCharArrayElements(JNIEnv *env, jcharArray array, jchar *elems, jint mode);
      void      ReleaseShortArrayElements(JNIEnv *env, jshortArray array, jshort *elems, jint mode);
      void      ReleaseIntArrayElements(JNIEnv *env, jintArray array, jint *elems, jint mode);
      void      ReleaseLongArrayElements(JNIEnv *env, jlongArray array, jlong *elems, jint mode);
      void      ReleaseFloatArrayElements(JNIEnv *env, jfloatArray array, jfloat *elems, jint mode);
      void      ReleaseDoubleArrayElements(JNIEnv *env, jdoubleArray array, jdouble *elems, jint mode);
  • Copying Java primitive arrays to and from C/C++ arrays:

    void GetBooleanArrayRegion(JNIEnv *env, jbooleanArray array, jsize start, jsize len, jboolean *buf);
    void GetByteArrayRegion(JNIEnv *env, jbyteArray array, jsize start, jsize len, jbyte *buf);
    void GetCharArrayRegion(JNIEnv *env, jcharArray array, jsize start, jsize len, jchar *buf);
    void GetShortArrayRegion(JNIEnv *env, jshortArray array, jsize start, jsize len, jshort *buf);
    void GetIntArrayRegion(JNIEnv *env, jintArray array, jsize start, jsize len, jint *buf);
    void GetLongArrayRegion(JNIEnv *env, jlongArray array, jsize start, jsize len, jlong *buf);
    void GetFloatArrayRegion(JNIEnv *env, jfloatArray array, jsize start, jsize len, jfloat *buf);
    void GetDoubleArrayRegion(JNIEnv *env, jdoubleArray array, jsize start, jsize len, jdouble *buf);
    
    void SetBooleanArrayRegion(JNIEnv *env, jbooleanArray array, jsize start, jsize len, jboolean *buf);
    void SetByteArrayRegion(JNIEnv *env, jbyteArray array, jsize start, jsize len, jbyte *buf);
    void SetCharArrayRegion(JNIEnv *env, jcharArray array, jsize start, jsize len, jchar *buf);
    void SetShortArrayRegion(JNIEnv *env, jshortArray array, jsize start, jsize len, jshort *buf);
    void SetIntArrayRegion(JNIEnv *env, jintArray array, jsize start, jsize len, jint *buf);
    void SetLongArrayRegion(JNIEnv *env, jlongArray array, jsize start, jsize len, jlong *buf);
    void SetFloatArrayRegion(JNIEnv *env, jfloatArray array, jsize start, jsize len, jfloat *buf);
    void SetDoubleArrayRegion(JNIEnv *env, jdoubleArray array, jsize start, jsize len, jdouble *buf);
  • For example, to sum the contents of a Java int array in C, we could:

    JNIEXPORT jint JNICALL Java_com_marakana_jniexamples_Math_sum(JNIEnv *env, jclass clazz, jintArray array) {
        jint *cArray = (*env)->GetIntArrayElements(env, array, NULL);
        if (cArray == NULL) {
            return 0;
        } else {
            jini len = (*env)->GetArrayLength(env, array);
            jint i;
            jint result = 0;
            for (i = 0; i < len; i++) {
                result += cArray[i];
            }
            (*env)->ReleaseIntArrayElements(env, array, cArray, JNI_ABORT);
            return result;
        }
    }
  • Alternatively, we could also extract the individual elements:

    JNIEXPORT jint JNICALL Java_com_marakana_jniexamples_Math_sum(JNIEnv *env, jclass clazz, jintArray array) {
        jint buf[1];
        jini len = (*env)->GetArrayLength(env, array);
        jint i;
        jint result = 0;
        for (i = 0; i < len; i++) {
            (*env)->GetIntArrayRegion(env, array, i, 1, buf);
            result += buf[0];
        }
        return result;
    }
  • To do the opposite, copy a C array to Java array, we would do:

    JNIEXPORT jintArray JNICALL Java_com_marakana_jniexamples_Foo_getData(JNIEnv *env, jclass class) {
        jint cArray[10];
        jsize len = sizeof(cArray);
        jintArray jArray = (*env)->NewIntArray(env, len);
        if (jArray != NULL) {
            jint i;
            /* "get" the data */
            for (i = 0; i < len; i++) {
                cArray[i] = i;
            }
            (*env)->SetIntArrayRegion(env, jArray, 0, len, cArray);
        }
        return jArray;
    }
  • To avoid any chance of memory copying, we could also use direct memory buffers (java.nio.ByteBuffer):

    jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity);
    void* GetDirectBufferAddress(JNIEnv* env, jobject buf);
    jlong GetDirectBufferCapacity(JNIEnv* env, jobject buf);
    • For example

      In Java
      public class Foo {
          public static void main(String[] args) {ByteBuffer buf = ByteBuffer.allocateDirect(1024);
              // populate buf
              processData(buf);}
      
          public native static void processData(ByteBuffer buf);
      }
      In C
      JNIEXPORT void JNICALL Java_com_marakana_jniexamples_Foo_processData(JNIEnv *env, jclass clazz, jobject buf) {
          char *cBuf = (*env)->GetDirectBufferAddress(env, buf);
          /* process cBuf from 0 to (*env)->GetDirectBufferCapacity(env, buf) */
      }
Note
A note about memory

The pointer resulting from GetTypeArrayElements(…) is valid untilReleaseTypeArrayElements(…) is called (unlessmode == JNI_COMMIT).

If isCopy is not NULL, then*isCopy is set toJNI_TRUE if a copy is made. When*isCopy == JNI_FALSE, the returned array is adirect pointer to the elements of the Java array, which is then pinned in memory.

Regardless of isCopy, we have to call ReleaseTypeArrayElements(…, int mode) when we are done using the native array, either to un-pin the Java array in memory when*isCopy == JNI_FALSE, or, when*isCopy == JNI_TRUE, to:

  • copy the native array over the Java array and free it (when mode == 0)

  • copy the native array over the Java array but not free it (whenmode == JNI_COMMIT)
    This option assumes that we will call ReleaseTypeArrayElements(…, JNI_ABORT) at some later point

  • leave the Java array intact and free the native array (when mode == JNI_ABORT)

JNI also supports a critical version of these functions:

void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy);
void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode);

The function GetPrimitiveArrayCritical(…) is similar toGetTypeArrayElements(…), except that the VM is more likely to return the pointer to the primitive array (i.e.*isCopy == JNI_FALSE is more likely). However, this comes with some caveats. The native code betweenGetPrimitiveArrayCritical(…) andReleasePrimitiveArrayCritical(…) must not call other JNI functions, or make any system calls that may cause the current thread to block and wait for another Java thread. This is because the VM may temporarily suspend the garbage collection - in case it does not support memory pinning.

Reflection

  • JNI supports getting a reference to a Java class in native code:

    jclass FindClass(JNIEnv *env, const char *name);
    • The name argument is a fully-qualified classname (with/ as the package separators)

      /* load the java.lang.String class */
      (*env)->FindClass(env, "java/lang/String");
    • For arrays, we use [Lclassname; format

      /* load the java.lang.String[] class */
      (*env)->FindClass(env, "[Ljava/lang/String;");
  • Given a reference to a class, we can find out its super class, as well as whether it is assignable from another class:

    jclass GetSuperclass(JNIEnv *env, jclass clazz);
    jboolean IsAssignableFrom(JNIEnv *env, jclass clazz1, jclass clazz2);
  • Given a reference to an object, we can find out its class, as well as whether it is an instance of another class:

    jclass GetObjectClass(JNIEnv *env, jobject obj);
    jboolean IsInstanceOf(JNIEnv *env, jobject obj, jclass clazz);
  • Once we have a class, JNI offers functions to lookup, get/set fields and invoke methods of that class - based on the following field and method signatures:

    Type SignatureJava Type

    Z

    boolean

    B

    byte

    C

    char

    S

    short

    I

    int

    J

    long

    F

    float

    D

    double

    Lfully-qualified-class;

    fully-qualified-class

    [type

    type[]

    (arg-types)ret-type

    method type

  • For fields, JNI offers functions to:

    • Lookup a field ID based on its name/signature:

      jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
      jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
    • Get/set the value of a field based on the field ID:

      jobject     (*GetStaticObjectField)(JNIEnv*, jclass, jfieldID);
      jboolean    (*GetStaticBooleanField)(JNIEnv*, jclass, jfieldID);
      jbyte       (*GetStaticByteField)(JNIEnv*, jclass, jfieldID);
      jchar       (*GetStaticCharField)(JNIEnv*, jclass, jfieldID);
      jshort      (*GetStaticShortField)(JNIEnv*, jclass, jfieldID);
      jint        (*GetStaticIntField)(JNIEnv*, jclass, jfieldID);
      jlong       (*GetStaticLongField)(JNIEnv*, jclass, jfieldID);
      jfloat      (*GetStaticFloatField)(JNIEnv*, jclass, jfieldID);
      jdouble     (*GetStaticDoubleField)(JNIEnv*, jclass, jfieldID);
      
      void        (*SetStaticObjectField)(JNIEnv*, jclass, jfieldID, jobject);
      void        (*SetStaticBooleanField)(JNIEnv*, jclass, jfieldID, jboolean);
      void        (*SetStaticByteField)(JNIEnv*, jclass, jfieldID, jbyte);
      void        (*SetStaticCharField)(JNIEnv*, jclass, jfieldID, jchar);
      void        (*SetStaticShortField)(JNIEnv*, jclass, jfieldID, jshort);
      void        (*SetStaticIntField)(JNIEnv*, jclass, jfieldID, jint);
      void        (*SetStaticLongField)(JNIEnv*, jclass, jfieldID, jlong);
      void        (*SetStaticFloatField)(JNIEnv*, jclass, jfieldID, jfloat);
      void        (*SetStaticDoubleField)(JNIEnv*, jclass, jfieldID, jdouble);
      
      jobject     (*GetObjectField)(JNIEnv*, jobject, jfieldID);
      jboolean    (*GetBooleanField)(JNIEnv*, jobject, jfieldID);
      jbyte       (*GetByteField)(JNIEnv*, jobject, jfieldID);
      jchar       (*GetCharField)(JNIEnv*, jobject, jfieldID);
      jshort      (*GetShortField)(JNIEnv*, jobject, jfieldID);
      jint        (*GetIntField)(JNIEnv*, jobject, jfieldID);
      jlong       (*GetLongField)(JNIEnv*, jobject, jfieldID);
      jfloat      (*GetFloatField)(JNIEnv*, jobject, jfieldID);
      jdouble     (*GetDoubleField)(JNIEnv*, jobject, jfieldID);
      
      void        (*SetObjectField)(JNIEnv*, jobject, jfieldID, jobject);
      void        (*SetBooleanField)(JNIEnv*, jobject, jfieldID, jboolean);
      void        (*SetByteField)(JNIEnv*, jobject, jfieldID, jbyte);
      void        (*SetCharField)(JNIEnv*, jobject, jfieldID, jchar);
      void        (*SetShortField)(JNIEnv*, jobject, jfieldID, jshort);
      void        (*SetIntField)(JNIEnv*, jobject, jfieldID, jint);
      void        (*SetLongField)(JNIEnv*, jobject, jfieldID, jlong);
      void        (*SetFloatField)(JNIEnv*, jobject, jfieldID, jfloat);
      void        (*SetDoubleField)(JNIEnv*, jobject, jfieldID, jdouble);
    • For example:

      In Java
      public class Foo {
          private String bar;public native void processBar();
      }
      In C
      JNIEXPORT void JNICALL Java_com_marakana_jniexamples_Foo_processBar(JNIEnv *env, jobject object) {
          /* Same as object.getClass() */
          jclass clazz = (*env)->GetObjectClass(env, object);
          if (clazz != NULL) { /* cannot be null in this case */
              /* Same as clazz.getField("bar") */
              jfieldID field = (*env)->GetFieldID(env, clazz, "bar", "Ljava/lang/String;");
              if (field != NULL) { /* make sure we got the field */
                  /* Same as field.get(object) */
                  jstring jString = (*env)->GetObjectField(env, object, field);
                  if (jString != NULL) {
                      /* Convert the value to a C (UTF-8) string */
                      const char *cString = (*env)->GetStringUTFChars(env, jString, NULL);
                      if (cString == NULL) {
                          return; /* Out of memory */
                      }
                      printf("Value of \"bar\" before the change: \"%s\"\n", cString);
                      (*env)->ReleaseStringUTFChars(env, jString, cString);
                  }
                  /* Create a new String */
                  jString = (*env)->NewStringUTF(env, "Bar2");
                  if (jString != NULL) { /* make sure we are not out of memory */
                      /* Same as field.set(object, jString) */
                      (*env)->SetObjectField(env, object, field, jString);
                  }
              }
          }
      }
  • For methods, JNI offers functions to:

    • Lookup a method ID based on its name and signature:

      jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
      jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
    • Invoke a method based on the method ID:

      jobject     (*CallStaticObjectMethod)(JNIEnv*, jclass, jmethodID, ...);
      jboolean    (*CallStaticBooleanMethod)(JNIEnv*, jclass, jmethodID, ...);
      jbyte       (*CallStaticByteMethod)(JNIEnv*, jclass, jmethodID, ...);
      jchar       (*CallStaticCharMethod)(JNIEnv*, jclass, jmethodID, ...);
      jshort      (*CallStaticShortMethod)(JNIEnv*, jclass, jmethodID, ...);
      jint        (*CallStaticIntMethod)(JNIEnv*, jclass, jmethodID, ...);
      jlong       (*CallStaticLongMethod)(JNIEnv*, jclass, jmethodID, ...);
      jfloat      (*CallStaticFloatMethod)(JNIEnv*, jclass, jmethodID, ...);
      jdouble     (*CallStaticDoubleMethod)(JNIEnv*, jclass, jmethodID, ...);
      void        (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...);
      
      jobject     (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
      jboolean    (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);
      jbyte       (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);
      jchar       (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);
      jshort      (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);
      jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
      jlong       (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);
      jfloat      (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...);
      jdouble     (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...);
      void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
      Note

      JNI also offers Call…(…) functions that take args in a form ofva_list as well as an array ofjvalue-s:

      <Type>     (*Call<Type>MethodV)(JNIEnv*, jobject, jmethodID, va_list);
      <Type>     (*CallStatic<Type>MethodV)(JNIEnv*, jobject, jmethodID, va_list);
      <Type>     (*Call<Type>MethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
      <Type>     (*CallStatic<Type>MethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
    • For example:

      In Java
      public class Foo {
          private String bar;
          public void setBar(String bar) {
              this.bar = bar;
          }public native void processBar();
      }
      In C
      JNIEXPORT void JNICALL Java_com_marakana_jniexamples_Foo_processBar(JNIEnv *env, jobject object) {
          /* Same as object.getClass() */
          jclass clazz = (*env)->GetObjectClass(env, object);
          if (clazz != NULL) {
              /* Same as clazz.getMethod("setBar", String.class) - assuming non-static */
              jmethodID method = (*env)->GetMethodID(env, clazz, "setBar", "(Ljava/lang/String;)V");
              if (method != NULL) { /* make sure we found the method */
                  /* Create a new Java String */
                  jstring jString = (*env)->NewStringUTF(env, "Bar2");
                  if (jString != null) {
                      /* Same as method.invoke(object, jString) */
                      (*env)->CallVoidMethod(env, object, method, jString);
                  }
              }
          }
      }

Registering Native Methods On Load

  • JNI supports JNI_OnLoad function, which if exported by the library, will automatically be called when the library is loaded bySystem.loadLibrary(String)

    jint JNI_OnLoad(JavaVM *vm, void *reserved);
  • When used with JNI’s RegisterNatives, we can pre-bind native methods as soon as our library is first loaded, and consequently, we no longer need to usejavah:

    #include <jni.h>
    
    static void sayHi(JNIEnv *env, jclass clazz, jstring who, jint times) {
      const char *name = (*env)->GetStringUTFChars(env, who, NULL);
      if (name != NULL) {
        jint i;
        for (i = 0; i < times; i++) {
          printf("Hello %s\n", name);
        }
        (*env)->ReleaseStringUTFChars(env, who, name);
      }
    }
    
    static JNINativeMethod method_table[] = {
      { "sayHi", "(Ljava/lang/String;I)V", (void *) sayHi }
    };
    
    static int method_table_size = sizeof(method_table) / sizeof(method_table[0]);
    
    jint JNI_OnLoad(JavaVM* vm, void* reserved) {
      JNIEnv* env;
      if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
      } else {
        jclass clazz = (*env)->FindClass(env, "com/marakana/jniexamples/Hello");
        if (clazz) {
          jint ret = (*env)->RegisterNatives(env, clazz, method_table, method_table_size);
          (*env)->DeleteLocalRef(env, clazz);
          return ret == 0 ? JNI_VERSION_1_6 : JNI_ERR;
        } else {
          return JNI_ERR;
        }
      }
    }
    
    NoteNotice that we JNI function that will be bound to the native Java method is declared as static. It won’t even be exported to the symbol table. It does not need to be becauseRegisterNatives will bind it via a function pointer to the Java method.
  • JNI supports JNI_OnUnload, but since this is only invoked when the class-loader that loaded the library is GC’ed, it’s not commonly used

Exceptions

  • JNI permits us to throw exceptions from native code

    • Assuming we have a Throwable object:

      jint Throw(JNIEnv *env, jthrowable obj);
    • Assuming we have a Throwable class, and that it has a constructor that takes a string message:

      jint ThrowNew(JNIEnv *env, jclass clazz, const char *message);
    • For example:

      static void ThrowExceptionByClassName(JNIEnv *env, const char *name, const char *message) {
          jclass clazz = (*env)->FindClass(env, name);
          if (clazz != NULL) {
              (*env)->ThrowNew(env, clazz, message);
              (*env)->DeleteLocalRef(env, clazz);
          }
      }if (invalidArgument == TRUE) {
          ThrowExceptionByClassName(env, "java/lang/IllegalArgumentException", "This argument is not valid!");
      }
  • Throwing an exception from native code just registers the throwable (as an exception pointer) with the current thread - itdoes not interrupt the current flow of the code!

    • The pending exception gets "thrown" upon returning to the Java code

    • While an exception is pending, we can only call the following JNI functions:

      • DeleteGlobalRef

      • DeleteLocalRef

      • DeleteWeakGlobalRef

      • ExceptionCheck

      • ExceptionClear

      • ExceptionDescribe

      • ExceptionOccurred

      • MonitorExit

      • PopLocalFrame

      • PushLocalFrame

      • Release<PrimitiveType>ArrayElements

      • ReleasePrimitiveArrayCritical

      • ReleaseStringChars

      • ReleaseStringCritical

      • ReleaseStringUTFChars

  • Calling methods on Java objects (e.g. via CallObjectMethod) can fail with an exception, but since exceptions don’t automatically abort our function calls, we must explicitly check for existence of pending exceptions (e.g. with ExceptionCheck)

  • Native code can "catch" an exception by calling ExceptionCheck(…) orExceptionOccurred(…), print its stack trace (to stderr) withExceptionDescribe(…), and "handle" it (i.e. clear it) withExceptionClear(…)

    jthrowable ExceptionOccurred(JNIEnv *env);  /* NULL if no exception is currently being thrown */
    jboolean ExceptionCheck(JNIEnv *env);
    void ExceptionDescribe(JNIEnv *env);
    void ExceptionClear(JNIEnv *env);
    • To get to the message of a throwable object we would do something like:

      (*env)->CallObjectMethod(env,); /* this can throw an exception */
      
      if ((*env)->ExceptionCheck(env)) {
          jthrowable throwable = (*env)->ExceptionOccurred(env);
          (*env)->ExceptionDescribe(env); /* optionally dump the stack trace */
          (*env)->ExceptionClear(env); /* mark the exception as "handled" */
          jclazz clazz = (*env)->GetObjectClass(env, throwable);
          jmethodID getMessageMethod = (*env)->GetMethodID(env, clazz, "getMessage", "()Ljava/lang/String;");
          jstring message = (*env)->CallObjectMethod(env, throwable, getMessageMethod);
          const char *cMessage = (*env)->GetStringUTFChars(env, message, NULL);
          if (cMessage) {
              printf("ERROR: %s\n", cMessage);
              (*env)->ReleaseStringUTFChars(env, message, cMessage);
          }
          (*env)->DeleteLocalRef(env, clazz);
      }

===============================================数据类型转换===================================================

作者:陈波 2011/10/30(转载请注明出处,From:http://blog.csdn.net/jinhill/article/details/6918821

最近做Android开发的人越来越多,Android开发难免会遇到调用本地库,这就需要采用JNI技术,JNI本身并不复杂,但大多数开发者在类型转换上遇到麻烦,今天特地将几种常用类型转换写成一个实例来告诉大家如何转换,尤其是Java的类和C的结构的转换,结构体中嵌套结构体如何处理,这部分网上的资料也比较少。

1.        编写Java类

package com.jinhill.util;

public class NativeModule {

    public native int testArg(int i, boolean b, char c, double d);

    public native byte[] testByte(byte[] b);

    public native String[] testString(String s, String[] sarr);

    public native int setInfo(MyInfo info);

    public native MyInfo getInfo();

    static {

       System.loadLibrary("NativeModule");

    }

}


其中MyInfo类定义如下:

public class Record {

    int id;

    String name;

    byte[] data;

}

public class MyInfo {

    public boolean b;

    public char c;

    public double d;

    public int i;

    public byte[] array;

    public String s;

    public Record rec;

}

 

C自定义结构体

typedef struct{

    int id;

    char name[255];

    char data[255];

}Record;

 

typedef struct{

    BOOL b;

    char c;

    double d;

    int i;

    char arr[255];

    char sz[255];

    Record rec;

}MyInfo;

2.        生成jni头文件

1)        编译javac com/jinhill/util/NativeModule.java

2)        javah –jni com.jinhill.util.NativeModule

这样com_jinhill_util_NativeModule.h文件就生成好了。

3.        编写C库

1)        Java与C不同类型参数转换实例

//不同类型参数处理

JNIEXPORT jintJNICALL Java_com_jinhill_util_NativeModule_testArg

  (JNIEnv *env, jobject jo, jint ji, jbooleanjb,  jchar jc, jdouble jd)

{

         //获取jint型值

         int i = ji;

         //获取jboolean型值

         BOOL b = jb;

         //获取jdouble型值

         double d = jd;

         //获取jchar型值,Java的char两字节

         char ch[5] = {0};

         int size = 0;

         size = WideCharToMultiByte(CP_ACP,NULL, (LPCWSTR)&jc, -1, ch, 5, NULL, FALSE);

         if(size <= 0)

         {

                   return -1;

        }

         Trace("ji=%d,jb=%d,jc=%s,jd=%lf",i, b, ch, d);

         return 0;

}

2)        Java byte与C char数组类型数组转换实例

//btye数组处理,形参作为输入或输出,返回btye数组

JNIEXPORTjbyteArray JNICALL Java_com_jinhill_util_NativeModule_testByte

  (JNIEnv *env, jobject jo, jbyteArray jbArr)

{

         char chTmp[] = "Hello JNI!";

         int nTmpLen = strlen(chTmp);

         //获取jbyteArray

         char *chArr = (char*)env->GetByteArrayElements(jbArr,0);

         //获取jbyteArray长度

         int nArrLen = env->GetArrayLength(jbArr);

         char *szStrBuf =(char*)malloc(nArrLen*2+10);

         memset(szStrBuf, 0, nArrLen*2+10);

         Bytes2String(chArr, nArrLen, szStrBuf,nArrLen*2+10);

         Trace("jbArr=%s", szStrBuf);

         //将jbArr作为输出形参

         memset(chArr, 0, nArrLen);

         memcpy(chArr, chTmp, nTmpLen);

         //返回jbyteArray

         jbyteArray jarrRV =env->NewByteArray(nTmpLen); 

    jbyte *jby =env->GetByteArrayElements(jarrRV, 0);

         memcpy(jby, chTmp, strlen(chTmp));

    env->SetByteArrayRegion(jarrRV, 0,nTmpLen, jby); 

         return jarrRV;

}

3)        Java String与C char数组类型转换实例

//String 和String[]处理

JNIEXPORTjobjectArray JNICALL Java_com_jinhill_util_NativeModule_testString

  (JNIEnv *env, jobject jo, jstring jstr,jobjectArray joarr)

{

         int i = 0;

         char chTmp[50] = {0};

         //获取jstring值

         const char* pszStr = (char*)env->GetStringUTFChars(jstr, 0);

         Trace("jstr=%s", pszStr);

         //获取jobjectArray值

         int nArrLen =env->GetArrayLength(joarr);

         Trace("joarr len=%d",nArrLen);

         for(i=0; i<nArrLen; i++)

         {

                   jstring js =(jstring)env->GetObjectArrayElement(joarr, i);

                   const char* psz = (char*)env->GetStringUTFChars(js, 0);

                   Trace("joarr[%d]=%s",i, psz);

         }

         //将joarr作为输出形参

         jstring jstrTmp = NULL;

         for(i=0; i<nArrLen; i++)

         {

                   sprintf(chTmp, "No.%dHello JNI!", i);

                   jstrTmp =env->NewStringUTF(chTmp);

                   env->SetObjectArrayElement(joarr,i, jstrTmp);

                   env->DeleteLocalRef(jstrTmp);

         }

         //返回jobjectArray

         jclass jstrCls =env->FindClass("Ljava/lang/String;");

         jobjectArray jstrArray =env->NewObjectArray(2, jstrCls, NULL);

         for(i=0; i<2; i++)

         {

                   sprintf(chTmp, "No. %dReturn JNI!", i);

                   jstrTmp =env->NewStringUTF(chTmp);

                   env->SetObjectArrayElement(jstrArray,i, jstrTmp);

                   env->DeleteLocalRef(jstrTmp);

         }

         return jstrArray;

}

4)        Java 类与C结构体类型转换实例

JNIEXPORT jint JNICALL Java_com_jinhill_util_NativeModule_setInfo

  (JNIEnv *env, jobject jo, jobject jobj)

{

    char chHexTmp[512] = {0};

    //将Java类转换成C结构体

    MyInfo mi;

 

    //获取Java中的实例类Record

    jclass jcRec = env->FindClass("com/jinhill/util/Record");

    //int id

    jfieldID jfid = env->GetFieldID(jcRec, "id", "I");

    //String name

    jfieldID jfname = env->GetFieldID(jcRec, "name", "Ljava/lang/String;");

    //byte[] data;

    jfieldID jfdata = env->GetFieldID(jcRec, "data", "[B");

 

    //获取Java中的实例类MyInfo

    jclass jcInfo = env->FindClass("com/jinhill/util/MyInfo");

    //获取类中每一个变量的定义

    //boolean b

    jfieldID jfb = env->GetFieldID(jcInfo, "b", "Z");

    //char c

    jfieldID jfc = env->GetFieldID(jcInfo, "c", "C");

    //double d

    jfieldID jfd = env->GetFieldID(jcInfo, "d", "D");

    //int i

    jfieldID jfi = env->GetFieldID(jcInfo, "i", "I");

    //byte[] array

    jfieldID jfa = env->GetFieldID(jcInfo, "array", "[B");

    //String s

    jfieldID jfs = env->GetFieldID(jcInfo, "s", "Ljava/lang/String;");

    //Record rec;

    jfieldID jfrec = env->GetFieldID(jcInfo, "rec", "Lcom/jinhill/util/Record;");

 

    //获取实例的变量b的值

    mi.b = env->GetBooleanField(jobj, jfb);

    //获取实例的变量c的值

    jchar jc = env->GetCharField(jobj, jfc);

    char ch[5] = {0};

    int size = 0;

    size = WideCharToMultiByte(CP_ACP, NULL, (LPCWSTR)&jc, -1, ch, 5, NULL, FALSE);

    mi.c = ch[0];

    //获取实例的变量d的值

    mi.d = env->GetDoubleField(jobj, jfd);

    //获取实例的变量i的值

    mi.i = env->GetIntField(jobj, jfi);

    //获取实例的变量array的值

    jbyteArray ja = (jbyteArray)env->GetObjectField(jobj, jfa);

    int  nArrLen = env->GetArrayLength(ja);

    char *chArr = (char*)env->GetByteArrayElements(ja, 0);

    memcpy(mi.arr, chArr, nArrLen);

    //获取实例的变量s的值

    jstring jstr = (jstring)env->GetObjectField(jobj, jfs);

    const char* pszStr = (char*)env->GetStringUTFChars(jstr, 0);

    strcpy(mi.sz, pszStr);

 

    //获取Record对象

    jobject joRec = env->GetObjectField(jobj, jfrec);

    //获取Record对象id值

    mi.rec.id = env->GetIntField(joRec, jfid);

    Trace("mi.rec.id=%d",mi.rec.id);

    //获取Record对象name值

    jstring jstrn = (jstring)env->GetObjectField(joRec, jfname);

    pszStr = (char*)env->GetStringUTFChars(jstrn, 0);

    strcpy(mi.rec.name, pszStr);

    //获取Record对象data值

    jbyteArray jbd = (jbyteArray)env->GetObjectField(joRec, jfdata);

    nArrLen = env->GetArrayLength(jbd);

    chArr = (char*)env->GetByteArrayElements(jbd, 0);

    memcpy(mi.rec.data, chArr, nArrLen);

 

    //日志输出

    Bytes2String(mi.arr, nArrLen, chHexTmp, sizeof(chHexTmp)); 

   Trace("mi.arr=%s, mi.b=%d, mi.c=%c, mi.d=%lf, mi.i=%d, \n mi.sz=%s\n mi.rec.id=%d, mi.rec.name=%s", chHexTmp, mi.b, mi.c, mi.d, mi.i, mi.sz, mi.rec.id, mi.rec.name);

     return 0;

}

5)        C结构体类型与Java 类转换实例

 

JNIEXPORT jobject JNICALL Java_com_jinhill_util_NativeModule_getInfo

  (JNIEnv *env, jobject jo)

{

    wchar_t wStr[255] = {0};

    char chTmp[] = "Hello JNI";

    int nTmpLen = strlen(chTmp);

    //将C结构体转换成Java类

    MyInfo mi;

    memcpy(mi.arr, chTmp, strlen(chTmp));

    mi.b = TRUE;

    mi.c = 'B';

    mi.d = 2000.9;

    mi.i = 8;

    strcpy(mi.sz, "Hello World!");

    mi.rec.id = 2011;

    memcpy(mi.rec.data, "\x01\x02\x03\x04\x05\x06", 6);

    strcpy(mi.rec.name, "My JNI");

 

    //获取Java中的实例类Record

    jclass jcRec = env->FindClass("com/jinhill/util/Record");

    //int id

    jfieldID jfid = env->GetFieldID(jcRec, "id", "I");

    //String name

    jfieldID jfname = env->GetFieldID(jcRec, "name", "Ljava/lang/String;");

    //byte[] data;

    jfieldID jfdata = env->GetFieldID(jcRec, "data", "[B");

 

    //获取Java中的实例类

    jclass jcInfo = env->FindClass("com/jinhill/util/MyInfo");

    //获取类中每一个变量的定义

    //boolean b

    jfieldID jfb = env->GetFieldID(jcInfo, "b", "Z");

    //char c

    jfieldID jfc = env->GetFieldID(jcInfo, "c", "C");

    //double d

    jfieldID jfd = env->GetFieldID(jcInfo, "d", "D");

    //int i

    jfieldID jfi = env->GetFieldID(jcInfo, "i", "I");

    //byte[] array

    jfieldID jfa = env->GetFieldID(jcInfo, "array", "[B");

    //String s

    jfieldID jfs = env->GetFieldID(jcInfo, "s", "Ljava/lang/String;");

    //Record rec;

    jfieldID jfrec = env->GetFieldID(jcInfo, "rec", "Lcom/jinhill/util/Record;");

 

    //创建新的对象

    jobject joRec = env->AllocObject(jcRec);

 

    env->SetIntField(joRec, jfid, mi.rec.id);

 

    jstring jstrn = env->NewStringUTF(mi.rec.name);

    env->SetObjectField(joRec, jfname, jstrn);

 

    jbyteArray jbarr = env->NewByteArray(6); 

    jbyte *jb = env->GetByteArrayElements(jbarr, 0);

    memcpy(jb, mi.rec.data, 6);

    env->SetByteArrayRegion(jbarr, 0, 6, jb);

    env->SetObjectField(joRec, jfdata, jbarr);

 

    //创建新的对象

    jobject joInfo = env->AllocObject(jcInfo);

 

    //给类成员赋值

    env->SetBooleanField(joInfo, jfb, mi.b);

 

//  MultiByteToWideChar (CP_ACP, 0, mi.c, -1, wStr, 255);

//  env->SetCharField(joInfo, jfc, (jchar)wStr);

 

    env->SetCharField(joInfo, jfc, (jchar)mi.c);

   

 

    env->SetDoubleField(joInfo, jfd, mi.d);

 

    env->SetIntField(joInfo, jfi, mi.i);

 

    jbyteArray jarr = env->NewByteArray(nTmpLen); 

    jbyte *jby = env->GetByteArrayElements(jarr, 0);

    memcpy(jby, mi.arr, nTmpLen);

    env->SetByteArrayRegion(jarr, 0, nTmpLen, jby);

    env->SetObjectField(joInfo, jfa, jarr);

 

    jstring jstrTmp = env->NewStringUTF(chTmp);

    env->SetObjectField(joInfo, jfs, jstrTmp);

 

    env->SetObjectField(joInfo, jfrec, joRec);

 

    return joInfo;

}

4.        编写Java测试代码

public class TestInfo {

 

    /**

     * @param args

     */

    public static void main(String[] args) {

       int i  =0;

       String[] sArr = new String[2];

       for(i=0; i<2; i++)

       {

           sArr[i] = "ID=" + i;

       }

       byte[] b = new byte[10];

       for(i=0; i<10; i++)

       {

           b[i] = (byte)i;

       }

       MyInfo mi = new MyInfo();

       mi.array = b;

       mi.b = false;

       mi.c = 'C';

       mi.d = 2011.11;

       mi.i = 1752;

       mi.s = "Hello World!";

      

       mi.rec = new Record();

       mi.rec.id = 2012;

       mi.rec.name = "Record";

       mi.rec.data = b;

       NativeModule nm = new NativeModule();

       nm.testArg(mi.i, mi.b, mi.c, mi.d);

       byte[] b2 = nm.testByte(mi.array);

       String[] s = nm.testString(mi.s, sArr);

       nm.setInfo(mi);

       MyInfo mi2 = nm.getInfo();

       System.out.println("finish");

    }

 

}

5.   Java String与 C char数组转换时的中文问题

//将jstring类型转换成windows类型
char* jstringToWindows( JNIEnv *env, jstring jstr )
{
int length = (env)->GetStringLength(jstr );
const jchar* jcstr = (env)->GetStringChars(jstr, 0 );
char* rtn = (char*)malloc( length*2+1 );
int size = 0;
size = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR)jcstr, length,rtn,(length*2+1), NULL, NULL );
if( size <= 0 )
return NULL;
(env)->ReleaseStringChars(jstr, jcstr );
rtn[size] = 0;
return rtn;
}

//将windows类型转换成jstring类型
jstring WindowsTojstring( JNIEnv* env, char* str )
{
jstring rtn = 0;
int slen = strlen(str);
unsigned short * buffer = 0;
if( slen == 0 )
rtn = (env)->NewStringUTF(str );
else
{
int length = MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, NULL, 0 );
buffer = (unsigned short *)malloc( length*2 + 1 );
if( MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, (LPWSTR)buffer, length )>0 )
rtn = (env)->NewString( (jchar*)buffer, length );
}
if( buffer )
free( buffer );
return rtn;
}

6.      源码下载


Android JNI学习路线可以按照以下步骤进行: 1. 了解JNI的基本概念和作用:JNI(Java Native Interface)是Java提供的一种机制,用于实现Java与其他编程语言(如C、C++)之间的交互。它允许在Java代码中调用本地代码(Native Code),并且可以在本地代码中调用Java代码。 2. 学习JNI的基本语法和规则:JNI使用一组特定的函数和数据类型来实现Java与本地代码之间的交互。你需要学习如何声明本地方法、如何在Java代码中调用本地方法、如何在本地代码中调用Java方法等。 3. 学习JNI的数据类型映射:JNI提供了一套数据类型映射规则,用于将Java数据类型映射到本地代码中的数据类型。你需要学习如何处理基本数据类型、对象类型、数组类型等。 4. 学习JNI的异常处理:在JNI中,Java代码和本地代码之间的异常处理是非常重要的。你需要学习如何在本地代码中抛出异常、如何在Java代码中捕获异常等。 5. 学习JNI的线程处理:JNI允许在本地代码中创建和操作线程。你需要学习如何创建和销毁线程、如何在线程之间进行通信等。 6. 学习JNI的性能优化:JNI涉及到Java代码和本地代码之间的频繁切换,因此性能优化是非常重要的。你需要学习如何减少JNI调用的次数、如何避免不必要的数据拷贝等。 7. 学习JNI的调试和测试:在开发JNI程序时,调试和测试是非常重要的。你需要学习如何使用调试器调试本地代码、如何进行单元测试等。 8. 学习JNI的进阶主题:一旦掌握了基本的JNI知识,你可以进一步学习JNI的进阶主题,如JNI与Java虚拟机的交互、JNI与动态链接库的交互、JNI与多线程的交互等。 总结起来,Android JNI学习路线包括了基本概念、基本语法、数据类型映射、异常处理、线程处理、性能优化、调试和测试以及进阶主题等内容。通过系统地学习这些知识,你将能够更好地理解和应用JNI技术。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值