Skip to content

Latest commit

 

History

History
821 lines (621 loc) · 28.1 KB

File metadata and controls

821 lines (621 loc) · 28.1 KB

java JNI

参考博客:

1. windows下使用vs2013做的,可以参考

[TOC]

在学习这部分内容之前,先回去看看c语言的动态库加载。
java JNI有两种方法:

  • 一般方法
    主要是通过javah获取函数的签名,然后实现这些函数,这也是java官方推荐的方法。实现相对简单。
  • JNI_OnLoad 方法
    如果在c库文件中没有实现JNI_OnLoad 方法,则默认使用上述普通方法,如果实现了JNI_OnLoad 方法,那么java程序在调用System.loadLibrary()或者System.load()方法后,dvm就会调用dvmResolveNativeMethod来动态解析。对应的也有析构方法:JNI_OnUnLoad()
    **使用JNI_OnLoad有两个用处: **
    a. 告诉 JVM,此 C 组件使用的哪个版本的 JNI,如果 c 库中没有JNI_OnLoad那么JVM默认使用最老的JNI1.1版本。这样新的JNI版本中的扩充就无法使用。
    b. C 组件可以在JNI_OnLoad中自定义一些初始化动作。

一、 一般方法

此方法使用简单,对 C 组件要求低。大致流程如下:

  1. java 中 使用 System.loadLibrary()或者System.load() 来加载 C 库 ,然后把需要用C/C++实现的函数声明为native方法(加关键字native即可),最后便可以直接使用。
  2. 编译java程序得到class文件,然后使用javah工具得到JNI头文件,里面包含了这些native方法,在 c/c++ 中的函数原型。
  3. 在 c 代码中包含jni.h 和 这个生成的头文件,实现定义的native方法。
  4. 最后讲 c 代码编译成 动态库即可。

1.编写 java 代码

public class JNIDemo {
	  
      /*     利用静态代码块,加载c库,后面介绍loadLibrary和load方法的区别
       * 这里写的时hello,在Linux下,会自动去库的路径下查找 “libhello.so”,即libxxx.so 
      */
      static {
          System.loadLibrary("hello");
      }

	  /*   假设我们的java程序这两个函数需要用c或者c++来实现,所以声明为本地(native)方法
       *   声明native方法很简单,只需要加上关键字native即可。
       *   这个关键字告诉javac编译器,此方法时本地方法,其实现不在java文件中,不要给我报错!!!
      */	
      public static native int sayHello(String name);
      public static native String sayGoodbye(String name);

      public static void main(String[] args) {
			
           /*   在java中便可以直接调用native方法了。 */ 	
          System.out.println("the return value of sayHello is : " +
                             sayHello("zhangsan"));
          System.out.println("the return value of sayGoodbye is : " +
                             sayGoodbye("lisi"));
      }
}

2. 编译java代码,利用javah得到带有本地方法签名的头文件

	/* a. 编译 java 文件,和编译一般的java没任何区别 */
    $shell  javac JNIDemo.java 
    
    /* b. 使用javah 得到头文件 -jni 参数可以不要 */
    $shell javah -jni JNIDemo 
    
    就会在当前目录下生成 JNIDemo.h

我们可以看看其具体内容:
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" {   // 这里需要注意下:如果实现的文件时cpp文件, 这里的意思是,下面的函数要被c语言调用。
#endif
/*
 * Class:     JNIDemo
 * Method:    sayHello
 * Signature: (Ljava/lang/String;)I   // 关于这些,在JNI_Onload中要用到,后面再说
 */
JNIEXPORT jint JNICALL Java_JNIDemo_sayHello
  (JNIEnv *, jclass, jstring);

