`

android jni代码编写规则--整理总结

阅读更多

 JNI层的代码其实比较简单,难点是要掌握c++和java数据类型的转换,明白java程序是运行在虚拟机中的,特别是函数并不是可以互相调用,jni中的内存概念并没有暴露给java虚拟机进程等。

 

一.   java参数类型和jni本地参数类型对照

 

基本类型

Java 类型         jni本地类型                    描述 

boolean             jboolean                    C/C++ unsigned 8 bits

byte                jbyte                       C/C++ signed 8 bits

char                jchar                       C/C++ unsigned 16 bits

short               jshort                      C/C++ signed 16 bits

int                 jint                        C/C++ signed 32 bits

long                jlong                       C/C++ signed 64 bits

float               jfloat                      C/C++  32位浮点型

double              jdouble                     C/C++  64位浮点型

void                void                        N/A

                    表一

 

对象类型

Object              jobject                     任何Java对象,或者没有对应

java类型的对象

Class               jclass                      class对象

String              jstring                     字符串对象

                    表二

 

数组类型

boolean[]           jbooleanArray               布尔型数组 unsigned

byte[]              jbyteArray                  比特型数组 signed

char[]              jcharArray                  字符型数组 unsigned

short[]             jshortArray                 短整型数组 signed

int[]               jintArray                   整型数组 signed

long[]              jlongArray                  长整型数组 signed

float[]             jfloatArray                 浮点型数组

double[]            jdoubleArray                双浮点型数组

Object[]            jobjectArray                任何对象的数组

                    表三

 

 

JNI引用类型与Java的对应关系如下树层次图:

1. java中的返回值void和JNI中的void是完全对应的。

 

2. java中的基本数据类型(boolean ,byte , char ,short ,int,long,float,double八种)在JNI中对应的数据类型只要在前面加上j就对应了(jboolean ,jbyte , jchar ,jshort ,jint,jlong,jfloat,jdouble)。

JNI中还有个Java中没有的jsize,定义如下:

typedef jint jsize;

其实jsize整型是用来描述基本指标和大小。

 

3. java中的对象,包括类库中定义的类、接口以及自定义的类接口,都对应于JNI中的jobject。

 

4. java中基本数据类型的数组对应与JNI中的j<type>array类型。(type就是上面说的8种基本数据类型)

 

5. java中对象的数组对应于JNI中的jobjectArray类型。(在java中一切对象、接口以及数组都是对象)

http://blog.csdn.net/xyz_lmn/article/details/6956003

http://www.cnblogs.com/liangwind/archive/2009/08/18/1925515.html

   

    Java基本类型的精度

java 的基本数据类型是不存在有符号和无符号这种概念的. JAVA中的基本数据类型不存在无符号的,它们的取值范围是固定的,不会随着机器硬件环境或者操作系统的改变而改变。

简单类型   字节数   范围/精度

float       4       32位IEEE754单精度

double      8       64位IEEE754双精度

byte        1       -128到127

short       2       -32,768到32,767

int         4       -2,147,483,648到2,147,483,647

long        8       -9,223,372,036,854,775,808到9,223,372,036,854,775,807

char        2       整个Unicode字符集

boolean     1       True或者false

像byte 是范围是 -128到127, 你想要变为 0到255 怎么办, 跟 0XFF 做与运算 就可以了.

如 byte bb , 如果你想赋值它值 255, 那是不行的, 就算赋值了, bb 的值也是 255 对 256 求模后的值 -1

      如果你只是想取他 0到255 的值, 还是很简单的,

      bb & 0XFF  , 如 bb = -1,  那 bb & 0XFF 结果为 255,

      这个与运算后的结果会隐式转换为int 类型的, 因为 byte 放不下了.

与运算 还是很快的, 比加减法还快的.

http://www.stuhack.com/biancheng/java/35169.html

   

   

二.jni层使用java的基本类型数据

对于上面八种基本的数据类型boolean ,byte , char ,short ,int,long,float,double,jni层的c++代码可以用强制直接转换成对于长度的c/c++类型数据。

如:unsigned char tmp = (unsigned char) m_jboolean;

    unsigned short tmp = (unsigned short)m_jchar;

    或者同长度类型的数据就可以直接赋值的,int tmp = m_jint;

 

 

三.jni层对数组的使用

JNI通过JNIEnv提供的操作Java数组的功能。它提供了两个函数:一个是操作java的简单型数组的,另一个是操作对象类型数组的。

1.     操作java的简单型数组

因为速度的原因,简单类型的数组作为指向本地类型的指针暴露给本地代码。因此,它们能作为常规的数组存取。这个指针是指向实际的Java数组或者Java数组的拷贝的指针。另外,数组的布置保证匹配本地类型。

为了存取Java简单类型的数组,你就要要使用GetXXXArrayElements函数(见表三),XXX代表了数组的类型。这个函数把Java数组看成参数,返回一个指向对应的本地类型的数组的指针。

        完整的函数族见下表:

函数                         Java 数组类型  本地类型

GetBooleanArrayElements     jbooleanArray   jboolean

GetByteArrayElements         jbyteArray      jbyte

GetCharArrayElements         jcharArray      jchar

GetShortArrayElements        jshortArray     jshort

GetIntArrayElements          jintArray       jint

GetLongArrayElements         jlongArray      jlong

GetFloatArrayElements        jfloatArray     jfloat

GetDoubleArrayElements       jdoubleArray    jdouble

 

当你对数组的存取完成后,要确保调用相应的ReleaseXXXArrayElements函数,参数是对应Java数组和GetXXXArrayElements返回的指针。如果必要的话,这个释放函数会复制你做的任何变化(这样它们就反射到java数组),然后释放所有相 关的资源。

例如:

static jint com_ginwave_fs_com_HWRC_GetRecogRange(JNIEnv* env, jclass clazz, jintArray Handle)

{

unsigned long *pHandle = NULL;

int ret = 0;

jint *tmpHandle = env->GetIntArrayElements(Handle, 0);

pHandle = (unsigned long *)tmpHandle;

ret = (int)HWRC_GetRecogRange(pHandle);

env->ReleaseIntArrayElements(Handle, tmpHandle, 0);

return r

}

 

获取数组的长度:

jint theArrayLength = env->GetArrayLength(Frame);

       

        创建一个新的函数数组簇如下:

NewBooleanArray

NewByteArray

NewCharArray

NewShortArray

NewIntArray

NewLongArray

NewFloatArray

NewDoubleArray

        参数为数组长度,如:

        jbyte *list;

        jbyteArray byteArray = NULL;

        byteArray = env->NewByteArray(len);

        if (byteArray)

            env->SetByteArrayRegion(byteArray, 0, len, list);

       

        关于函数簇GetXXXArrayRegion和SetXXXArrayRegion,其中XXX为基本类型。

        例如:

        env->SetByteArrayRegion(buffer, destOffset, count, (const jbyte *)address + srcOffset);

        Setxxx的方向是从JNI层往java层传递;

        env->GetByteArrayRegion(buffer, srcOffset, count, (jbyte *)address + destOffset);

        而Getxxx的方向则是数据从java层向jni层传递。

       

        这里是获取简单型数组中的数据供jni或者下层使用,如果需要在jni层设置java

中对于的简单型数组的话,就需要使用到接下来讲到的对象类型的一些操作。

    总结下,有以下几簇函数:

    GetArrayLength

    NewXXXArray

    GetXXXArrayElements

    ReleaseXXXArrayElements

    GetXXXArrayRegion

    SetXXXArrayRegion

    对于数据,暂时遇到这些函数了。。。

   

 

 

2.     操作java对象类型数据

Java对象做为引用被传递到本地方法中,所有这些Java对象的引用都有一个共同的父类型jobject(相当于java中的Object类是所有类的父类一样)。

 

1). string对象

从java程序中传过去的String对象在本地方法中对应的是jstring类型,jstring类型和c中的char*不同,所以如果你直接当做char*使用的话,就会出错。因此在使用之前需要将jstring转换成为c/c++中的char*,这里使用JNIEnv的方法转换。

static jstring  com_prompt_getLine(JNIEnv *env, jobject obj, jstring prompt)

{

char buf[128];

const char *str = (*env)->GetStringUTFChars(env, prompt, 0);

printf("%s", str);

env->ReleaseStringUTFChars(prompt, str);

...

        

}

这里使用GetStringUTFChars方法将传进来的prompt(jstring类型)转换成为UTF-8的格式,就能够在本地方法中使用了。

注意:在使用完你所转换之后的对象之后,需要显示调用ReleaseStringUTFChars方法,让JVM释放转换成UTF-8的string的对象的空间,如果不显示的调用的话,JVM中会一直保存该对象,不会被垃圾回收器回收,因此就会导致内存溢出。

 

下面是访问String的一些方法:

GetStringUTFChars        将jstring转换成为UTF-8格式的char*

GetStringChars           将jstring转换成为Unicode格式的char*

ReleaseStringUTFChars    释放指向UTF-8格式的char*的指针

ReleaseStringChars       释放指向Unicode格式的char*的指针

NewStringUTF             创建一个UTF-8格式的String对象

NewString                创建一个Unicode格式的String对象

GetStringUTFLength       获取UTF-8格式的char*的长度

GetStringLength          获取Unicode格式的char*的长度

 

提供给两个jstring和char *互相转换的函数:

/* c/c++ string turn to java jstring */

static jstring strTojstring(JNIEnv* env, const unsigned char* pStr)

{

    int        strLen    = strlen((const char*)pStr);

    jclass     jstrObj   = env->FindClass("java/lang/String");

    jmethodID  methodId  = env->GetMethodID(jstrObj, "", "([BLjava/lang/String;)V");

    jbyteArray byteArray = env->NewByteArray(strLen);

    jstring    encode    = env->NewStringUTF("utf-8");

 

    env->SetByteArrayRegion(byteArray, 0, strLen, (jbyte*)pStr);

   

    return (jstring)env->NewObject(jstrObj, methodId, byteArray, encode);

}

//check ok!

 

 

/* java jstring turn to c/c++ string */

static char* jstringTostr(JNIEnv* env, jstring jstr)

{       

    char* pStr = NULL;

 

    jclass     jstrObj   = env->FindClass("java/lang/String");

    jstring    encode    = env->NewStringUTF("utf-8");

    jmethodID  methodId  = env->GetMethodID(jstrObj, "getBytes", "(Ljava/lang/String;)[B");

    jbyteArray byteArray = (jbyteArray)env->CallObjectMethod(jstr, methodId, encode);

    jsize      strLen    = env->GetArrayLength(byteArray);

    jbyte      *jBuf     = env->GetByteArrayElements(byteArray, JNI_FALSE);

 

    if (jBuf > 0)

    {

        pStr = (char*)malloc(strLen + 1);

 

        if (!pStr)

        {

            return NULL;

        }

 

        memcpy(pStr, jBuf, strLen);

 

        pStr[strLen] = 0;

    }

 

    env->ReleaseByteArrayElements(byteArray, jBuf, 0);

 

    return pStr;

}

// check ok!

       

        2) 访问java对象

    JNI提供的另外一个功能是在本地代码中使用Java对象。通过使用合适的JNI函数,你可以创建Java对象,get、set 静态(static)和 实例(instance)的域,调用静态(static)和实例(instance)函数。JNI通过ID识别域和方法,一个域或方法的ID是任何处理域和方法的函数的必须参数。

下表列出了用以得到静态(static)和实例(instance)的域与方法的JNI函数。每个函数接受(作为参数)域或方法的类,它们的名称,符号和它们对应返回的jfieldID或jmethodID。

       

        函数                    描述

GetFieldID              得到一个实例的域的ID

GetStaticFieldID        得到一个静态的域的ID

GetMethodID             得到一个实例的方法的ID

GetStaticMethodID       得到一个静态方法的ID

       

        下面以一个例子来说明用法:上下层之间需要传递一个或者多个结构体值。

        c/c++结构体定义:

        typedef struct tagTHWFrame{

            short left;

            short top;

            short width;

            short height;

} THWFrame;

当然在java层也需要定义一个匹配的类出来:

public class THWFrame{

    public short left;

    public short top;

    public short width;

    public short height;   

}

注意貌似这里只能定义成public的。

下面是jni层相关的代码,主要思想是对java对应类对象的属性域获得ID值后一个一个访问。

/* int HWRC_SetInputBox( unsigned long *pHandle, const THWFrame *pFrame ); */

static void ObjectTOTHWFrameStruct(JNIEnv* env, jobjectArray Frame, THWFrame *pFrame, int index)

{

    jobject obj = env->GetObjectArrayElement(Frame, index);

    jclass cls = env->GetObjectClass(obj);

    jfieldID left = env->GetFieldID(cls, "left", "S");

    pFrame[index].left = (short)env->GetShortField(obj, left);

       

    jfieldID top = env->GetFieldID(cls, "top", "S");

    pFrame[index].top = (short)env->GetShortField(obj, top);

       

    jfieldID width = env->GetFieldID(cls, "width", "S");

    pFrame[index].width = (short)env->GetShortField(obj, width);

       

    jfieldID height = env->GetFieldID(cls, "height", "S");

    pFrame[index].height = (short)env->GetShortField(obj, height); 

   

}

static jint com_ginwave_fs_com_HWRC_SetInputBox(JNIEnv* env, jclass clazz,

            jintArray Handle, jobjectArray Frame)

{

    unsigned long *pHandle = NULL;

    THWFrame *pFrame = NULL;

    int frame_len = 0;

    int ret = 0;

   

    jint *tmpHandle = env->GetIntArrayElements(Handle, 0);

    pHandle = (unsigned long *)tmpHandle;

   

    jint theArrayLength = env->GetArrayLength(Frame);

    frame_len = theArrayLength;

    pFrame = (THWFrame *)malloc( sizeof(THWFrame) * frame_len );

   

    for( int i = 0; i < frame_len; i++ ){

        ObjectTOTHWFrameStruct(env, Frame, pFrame, i);

    }

   

    ret = HWRC_SetInputBox(pHandle, (const THWFrame *)pFrame);

   

    env->ReleaseIntArrayElements(Handle, tmpHandle, 0);

    free(pFrame);

    frame_len = NULL;

   

    return ret;

}

// {"HWRC_SetInputBox", "([I[Ljava/com/ginwave/fs/com/THWFrame)I" , (void *)com_ginwave_fs_com_HWRC_SetInputBox },

// check ok!

 

 

/* int HWRC_GetInputBox( unsigned long *pHandle, THWFrame *pFrame ); */

static void THWFrameStructTOObject(JNIEnv* env, jobjectArray Frame, THWFrame *pFrame, int index)

{

    jobject obj = env->GetObjectArrayElement(Frame, index);

    jclass cls = env->GetObjectClass(obj);

    jfieldID left = env->GetFieldID(cls, "left", "S");

    env->SetShortField(obj, left, (short)pFrame[index].left);

       

    jfieldID top = env->GetFieldID(cls, "top", "S");

    env->SetShortField(obj, top, (short)pFrame[index].top);

       

    jfieldID width = env->GetFieldID(cls, "width", "S");

    env->SetShortField(obj, width, (short)pFrame[index].width);

       

    jfieldID height = env->GetFieldID(cls, "height", "S");

    env->SetShortField(obj, height, (short)pFrame[index].height);

}

static jint com_ginwave_fs_com_HWRC_GetInputBox(JNIEnv* env, jclass clazz,

            jintArray Handle, jobjectArray Frame)

{

    unsigned long *pHandle = NULL;

    THWFrame *pFrame = NULL;

    int frame_len = 0;

    int ret = 0;

   

    jint *tmpHandle = env->GetIntArrayElements(Handle, 0);

    pHandle = (unsigned long *)tmpHandle;

   

    jint theArrayLength = env->GetArrayLength(Frame);

    frame_len = theArrayLength;

    pFrame = (THWFrame *)malloc( sizeof(THWFrame) * frame_len );

   

    ret = HWRC_GetInputBox(pHandle, pFrame);

   

    for( int i = 0; i < frame_len; i++ ){

        THWFrameStructTOObject(env, Frame, pFrame, i);

    }

   

    env->ReleaseIntArrayElements(Handle, tmpHandle, 0);

    free(pFrame);

    frame_len = NULL;

   

    return ret;

}

// {"HWRC_GetInputBox", "([I[Ljava/com/ginwave/fs/com/THWFrame)I" , (void *)com_ginwave_fs_com_HWRC_GetInputBox },

// check ok!

 

其中,比较难理解的应该是函数的签名了,下面是他们的一些规则:

这个数组的类型是JNINativeMethod,定义如下:

typedef struct {

const char* name;

const char* signature;

void* fnPtr;

} JNINativeMethod;

             

第一个变量name是Java中函数的名字。

第二个变量signature,用字符串是描述了函数的参数和返回值

第三个变量fnPtr是函数指针,指向C函数。

 

其中比较难以理解的是第二个参数,例如

"()V"

"(II)V"

"(Ljava/lang/String;Ljava/lang/String;)V"

             

实际上这些字符是与函数的参数类型一一对应的。

"()" 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Func();

"(II)V" 表示 void Func(int, int);

具体的每一个字符的对应关系如下

             

字符             Java类型      C类型

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[]

 

objects对象          Lfully-qualified-class-name;         L类名

Arrays数组          [array-type                                 [数组类型

 

方法参数或者返回值为java中的对象时,必须以“L”加上其路径,不过此路径必须以“/”分开,自定义的对象也使用本规则,不在包中时直接“L”加上类名称。比如说 java.lang.String为“java/lang/String”,com.nedu.jni.helloword.Student为"com /nedu/jni/helloword/Student"

 

方法参数或者返回值为数组时类型前加上[,例如[I表示 int[],[[[D表示 double[][][],即几维数组就加几个[。

 

JNI函数中始终包含两个必要的参数:JNIEnv* env, jclass clazz

JNIEnv *――它是一个接口指针,用于定位函数表中的函数!

在JNI规范中一般称  为   “Interface Pointer”。看到这儿好像和过程调用很类似了!是的,JNI中的操作过程,就是面向过程的!后面的jobject是  一个指向该类的指针,类似与C语言中的this。这个第二个参数是变化的,当该方法为类的实例方法时该参数为jobject;当该方法为类方法 (即静态方法)时该参数为jclass,指向该类的class。

通过ndk编程来得到jni层头文件的时候,这第二个参数对于staic方法,生成出来的就是jclass,而对于非staic方法,生成出来的就是jobject。

              

从上图可知,jobject包含了其实概括了所有的java类型,也就是说,像上图中的非jobject类型的数据,在传递参数的时候都可以以jobject类型传递下去。比如说,如果要java中要传递一个二(多)维int数组下去,就可以包装成jobjectArray传下去,只不过对应的签名要弄成[[I了。

 

对于访问java对象的方法

在本地方法中调用Java对象的方法的步骤:

①.获取你需要访问的Java对象的类:

jclass cls = (*env)->GetObjectClass(env, obj);   // FindClass(“android/util/log”)

使用GetObjectClass方法获取obj对应的jclass。 // 直接搜索类名,需要是static修饰的类。

②.获取MethodID:

jmethodID mid = (*env)->GetMethodID(env, cls, "callback", "(I)V");

// GetStaticMethodID(…)  , 获取静态方法的ID

使用GetMethdoID方法获取你要使用的方法的MethdoID。其参数的意义:

env-->JNIEnv

cls-->第一步获取的jclass

"callback"-->要调用的方法名

"(I)V"-->方法的Signature, 签名同前面的JNI规则。

③.调用方法:

(*env)->CallVoidMethod(env, obj, mid, depth);

// CallStaticIntMethod(….) , 调用静态方法

使用CallVoidMethod方法调用方法。参数的意义:

env-->JNIEnv

obj-->通过本地方法穿过来的jobject

mid-->要调用的MethodID(即第二步获得的MethodID)

depth-->方法需要的参数(对应方法的需求,添加相应的参数)

注:这里使用的是CallVoidMethod方法调用,因为没有返回值,如果有返回值的话使用对应的方法,在后面会提到。

CallVoidMethod               CallStaticVoidMethod

CallIntMethod                     CallStaticVoidMethod

CallBooleanMethod              CallStaticVoidMethod

CallByteMethod                   CallStaticVoidMethod

其实jni中还有很多很多的接口函数这里没有列举,可以直接参考源码:

$ find  frameworks/base  type d  -name  jni

./voip/jni

./rfid/jni

./freestylus/jni

./native/graphics/jni

./drm/jni

./tests/BrowserTestPlugin/jni

./services/jni

./packages/TtsService/jni

./media/jni

./media/libdrm/mobile1/include/jni

./media/libdrm/mobile1/src/jni

./graphics/jni

./core/jni

./opengl/tests/gl_jni/jni

./opengl/tests/gl2_jni/jni

./opengl/tests/gldual/jni

这么多jni目录都可以参考,其中主要是core/jni目录了。

四、关于异常

异常接口有:

jniThrowException(env, "java/lang/RuntimeException", "Method called after release()");

env->ThrowNew(env->FindClass("java/io/IOException"),"CWJLog Error, IOException");

doThrow(env, "java/lang/IllegalStateException", msg);

使用Throw,自己构造(没用过)

jclass clazz = env->FindClass("java/io/IOException");

jmethodID methodId = env->GetMethodID(clazz, "<init>", "()V");

jthrowable throwable = env->NewObject(clazz, methodId);

env->Throwthrowable);

 

 

参考网址:

http://blog.csdn.net/xyz_lmn/article/details/6959545

 Android JNI入门第三篇——jni头文件分析 

http://blog.csdn.net/xyz_lmn/article/details/6966259

 Android JNI入门第四篇——Android.mk文件分析

http://blog.csdn.net/xyz_lmn/article/details/7017420

 Android JNI开发提高篇 

http://blog.csdn.net/xyz_lmn/article/details/6956003

 Android JNI入门第二篇——Java参数类型与本地参数类型对照

 

http://wenku.baidu.com/view/e9e28ca1b0717fd5360cdc18.html

JNI入门

http://www.ibm.com/developerworks/cn/java/j-jni/

使用 Java Native Interface 的最佳实践

http://helloxuweifu.iteye.com/blog/1168647

http://blog.csdn.net/kangyaping/article/details/6584027

JNI函数调用大全

 

http://newfaction.net/2010/11/30/java-jni-getfieldid-and-getmethodid-and-parameter-description.html

java jni GetFieldID 和 GetMethodID 以及参数的说明

http://hi.baidu.com/spmno/blog/item/7d4d764ea78a6809b3de0588.html

jni中使用数组的几个方法

http://xxw8393.blog.163.com/blog/static/3725683420107109411366/

JNI 返回结构体参数 

http://www.cnblogs.com/nicholas_f/archive/2010/11/30/1892124.html

JNI中java类型与C/C++类型对应关系

http://blog.csdn.net/sunny09290/article/details/6884994

JNI数据类型

http://www.cnblogs.com/liangwind/archive/2009/08/18/1925515.html

jni --c/c++ 数据类型、数组、对象

http://www.cnblogs.com/diyunpeng/archive/2009/09/24/1573296.html

Java有符号数与无符号数

http://www.stuhack.com/biancheng/java/35169.html

Java的基本数据类型是无符号的

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics