JNI入门学习-翻译官方

Home / Android MrLee 2015-3-3 3789

我们知道,Java应用程序是一处编码,处处运行的,之所以可以这么威风,靠的就是JVM这个东西,那什么是JVM呢,JVM乃Java虚拟机,是一种虚拟技术,位于java应用程序和特定的操作系统之间,担当着“一处编码,处处运行”的重任。他隐藏了操作系统的差异性,使得运行在上层的java程序可以不用管底层到底是神马操作系统。这样,只要你的机子上有JVM,那么你的机子就可以跑java程序,至于你用的是神马操作系统,我不用管。

可是,有滴时候,我们并不能抛开特定的操作系统的特性,而是用纯粹的java来完成我们的应用。虽然,java是多么多么滴强大,但是他也不是万能的。比如,现在我们的应用需要一些特定操作系统的特性,但是java它不支持,怎么办?或者,我们需要的部分核心功能已经存在了,但是是C/C++的库文件,更常见的一个理由是“这个处理过程性能要求高,java性能忒差,满足不了客户要求,用C/C++吧,性能高”!

如此可见,JNI的存在,总有它的理由。但是JNI的使用,我们破坏了java“一处编码,处处运行”的优势,使得我们的应用是“Host-Dependable”。同时,Java是类型安全的,而C/C++不是滴,所以,使用JNI,java这两大特性就荡然无存了。也因此,要不要使用JNI,我们需要三思而后行。。。

扯了这么远,回过头来,JNI是如何使得Java和C/C++想通的?我们知道,Java之所以不能和C/C++想通,最主要的原因就是类型差异。
关于JNI如何处理类型差异的,后面再说。这里,我们看看Java的数据类型String和C语言的Char*类型是在可见的(黑盒)层面如何相互沟通的。

我们使用Java写个类Jni.java:
package demo.jni;  
public class Jni {  
      
    public native int add();  
    public native String getString();  
}  
在使用javac编译该文件生成Jni.class文件后,我们使用javah -jni来生成一个C语言的头文件(关于javah命令的正确使用,请看我的另一个帖子),该文件名称是demo_jni_Jni.h:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class demo_jni_Jni */
#ifndef _Included_demo_jni_Jni
#define _Included_demo_jni_Jni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     demo_jni_Jni
 * Method:    add
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_demo_jni_Jni_add
  (JNIEnv *, jobject);