/*
 * Class:     JNIDemo
 * Method:    sayGoodbye
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_JNIDemo_sayGoodbye
  (JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif

3. 在 c/c++ 中实现本地方法

#include <stdio.h>

#include <jni.h>
#include "JNIDemo.h"


JNIEXPORT jint JNICALL Java_JNIDemo_sayHello
  (JNIEnv *env, jclass cls, jstring name) {
      int  ret = 100;  // just for a test;
      char* str_name=(char*) env->GetStringUTFChars(name,JNI_FALSE);

      printf("cpp :  hello %s\n",(char*)str_name);

      env->ReleaseStringUTFChars(name,str_name);

      return ret;
  }

JNIEXPORT jstring JNICALL Java_JNIDemo_sayGoodbye
  (JNIEnv *env, jclass cls, jstring name) {
	  /* 从jstring中获取字符串 ,后面详细介绍*/	
      char* str_name=(char*) env->GetStringUTFChars(name,JNI_FALSE);

      printf("call sayGoodbye  %s\n",str_name);
     
      env->ReleaseStringUTFChars(name,str_name);
      return 0;
  }

4. 编译动态库

因为我们在c代码中使用到了两个头文件jni.hJNIDemo.h 所以在编译时要用 -I 选项他们的路径,也可以把JNIDemo.h 拷贝到c目录下,但是jni.h 除了 它自己以外还需要别的头文件,所以不能直接拷贝。

  • 首先找到jni.h 的目录 jni.h 时随着jdk一起发布的,jni属于jdk的一部分。所以在jdk中。
$shell find /usr/local/java/jdk1.8.0_131/ -name "jni.h" 
/usr/local/java/jdk1.8.0_131/include/jni.h

在编译时我们发现,除了jni.h 还需要别的头文件,在LInux上还需要包含:
/usr/local/java/jdk1.8.0_131/include/linux  目录下的头文件
  • 编译动态库
	/* 编译选项就不多解释了,用的很多了 */
	$shell g++ -shared -fPIC -I/usr/local/java/jdk1.8.0_131/include -I/usr/local/java/jdk1.8.0_131/include/linux -o libhello.so hello.cpp 

5. java 程序的执行

上看我们也说了,java中加载动态库或者说windows下的共享库时,有两个方法。loadLibrary 方法和 load 方法。他们的区别在于:

  • System.loadLibrary("hello")
    loadLibrary() 参数为库名xxx。那么默认会加载libxxx.so 或者libxxx.dll
    查找路径:由jvm变量:java.library.path 指定。默认情况下这个变量包含了:
    1.和jre相关的一些目录
    2.程序当前目录
    3.Windows目录
    4.系统目录(system32),Linux下为/lib ,/usr/lib
    5.系统环境变量path指定目录

  • System.load("/home/coewang/work/java_project/0001th_java_jni/src/cpp_src/libhello.so")
    load 传入的必须是完整的动态库全路径,包含后缀名。 缺点:
    如果你要载入的库文件静态链接到其它动态链接库,例如TestJNI.dll 静态链接到dependency.dll, 那么你必须注意 如果你选择System.load("D://aaa//TestJNI.dll");
    那么即使你把dependency.dll同样放在D://aaa//下,load还是会因为找不到依赖的dll而失败。
    因为jvm在载入TestJNI.dll会先去载入TestJNI.dll所依赖的库文件dependency.dll
    而dependency.dll并不位于java.library.path所指定的目录下,所以jvm找不到dependency.dll。
    所以你要先laod依赖库,再load这个库。或者把目录加入到java.library.path 中。

我们使用了System.loadLibrary("hello"),所以要指定java.library.path

$shell  java -Djava.library.path=../cpp_src JNIDemo 

6. JNI 中 java 变量和 C 变量的转换

参考博客: JNI:在java和c之间进行数据传递

a. 基本数据类型

基本数据类型在java和c/c++ 中可以直接使用。他们的对应关系如下表所示:

java 本地类型 占用字节数
boolean jboolean 8,unsigned
byte jbyte 8
char jchar 16,unsigned
short jshort 16
int jint 32
long jlong 64
float jfloat 64
double jdouble 64
void void n/a

b. jstring 和 char * 的相互转换

  • jstring 转换为 char*
    jstring不能在c中被直接转换为char* 使用,我们可以想象一下,在 java 中 String 是一个类,它里面包含了字符数组和众多方法,要想把一个String 对象传递到 c 中,肯定是以某种数据结构打包发送到 c ,c中需要调用解析这个数据结构的函数来获得数据和对象中的方法。 下面是访问String的一些方法:

注意!!! 这些方法在C和CPP中有所不同!!!

