JNI 简介:

参考以下blog:http://blog.csdn.net/linweig/article/details/5417319

JNI 是 java native interface 的缩写,Java本地编程接口,Java的app是运行在JVM(Java virtual machine)上面的,
app使用Java语言编写,而linux kernel是使用c语言编写的,Java语言编写的app,要想调用c语言编写的linux接口,就必须通过JNI
使用JNI的好处,就是可以使Android 上用Java写的app,可以不用考虑底层的执行代码是哪种语言,可以兼容所有的运行平台。
java 程序要 调用 c 程序,主要有以下三个步骤:
一、加载,通过 System.loadLibrary 方法来查找和加载 c 库
二、建立 java 和 c 之间的映射关系,这个过程,主要是在c里面实现
三、调用,java里面调用已经和c建立映射关系的函数

层次关系如下:
Android App
---------------------------------------
JVM、JNI
---------------------------------------
linux kernel
linux driver


如何编写一个简单的JNI程序:
所有代码均保存在 JNIDemo 文件夹
1.先建立一个 JNIDemo.java 文件,内容如下:

public class JniDemo{    static   //静态代码块,在创建对象时,只会被执行一次    {    //通过调用这个方法,查找 和 加载 c 库里面的动态链接文件libnative.so    System.loadLibrary("native");   // libnative.so    }    public native static void hello(); //通过 native 来声明 hello() 方法是调用c库里面的    public static void main(String args[])    {         //在 java 的 hello() 和 c语言的 c_hello() 之间,建立映射关系         //这个映射关系,是由c语言来实现的         hello();   //调用 hello() 方法    }}




2.再建立一个 native.c 文件,内容如下:
#include "jni.h"#include "stdio.h"#include "stdlib.h"#if 0typedef struct {    const char* name;            /*Java中函数的名字*/             const char* signature;      /*描述了函数的参数和返回值*/    void* fnPtr;               /*函数指针,指向C函数*/} JNINativeMethod;#endif




//执行函数
//在 c语言里,永远都会多两个参数:JNIEnv *env, jclass cls ,
//这两个参数是固定默认给的,不需要在 JNINativeMethod 里体现出来
void c_hello(JNIEnv *env, jclass cls)      {      printf("hello world! \n");}

//这是一个 JNINativeMethod 类型的数组
//"()" 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Func(); "(II)V" 表示 void Func(int, int);
//如果Java函数的参数是class,则以"L"开头,以";"结尾,中间是用"/" 隔开的包及类名。
//而其对应的C函数名的参数则为jobject. 一个例外是String类,其对应的类为jstring
//除了 String 类用 jString来表示之外,其他的,都用 jObject 来表示
static const JNINativeMethod methods[] = {    {"hello","()V",(void*)c_hello},};JNIEXPORT jint JNICALL   //这只是一个宏JNI_OnLoad(JavaVM *jvm, void *reserved){    JNIEnv *env;    jclass cls;/* cache the JavaVM pointer *///获取Java 虚拟机的运行环境,获取运行环境,//是因为后面的操作函数如FindClass等,都是来源于 env 的    if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4))      {        return JNI_ERR; /* JNI version not supported */    }    cls = (*env)->FindClass(env, "JniDemo");  //查找需要建立映射的类,这个类就是上面建立的 JniDemo 类    if (cls == NULL)     {        return JNI_ERR;    }    /* 调用 RegisterNatives 把method注册进去*/    /* 参数定义:运行环境env  查找到的类cls   JNINativeMethod类型的数组  1-表示数组里面只有一个成员*/    if((*env)->RegisterNatives(env, cls, methods, 1))    {         return JNI_ERR;    }    return JNI_VERSION_1_4;   //返回一个 JNI 的版本号}

3.编写完成后,执行以下指令进行编译:
先编译java程序:javac JNIDemo.java

native.c 里有使用 jni.h 文件,该文件在 PC 机的如下目录:
ls /usr/lib/jvm/java-7-openjdk-amd64/include/jni.h -l

执行以下指令,把 native.c 编译成 libnative.so
gcc -I/usr/lib/jvm/java-7-openjdk-amd64/include/ -fPIC -shared -o libnative.so native.c
编译参数解释:
-I 表示指定 jni.h 的文件路径位置
-fPIC 表示生成位置无关码,参考博客: http://blog.sina.com.cn/s/blog_54f82cc201011op1.html
-shared 表示生成动态链接库 .so 文件
-o 表示生成目标文件

4.编译成功后,执行以下指令设置环境变量
把当前目录设置为库的加载目录: export LD_LIBRARY_PATH=.

5.执行 java JniDemo ,可以看到程序正确运行

------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------
如果 java 里面调用的 c函数有比较复杂的参数或者返回值,可以采用以下方法生成一个头文件,
这个头文件里面,包含了这些参数和返回值的描述符,这些描述符就是用来填充 JNINativeMethod methods[] 的


先要成功编译 JNIDemo.java
执行以下命令生成 JNIDemo.java 的头文件 JNIDemo.h
javah -jni JNIDemo 生成的 JNIDemo.h 的内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JniDemo */


#ifndef _Included_JniDemo
#define _Included_JniDemo
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: JniDemo 类名
* Method: hello 方法
* Signature: ()V 参数和返回值类型
*/
JNIEXPORT void JNICALL Java_JniDemo_hello
(JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
从 java 里传入 int 类型的数据,然后再返回 int 类型的数据
java程序里 public native static void hello(); 改为 public native static int hello(int i);
c 程序作如下修改:
函数 void c_hello(JNIEnv *env, jclass cls) 改为:jint c_hello(JNIEnv *env, jclass cls,jint m)
数组:
static const JNINativeMethod methods[] = {    {"hello","()V",(void*)c_hello},};

改为:
static const JNINativeMethod methods[] = {    {"hello","(I)I",(void*)c_hello},};

------------------------------------------------------------------------------------
------------------------------------------------------------------------------------
从java 里传入 String 类型的数据,然后再从c返回 String 类型的数据,比较复杂,
在c里面不能直接使用 String 类型的数据,要使用 env 里面提供的某些方法来访问 String 类的数据
参考 jni.pdf 第39页内容
const jbyte *str;str = (*env)->GetStringUTFChars(env, prompt, NULL);if (str == NULL) {    return NULL; /* OutOfMemoryError already thrown */}printf("%s", str);

要使用 GetStringUTFChars 方法,把从 java 传进来的 prompt 字符串转化为 jbyte 类型的数据

修改 native.c 里面的函数为以下内容:
jstring JNICALL c_hello(JNIEnv *env, jclass cls, jstring str){    const jbyte *string;    string = (*env)->GetStringUTFChars(env, str, NULL);   //获取 java 传入的 String 字符串     if (string == NULL)     {        return NULL;     }    printf("get string from java : %s\n",string);    (*env)->ReleaseStringUTFChars(env, str, string);     //释放之前分配的空间        return (*env)->NewStringUTF(env, "return from c");   //返回一个字符串给 java 程序}

-----------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
从 java 里面传入数组,从 c 程序里取出数组,不能直接使用该数组,同样要调用 env 里面的某些方法
参考 jni.pdf 第48页内容
JNIEXPORT jint JNICALLJava_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr){    jint buf[10];    jint i, sum = 0;    (*env)->GetIntArrayRegion(env, arr, 0, 10, buf);  //这个函数从arr数组里取出指定长度的数据    for (i = 0; i < 10; i++)     {        sum += buf[i];    }    return sum;}JNIEXPORT jint JNICALLJava_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr){    jint *carr;    jint i, sum = 0;    carr = (*env)->GetIntArrayElements(env, arr, NULL); //这个函数从 arr 数组取出所有数据    if (carr == NULL) {        return 0; /* exception occurred */    }    for (i=0; i<10; i++) {        sum += carr[i];    }    (*env)->ReleaseIntArrayElements(env, arr, carr, 0);    return sum;}

The GetArrayLength function returns the number of elements in primitive or
object arrays. The fixed length of an array is determined when the array is first
allocated.
这段话,大概意思是可以使用 GetArrayLength 函数来取出数组的长度
查看 jni.h 可以找到 GetArrayLength 函数原型:jsize (JNICALL *GetArrayLength)(JNIEnv *env, jarray array);

修改 c_hello 函数为如下内容:
jint JNICALL c_hello(JNIEnv *env, jclass cls, jintArray arr){    jint *carr;   //定义一个 carr 数组来保存数组内容    jint i, sum = 0;    carr = (*env)->GetIntArrayElements(env, arr, NULL); //获取数组里的所有元素    if (carr == NULL)     {        return 0; /* exception occurred */    }    for (i=0; i<(*env)->GetArrayLength(env,arr); i++) //GetArrayLength是获取数组的长度    {        sum += carr[i];    }    (*env)->ReleaseIntArrayElements(env, arr, carr, 0);  //释放数组元素    return sum; }

JNINativeMethod 类型的数组,改为以下内容:
static const JNINativeMethod methods[] = {    {"hello","([I)I",(void*)c_hello},};

"([I)I" --- 表示传入的是 int 类型的数组,返回值是 int 的数据类型

---------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------
从 java 程序里传入一个数组,然后返回一个数组,可以参考 jni.pdf 第50页的内容,关于数组操作的原始函数
先修改 JNIDemo.java 函数,然后编译,生成 JNIDemo.h ,查看c函数需要的描述符
JNIDemo.java 修改为如下内容:
public class JniDemo{    static     {        /* 1.load */        /* static block ,it could be execute one time */        System.loadLibrary("native");   // libnative.so    }    public native static int[] hello(int[] array);    public static void main(String args[])    {        int[] arr = {1,2,3};        int[] barry = null;        int i = 0;        barry = hello(arr);        for(i=0;i<barry.length;i++)        {            System.out.println(barry[i]);        }    }}

native.c 修改为如下内容:
JNINativeMethod 类型数组:
static const JNINativeMethod methods[] = {    {"hello","([I)[I",(void*)c_hello},};

"([I)[I" -- 表示传入的参数是 int 型数组,返回的是 int 型数组

c_hello() 函数修改为以下内容:
jintArray JNICALL c_hello(JNIEnv *env, jclass cls, jintArray arr){    jint *carr;   //用来保存从java传入的数组    jint *oarr;   //用来保存位置互换后的数组值    jintArray rarr;   //用来保存数组的返回值    jint i = 0;    jint n = 0;   //保存数组长度    carr = (*env)->GetIntArrayElements(env, arr, NULL);  //获取数组所有元素    if (carr == NULL)     {        return 0; /* exception occurred */    }    n = (*env)->GetArrayLength(env,arr);   //计算数组长度     oarr = malloc(n);   //申请一段内存空间    if(oarr == NULL)    {        (*env)->ReleaseIntArrayElements(env, arr, carr, 0);        return 0;   //申请失败,返回0      }    for(i=0;i<n;i++)    {        oarr[i] = carr[n-1-i];   //位置互换    }    rarr = (*env)->NewIntArray(env,n);    //new 一段空间给 rarr数组    if(rarr == NULL)   //判断一下返回值    {        (*env)->ReleaseIntArrayElements(env, arr, carr, 0);        return 0;   //申请失败,返回0     }    (*env)->SetIntArrayRegion(env,rarr,0,n,oarr);  //设置rarr数组里面的值    (*env)->ReleaseIntArrayElements(env, arr, carr, 0);    free(oarr);    return rarr; }

查看 jni.h ,可以分别找到 NewIntArray 和 SetIntArrayRegion 函数原型:
jintArray (JNICALL *NewIntArray)(JNIEnv *env, jsize len); 参数 :env-java环境 len-数据长度

void (JNICALL *SetIntArrayRegion)(JNIEnv *env, jintArray array, jsize start, jsize len, const jint *buf);
参数:env-java环境 array-要设置的jintArray类型数组,start-数组的起始点,len-要设置的长度,*buf-要设置的数据源


完成以上修改后,重新编译 native.c ,运行,程序正确


-------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------


以上是 JNI 的基本数据传递,更深入的 JNI 知识,会在后面继续补充。



未完待续。。。

更多相关文章

  1. Android(安卓)SharedPreferences 与 SQLite 基本使用
  2. Android中hw_get_module函数分析
  3. Android中Java服务过程
  4. android动画详解
  5. [转]Android自定义控件之TextView
  6. Android的init过程:初始化语言(init.rc)解析
  7. Android(安卓)SimpleAdapter的参数
  8. Android(安卓)NFS 文件系统
  9. 宏定义简单实现jni函数命名

随机推荐

  1. Workbench连接不上阿里云服务器Ubuntu的M
  2. MySQL 的覆盖索引与回表的使用方法
  3. 详解Mysql查询条件中字符串尾部有空格也
  4. MySQL 学习总结 之 初步了解 InnoDB 存储
  5. 解决MySql客户端秒退问题(找不到my.ini)
  6. MySQL延时复制库方法详解
  7. PostgreSQL物化视图(materialized view)过
  8. 如何更改MySQL数据库的编码为utf8mb4
  9. mysql不支持group by的解决方法小结
  10. Windows10下mysql 8.0.19 安装配置方法图