/*
 * Class:     demo_jni_Jni
 * Method:    getString
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_demo_jni_Jni_getString
  (JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
可以看到,生成的本地方法中,第一个参数类型是JNIEnv*,是一个指向JNIEnv的指针类型,那么JNIEnv是一个什么类型呢? JNIEnv是一个指向了一个Pointer,这个Pointer指向了一张函数表,这张函数表中的每一项就是JNI中的一个函数的入口。本地的方法通过查找这张表来调用某个jni函数,来和JVM交互。如图:

417812_1305104788uX9v


JNI为了扮演在c/c++和java之间的匹配工作,它首先需要做的就是既要认识java中的数据类型,也要认识c/c++中的数据类型。这时,jni就相当于一个适配者了。好比,A与C两个人本来不认识,可是现在B认识A,B也认识C,那么通过C的指引和介绍,A和C就认识了!那么jni如何做到既认识C/C++,也认识java呢?其实很简单,一张匹配表足矣,见图!1、原始数据类型的匹配:

417812_13061561471Juu


其中jni中的本地类型都是以j开头,可以在c/c++中直接和c/C++类型互通互换!2、复杂数据类型的匹配:

417812_13061561480w5w

几种复合数据类型,主要是字符串jstring和数组jarray。注意jni中复合数据类型不能直接转换为c/C++中相应的类型,需要调用jni中自带的相应的方法来操作!3、同时,JNI中还包含了一组描述符,包含类描述符、域描述符和方法描述符。3.1 类描述符 类描述符是类的完整名称(包名+类名),将原来的.分隔符换成/分隔符。比如在java代码中的java.lang.String类的类描述符就是java/lang/String。对于数组,其描述符是[+该类型的域描述符。比如int[] 其描述符为[I;float[] 其描述符为[F;String[]其描述符为[Ljava/lang/String;对于int[][] 其描述符为[[I;以此类推。。。3.2 域描述符 域描述符,分为原始数据类型的域描述符,和引用类型的域描述符。原始数据类型的域描述符如下图:

417812_1306239417ZZgG

注意哦,仔细看看long类型的域描述符(J);对于引用类型的域描述符,其以L开始,以;结束。对于数组,其为[+其类型的域描述符+; 比如:String类型的域描述符为Ljava/lang/String;int[]类型的域描述符为[I;Object[]类型的域描述符为[Ljava/lang/Object;3.3 方法描述符 方法描述符,将参数类型的域描述符按照申明顺序放入一对括号中后跟返回值类型的域描述符。对于,没有返回值的,用V表示。看下面一组例子:String test(); ----------------------------->()Ljava/lang/String; int f(int i, Object j);---------------------->(ILjava/lang/Object;)I set(byte[] bs);---------------------------->([B)V
简单点的实例来看看java端的类型,如何被C端识别。同时,在C端实现定义的一个类型 如何传递到java端。本篇以String类型为例,看看java端的unicode编码的String类型如何和C端utf-8编码的char*类型进行交互!

417812_1306303844A6EA

1、首先,在java端写一个Prompt类:其中定义一个getLine方法,他含有一个String类型的参数,同时,返回String类型。
package demo.jni;
public class Prompt {
	
	//传递给一个String对象
	private native String getLine(String str);
}

2、生成头文件,这里略。。。3、编写C端:讲解在注释中了
#include 
#include 
#include "demo_jni_Prompt.h"
JNIEXPORT jstring JNICALL Java_demo_jni_Prompt_getLine
  (JNIEnv *env, jobject clazz, jstring prompt)
{
     /**java中String类型的prompt这里,被转换成了jni中的一个类型jstring。
     ***那么这个jstring类型,我们可以直接cout输出吗?答案是否定的。
     ***我们知道在java中字符采用的是Unicode编码,在c/c++中,字符使用UTF-8编码
     ***我们不需要深入了解Unicode和UTF-8两种编码的具体方法和内容。我们只需要知道
     ***Unicode采用的是16-bit编码,而UTF-8采用的是7-bit编码的,所以,我们需要
     ***完成这一转换工作,jni已经提供了相应的接口,如下: 
     **/
     char buf[128] = "我是从本地代码获取到的"; //字符缓冲
     jbyte *str;//jbyte类型对应C中的unsigned char 
     str = (*env)->GetStringUTFChars(env, prompt, NULL); //这个函数就是将jstring类型的字符串转换为本地字符串,返回jbyte*类型
     if(str == NULL)
     {
         /**
            GetStringUTFChars方法可能会抛出一个OutOfMemoryError的异常,在jni中的异常机制和java中的并不一样
            在java中抛出异常,如果没有捕获,则程序结束运行;但是,在jni中,即使抛出异常,在本地代码的执行顺序依然不变。
            所以,这里判断NULL是必须的 
         **/ 
         return NULL;
         
     }
     printf("%s",str);
     /**
     **使用完了utf-8类型的字符后,我们需要释放由上面方法返回的字符串,这样可以释放被这些字符占用的内存空间,避免造成内存瘫痪 
     **/
     (*env)->ReleaseStringUTFChars(env, prompt, str);
     
     //下面我们看看如何将本地的代码传到java
     
     return (*env)->NewStringUTF(env, buf);  //该方法实例化一个UTF-8编码的本地字符串为java.lang.String类型,新创建的就是java中
     //Unicode类型的代表同一字符串的实例
     //该方法同样可能抛出一个OutOfMemoryError的异常并返回NULL。 
          
}
 
可以看到,在生成的本地方法中,原来的String类型的prompt被替换成了jstring。jni提供了GetStringUTFChars方法供我们,将jstring读取到本地。同时提供了NewStringUTF方法,让我们将本地的类型传递到java端。同理,对于其他的类型,比如数组等,操作也是类似,只是所使用的方法不同而已。

本文链接:https://www.it72.com/1209.htm

推荐阅读
最新回复 (1)
  • cuigx1991 2015-3-3
    引用 2
    开发过程中可以参考文档中对应JAVA和C++中的变量
返回