C语言 : JNIEnv中从jstring获取字符串的方法 描述
const char* (JNICALL *GetStringUTFChars) (JNIEnv *env, jstring str, jboolean *isCopy) 将 jstring 转换成为 UTF-8 格式的char*
void (JNICALL *ReleaseStringUTFChars) (JNIEnv *env, jstring str, const char* chars) 释放指向UTF-8格式的char*的指针
const jchar *(JNICALL *GetStringChars) (JNIEnv *env, jstring str, jboolean *isCopy) 将jstring转换成为Unicode格式的char*
void (JNICALL *ReleaseStringChars) (JNIEnv *env, jstring str, const jchar *chars) 释放指向Unicode格式的char*的指针

实质上在成c++中这些函数是对c函数的一个封装,把c函数中第一个参数使用this代替。

C++ : JNIEnv中从jstring获取字符串的方法 描述
const char* GetStringUTFChars(jstring str, jboolean *isCopy) 将 jstring 转换成为 UTF-8 格式的char*
void ReleaseStringUTFChars(jstring str, const char* chars) 释放指向UTF-8格式的char*的指针
const jchar *GetStringChars(jstring str, jboolean *isCopy) 将jstring转换成为Unicode格式的char*
void ReleaseStringChars(jstring str, const jchar *chars) 释放指向Unicode格式的char*的指针

声明 : JNI在c和c++中的区别

**在调用方面,cpp中使用 env->xxxx(...) 因为在c++中 env 为对象; ** *在c语言中使用 (env)->xxx(env,...) ,在c中 env 是 2重指针 以上结论对所有函数实用,下面只介绍c++的,c语言可以推出来,加env即可

测试代码 c++:

JNIEXPORT jint JNICALL Java_JNIDemo_sayHello
  (JNIEnv *env, jclass cls, jstring name) {
      int  ret = 100;  // just for a test;
      /* 第二个参数指定是不是要拷贝,还是直接引用 */
      char* str_name=(char*) env->GetStringUTFChars(name,JNI_FALSE);

      printf("cpp :  hello %s\n",(char*)str_name);
      
	  /* 使用之后切记要释放,否则会造成内存的泄露 */	
      env->ReleaseStringUTFChars(name,str_name);

      return ret;
  }
  • char* 封装为 jstring 对象
    同样的c\c++中的字符串也是不能直接转化为jstring返回给java程序的。对于字符串封装为jstring,jni为我们提供了线面几个常用的函数: 具体操作流程看下面的测试代码。
C++ : JNIEnv中将字符串封装成jstring的方法 描述
jstring NewStringUTF(const char *utf) 创建一个UTF-8格式的String对象
jstring NewString(const jchar *unicode, jsize len) 创建一个Unicode格式的String对象
jsize GetStringUTFLength(jstring str) 获取 UTF-8格式的char*的长度
jsize GetStringLength(jstring str) 获取Unicode格式的char*的长度

C语言中都是加上一个env参数。

测试代码 c ++:

JNIEXPORT jstring JNICALL Java_JNIDemo_sayGoodbye
  (JNIEnv *env, jclass cls, jstring name) {

      char* str_name=(char*) env->GetStringUTFChars(name,JNI_FALSE);

      printf("call sayGoodbye  %s\n",str_name);

      env->ReleaseStringUTFChars(name,str_name);

	  /* 使用 字符串构造 jstring对象 */
      jstring str =  env->NewStringUTF("hahahaha");

      return str;
  }

c. java对象的转换

  • java向c++传递类对象
JNIEXPORT jobject JNICALL Java_com_oracle_estt_sc_db_impl_SCQueryODBC__1getCustomer
(JNIEnv *env, jobject, jobject customer){

    jmethodID methodId; 
    //获得customer对象的句柄
    jclass cls_objClass=env->GetObjectClass(customer); 
    //获得customer对象中特定方法getName的id 
    methodId=env->GetMethodID(cls_objClass,"getName","()Ljava/lang/String;");
    //调用customer对象的特定方法getName
    jstring js_name=(jstring)env->CallObjectMethod(customer,methodId,NULL);

    ...
}
  • c++向java返回对象
JNIEXPORT jobject JNICALL Java_com_oracle_estt_sc_db_impl_SCQueryODBC__1getCustomer
(JNIEnv *env, jobject, jobject customer){

    ......

        //发现java Customer类,如果失败,程序返回
        jclass clazz = env->FindClass("com/oracle/estt/sc/busi/Customer"); 
    if(clazz == 0) 
        return 0; 
    //为新的java类对象obj分配内存 
    jobject obj = env->AllocObject(clazz); 
    //发现类中的属性,如果失败,程序返回 
    jfieldID fid_id = env->GetFieldID(clazz,"customerID","I"); 
    if (fid_id == 0) 
        return 0;
    jfieldID fid_name = env->GetFieldID(clazz,"name","Ljava/lang/String;"); 
    if (fid_name == 0) 
        return 0;
    ......

        env->SetIntField(obj, fid_id, 1
        env->SetObjectField(obj, fid_name, jname);

    ......

        return obj;

}

d. 对象数组的互相转换

  • java向 c++ 传递对象数组

对于这种情况,先得到数组的大小,接下来取出数组中的对象,取得对象的属性值或者调用对象的方法,将获得值存到本地数组中,然后可以灵活使用这些数 据了。举例说明:java向c传递一个含有多个customer对象的数组,在c中将这个数组的分解出来,存到本地的临时数组中去。

JNIEXPORT void JNICALL Java_com_oracle_estt_sc_db_impl_SCInsertODBC__1insertCustomeRequest___3Lcom_oracle_estt_sc_busi_CustomerRequest_2
(JNIEnv *env, jobject, jobjectArray oa){

    ...... 

        //声明customerrequest对象
        jobject o_customer;

    int i;
    jmethodID methodId; 
    jint size=env->GetArrayLength(oa);

    _tmp_bind[0]= (char *)malloc(size*sizeof(int));
    _tmp_bind[1]= (char *)malloc(size*sizeof(char)*( 20 + 1));

    ...

        //将输入数组的数据拷贝到临时数组中去
        for(i=0;i<size;i++){
            //从数组中获得customerrequest对象
            o_request=env->GetObjectArrayElement(oa,i);
            //获得customerrequest对象的句柄
            jclass cls_objClass=env->GetObjectClass(o_request);

            //获得customerrequest对象的特定方法getCustomerID的id
            methodId=env->GetMethodID(cls_objClass,"getCustomerID","()I");
            //调用customerrequest对象的特定方法getCustomerID
            int_customerID=env->CallIntMethod(o_request,methodId,NULL); 
            //获得customerrequest对象中特定方法getTelNum的id 
            methodId=env->GetMethodID(cls_objClass,"getTelNum","()Ljava/lang/String;");
            //调用customerrequest对象的特定方法getTelNum
            str_telNum=(jstring)env->CallObjectMethod(o_request,methodId,NULL); 

            ...

                //将用户id拷贝到临时数组
                memcpy(_tmp_bind[0]+i*sizeof(int),&int_customerID,sizeof(int));

            //将电话号码拷贝到临时数组,如果电话号码字符串超长,报错返回
            if(sizeof(char)*strlen(chr_tel)<=sizeof(char)*( 20 + 1)){
                memcpy(_tmp_bind[1]+i*sizeof(char)*( 20+1 ),chr_tel,strlen(chr_tel)+1);
            }else{
                printf("%s too long!\n",chr_tel);
                return;
            }

            ...

        }

        ...

}
  • c++ 向 java 返回对象数组

先创建数组,然后加载java对象,给每个java对象的属性赋值,添加到数组中,最后返回数组。如下例:

JNIEXPORT jobjectArray JNICALL Java_com_oracle_estt_sc_db_impl_SCQueryODBC__1getCustomerRequest
(JNIEnv *env, jobject, jint customerid){

    ......

        //声明存放查询结果的objectarray
        jobjectArray jo_array = env->NewObjectArray(MAX_LINE,env->FindClass("com/oracle/estt/sc/busi/CustomerRequest"), 0); jobject obj;
    //发现java Customerrequest类,如果失败,程序返回
    jclass clazz = env->FindClass("com/oracle/estt/sc/busi/CustomerRequest"); 
    if(clazz == 0) 
        return 0;

    while ((rc = SQLFetch(hstmt)) == SQL_SUCCESS ||rc == SQL_SUCCESS_WITH_INFO) {

        obj = env->AllocObject(clazz);

        jfieldID fid_customerID = env->GetFieldID(clazz,"customerID","I"); 
        if (fid_customerID == 0) 
            return 0;

        jfieldID fid_priority = env->GetFieldID(clazz,"priority","I"); 
        if (fid_priority == 0) 
            return 0;

        ...

            env->SetIntField(obj, fid_customerID, col_customerID);

        env->SetIntField(obj, fid_priority, col_priority);

        ...

            //将对象obj添加到object array中
            if(j<MAX_LINE){
                env->SetObjectArrayElement(jo_array, j, obj);
            }else{
                break;
            }

    }

    return jo_array;

}

7. 数据传递综合示例

  • java层
/* java jni 数据传递,综合测试 */

class Student {
  private String name;
  private int age;
  
  /* constructor */
  /* setter and getter */
  
  /* show method */
  public String show() {
      return "[ " + this.name + "," +this.age + " ]";
  }
}

public class JNIDemo {

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

  /* 1. 基本数据类型 , 计算两个数的和并返回 */
  public static native int add(int a,int b);

  /* 2. 基本数据类型数组,每一项对应相加,存入数组返回 */
  public static native int[] sum(int[] a, int[] b);

  /* 3. String -- char *  ,提取子串并返回 */
  public static native String subString(String src,int start,int len);

  /* 4. 对象传递 , 修改对象属性,构造新的对象并返回  */
  public static native Student changeStudentInfo(Student stu);

  /* 5. 对象数组 ,修改多个 */
  public static native Student[] changeAll(Student[] studets);

  public static void sop(Object o) {
    System.out.println("[ in java ] : " + o );
  }

  public static void main(String[] args) {
        sop("add(5 , 6) = " + add(5,6));

        int[] a = {1,2,3};
        int[] b = {3,2,1};
        int[] c =  sum(a,b);
        sop("sum( [1,2,3] , [3,2,1] ) = " + c[0] + ","+ c[1]+ ","+c[2]);

        sop("subString(abcdef,2,3) = " +  subString("abcdef",2,3));

        Student liming = new Student("liming",25);
        sop("Student(liming,25) ---> after change()  ..."  );
        Student ret = changeStudentInfo(liming);
        sop("before change ..." + ret.show());

        Student[]  students = { new Student("zhangsan",20) ,
                                new Student("lisi",21),
                                new Student("wangwu",20)};
  }
}
  • 编译java,生成jni所需要的头文件
	$shell javac JNIDemo.java    
    $shell javah -jni JNIDemo
    
   现在目录下有:
   JNIDemo.class  
   JNIDemo.h  
   JNIDemo.java  
   Student.class
  • 编写c程序(使用cpp)
    打开刚才生成的JNIDemo.h 可以看到所有native方法的定义。把这个头文件拷贝到c程序目录下。
======	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:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_JNIDemo_add
  (JNIEnv *, jclass, jint, jint);

/*
 * Class:     JNIDemo
 * Method:    sum
 * Signature: ([I[I)[I
 */
JNIEXPORT jintArray JNICALL Java_JNIDemo_sum
  (JNIEnv *, jclass, jintArray, jintArray);

/*
 * Class:     JNIDemo
 * Method:    subString
 * Signature: (Ljava/lang/String;II)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_JNIDemo_subString
  (JNIEnv *, jclass, jstring, jint, jint);

/*
 * Class:     JNIDemo
 * Method:    changeStudentInfo
 * Signature: (LStudent;)LStudent;
 */
JNIEXPORT jobject JNICALL Java_JNIDemo_changeStudentInfo
  (JNIEnv *, jclass, jobject);

/*
 * Class:     JNIDemo
 * Method:    changeAll
 * Signature: ([LStudent;)[LStudent;
 */
JNIEXPORT jobjectArray JNICALL Java_JNIDemo_changeAll
  (JNIEnv *, jclass, jobjectArray);

#ifdef __cplusplus
}
#endif
#endif   

然后在cpp中实现这些方法:
hello.cpp

/*
 *  hello.cpp jvav JNI 测试用 动态库源文件
 *           by codewang 2017.7.17 @heu
*/

#include <stdio.h>
#include <stdlib.h>

/*
 *  对动态库的一些封装和对java JNI库函数的支持
*/
#include <jni.h>
#include "JNIDemo.h"


/* 对于java中的int add(int int) */
JNIEXPORT jint JNICALL Java_JNIDemo_add
  (JNIEnv *env, jclass cls, jint a, jint b)
  {
      /* 基本数据类型,直接使用 */
      return a+b;
  }

/* 对应java中的 int[] sum(int[] ,int[]) */
JNIEXPORT jintArray JNICALL Java_JNIDemo_sum
  (JNIEnv *env, jclass cls, jintArray a, jintArray b)
  {
      jint *arr_a,*arr_b;
      jint len = env->GetArrayLength(a);
      printf("arr len = %d\n",len);
      // 注意,这里不释放会造成内存泄漏。
      // 一般我们在java层提供一个释放native方法,在cpp中释放内存
      jint* ret = new jint[len];

      arr_a = env->GetIntArrayElements(a,JNI_FALSE);
      arr_b = env->GetIntArrayElements(b,JNI_FALSE);

      for(int i=0;i<len;i++) {
          ret[i] = arr_a[i] + arr_b[i];
      }

      /* 构造 jintArray返回给java */
      jintArray jintarray = env->NewIntArray(len);
      env->SetIntArrayRegion(jintarray,0,len,ret);

      return jintarray;
  }

/* 对应java中的 String subString(String ,int ,int) */
JNIEXPORT jstring JNICALL Java_JNIDemo_subString
  (JNIEnv *env, jclass cls, jstring str, jint start, jint len)
  {
      /* 从jstring中获取字符串 */
      char *src = (char*)env->GetStringUTFChars(str,JNI_FALSE);
      /* 分离子串 */
      char* subStr = new char[len+1];

      for(jint i=start,j=0;j<len;i++,j++) {
           subStr[j] = src[i];
      }
      subStr[len] = '\0';

      /* 封装为jstring 返回 */
      jstring retString = env->NewStringUTF(subStr);

      /* 释放 */
      env->ReleaseStringUTFChars(str,src);
      return retString;
  }

/* 对应java中的 Student changeStudentInfo(Student ) */
JNIEXPORT jobject JNICALL Java_JNIDemo_changeStudentInfo
  (JNIEnv *env, jclass cls, jobject student)
  {
      /* 先获得类 */
      jclass classStudent = env->GetObjectClass(student);

      /* 获得方法 */
      jmethodID getName = env->GetMethodID(classStudent,
                                           "getName",
                                           "()Ljava/lang/String;");

      /* 执行方法 */
      jstring name = (jstring)env->CallObjectMethod(student,getName,NULL);
      char *src = (char*)env->GetStringUTFChars(name,JNI_FALSE);
      printf("int cpp  :  get name = %s\n",src);


      /* 返回新对象 */
      jobject newObject = env->AllocObject(classStudent);
      /* 可以通过调用方法设置属性,也可以直接设置 */

      jfieldID nameID = env->GetFieldID(classStudent,"name","Ljava/lang/String;");
      jfieldID ageID  = env->GetFieldID(classStudent,"age","I");

      jstring codewang = env->NewStringUTF("codewang");

      env->SetObjectField(newObject,nameID,codewang);
      env->SetIntField(newObject,ageID,22);

      return newObject;

  }


JNIEXPORT jobjectArray JNICALL Java_JNIDemo_changeAll
  (JNIEnv *, jclass, jobjectArray)
  {
      /* 和 intArray操作 一样,省略了。。。 */
  }
  • 编译动态库
	$shell   g++ -fPIC -shared -o libhello.so hello.cpp -I/usr/local/java/jdk1.8.0_131/include/  -I/usr/local/java/jdk1.8.0_131/include/linux 
    
    切记这里如果是cpp代码,一定要用g++,如果用gcc会出现找不到符号_Znam 错误。
    
  • 运行java测试
	$shell java -Djava.library.path=../cpp_src JNIDemo	
    运行结果:
    [ in java ] : add(5 , 6) = 11
	arr len = 3
	[ in java ] : sum( [1,2,3] , [3,2,1] ) = 4,4,4
	[ in java ] : subString(abcdef,2,3) = cde
	[ in java ] : Student(liming,25) ---> after change()  ...
	int cpp  :  get name = liming
	[ in java ] : before change ...[ codewang,22 ]

二、 使用JNI_OnLoad

相比于直接使用javah获得函数签名,实现本地方法的方式,使用JNI_OnLoad 方法更加灵活,在Android中也是使用这个方法来实现JNI应用到Android的硬件服务系统中。
在java代码中调用System.loadLibrary()或者System.load() 方法后,java虚拟机首先调用dlopen打开动态库,然后查询有没有JNI_OnLoad方法,如果没有就是用默认的最老JNI版本,如果有则先调用JNI_OnLoad方法,所以我们的JNI_OnLoad 方法至少要设置JNI版本。

下面以例子程序讲解JNI_Onload的使用过程:

/* 假设下面时在c中实现的本地方法,方法体忽略,我们只关注参数类型 */
jint func1(jint ,jint){}
jintArray func2(jIntArray){}
jstring func3(jstring){}
jobject func4(jobject){}
jObjectArray func5(jObjectArray){}

/* 定义方法映射的数组 */
/*
 *  关于这些参数符号表,怎么定义,实质上在我们用javah生成的头文件里有
 *  大概规则为: 
 *  1. (参数列表)返回值
 *  2. [ 代表数组
 *  3. L 代表对象
 *  4. 基本数据类型由一个字母代替,见下面的表
 *  5. String对象比较特殊,要用java/lang/String 表示
 *  6. 其他对象均被转化为Onject类
 *  我们再看看这个JNINativeMethod结构体如何定义的:
 *  typedef struct {
 *    char *name;        // java中对应的方法名
 *   char *signature;    // 参数返回值符号表 
 *   void *fnPtr;        // c中的对应的函数指针
 *  } JNINativeMethod;
*/
static const JNINativeMethod gMethods[] = {
    { "java_func1" , "(II)I"  , (void*)func1 },
    { "java_func2" , "([I)[I" , (void*)func2 },
    { "java_func3" , "(Ljava/lang/String)Ljava/lang/String" , (void*)func3 },
    { "java_func4" , "(LObject)Lobject" , (void*)func4 } ,
    { "java_func5" , "([LObject)[LObject" , (void*)func5}
}

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
	/* 在前面我们实验时,都知道第一个参数时一个env,如果没有这个函数,jvm默认给我们最
     *老版本的JNI ,但是有了初始化函数,我们可以选择JNI版本。	
    */
    JNIEnv * env;
    vm->GetEnv((void**)&env,JNI_VERSION_1_4);  // 获取版本1.4
    
    /* 注册方法 */
    /* 1. 注册方法,要注册到哪个类? */
    jclass clazz = env->FindClass("JNIDemo");
    /* 2. 注册到这个类 */
    env->RegisterNatives(clazz,gMethods,5);
    
    /* 可以想象一下:
     * 这中方法实质上和前面一种方法的区别就在于,改了一个env,那么我们在c程序中更改了env,JVM却不知道啊,
     * 所以我们要把选择的版本告诉JVM
    */
    return JNI_VERSION_1_4;
}


/*
*  除了Onload 之外 ,还有与之对应的OnUnLoad,它在动态库卸载之前被调用,可以联想下c的动态库连接技术
*  还有很多函数供我们使用,可以在jni.h中看到他们的定义。
*/

JNI 符号表

java类型 c中的类型 符号
Boolean jboolean Z
Byte jbyte B
Char jchar C
Short jshort S
Int jint I
Long jlong L
Float jfloat F
Double jdouble D
void jvoid V
String jstring Ljava/lang/String
对象 jobject 以"L"开头,以";"结尾,中间是用"/" 隔开的包及类名。如果是嵌套类,则用$来表示嵌套
数组 jXxxArray 用"["加上如此表所示的对应类型的简写形式