在java中使用和創建自定義的native方法

    JNI:Java Native Interface(Java本地接口)
    使用了JNI接口的JAVA程序,不再像以前那樣自由的跨平臺。因爲JNI底層是用C/C++實現的,後者是依賴操作平臺的,要實現跨平臺,就必須將本地代碼在不同的操作系統平臺下編譯出相應的動態庫。下面簡單介紹下JNI的開發流程。
從編寫到運行得到結果共需要執行6步:
   (1)編寫Java源代碼、(2)將Java源代碼編譯成class字節碼文件、(3)用javah命令生成.h頭文件、(4)用本地代碼實現.h頭文件中的函數、(5)將本地代碼編譯成動態庫、(6)運行Java程序

操作環境:Windows

1、編寫Java源代碼


package com.evan;
public class Hello{
 public static void main(String[] args){
  System.out.println("---------basic type test---------");
  System.out.println(""+5+"+"+8+"="+sum(5,8));
 
  System.out.println("---------String type test---------");
  if(args.length == 0){
   System.out.println("input the data");
   return;
  }
  String newString = addHeaderString(args[0]);
 
  System.out.println(newString);
  System.out.println("length="+newString.length());
 
  System.out.println("---------Array type test---------");
  int[] data = new int[10];
  System.out.println("primitive data is:");
  for(int ii=0;ii<data.length;ii++){
   data[ii] = ii;
   System.out.print(data[ii]+" ");
  }
  System.out.println();
  System.out.println("improved data is:");
  int[] result = improve(data,data.length);
  for(int ii=0;ii<result.length;ii++){
   System.out.print(result[ii]+",");
  }
  System.out.println();
  System.out.println("primitive data is:");
  for(int ii=0;ii<data.length;ii++){
   data[ii] = ii;
   System.out.print(data[ii]+" ");
  }
 }
 private static native String addHeaderString(String str);
 private static native int sum(int a,int b);
 private static native int[] improve(int[] data,int size);
 static{
  System.loadLibrary("Hello");
  /*when classload load the class input jvm,it will load the methods of Hello.dll into native method of jvm;
    this way suppport the input with absolute path.such as xx/xx/Hello.dll;
  */
 }
}


2、將Java源代碼編譯成class字節碼文件

操作:
javac Hello.java -d ./
備註:
-d指明瞭產生的Hello.class文件放置的位置;
結果:
得到./com/evan/Hello.class

3、用javah命令生成.h頭文件

操作:
javah -classpath ./ -d ./ com.evan.Hello
備註:
-classpath 指定class目標文件所在目錄, 程序將會從指定的目錄去尋找目標類,即最後的com.evan.Hello
結果:
得到com_evan_Hello.h文件

#include <jni.h>
/* Header for class com_evan_Hello */

#ifndef _Included_com_evan_Hello
#define _Included_com_evan_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_evan_Hello
 * Method:    addHeaderString
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_evan_Hello_addHeaderString
  (JNIEnv *, jclass, jstring);

/*
 * Class:     com_evan_Hello
 * Method:    sum
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_com_evan_Hello_sum
  (JNIEnv *, jclass, jint, jint);

/*
 * Class:     com_evan_Hello
 * Method:    improve
 * Signature: ([II)[I
 */
JNIEXPORT jintArray JNICALL Java_com_evan_Hello_improve
  (JNIEnv *, jclass, jintArray, jint);

#ifdef __cplusplus
}
#endif
#endif

4、用本地代碼實現.h頭文件中的函數

操作:
編寫一個c或者c++文件,這裏創建了一個Hello.c文件;

#include "com_evan_Hello.h"
#include <String.h>
JNIEXPORT jstring JNICALL Java_com_evan_Hello_addHeaderString
(JNIEnv *env, jclass jcl, jstring jstr)
{
 const char *cstr = (*env)->GetStringUTFChars(env,jstr,0);
 //獲取參數jstr的指針;之後通過cstr就可以訪問jstr中的數據;最後一個參數是isCopy?
 jsize size = (*env)->GetStringUTFLength(env,jstr);
 //獲取String數據的長度
 char buff[256] = {0}; //注意C語言必須要將所有的類型聲明放在函數最開始的位置!
 if(cstr==NULL){
  printf("out of memory \n");
 }
 (*env)->ReleaseStringUTFChars(env,jstr,cstr);
 //釋放本函數生成的指向jstr的指針,類似於將cstr置空
 //該方法與GetStringUTFChars方法成對出現
 sprintf(buff,"evan %s %d",cstr ,size); //這是一個C方法,用於打印字符串到有一個字符數組當中
 return (*env)->NewStringUTF(env,buff);
 //這裏是創建一個新的jstring對象,新對象的值由buff決定,長度是buff實際的值;即一般是小於256
 
}
JNIEXPORT jint JNICALL Java_com_evan_Hello_sum
  (JNIEnv * env, jclass jcl, jint a, jint b)
{
 return  a+b;
}


JNIEXPORT jintArray JNICALL Java_com_evan_Hello_improve
  (JNIEnv *env, jclass jcl, jintArray array,jint size)
{
 jint* intarray = (*env)->GetIntArrayElements(env,array,0);
 //本地獲取到一個訪問java堆中數組的指針
 jintArray data = (*env)->NewIntArray(env,size);
 //生成一個長度爲size的IntArray數組
 int index = 0;
 jint buff[100];
 int length=0;
 if(size<100) length=size;
 else length =100;
 if(data==NULL){
  printf("out of memory \n");
 }
 for(;index<length;index++){
  intarray[index]++;
  //這裏雖然改了這個指針所指向的數據,
  //但是在java堆中所存儲的數組並不會改變,即java類中定義的數組並不會發生變化
  buff[index] = 2+intarray[index];
 }
 (*env)->ReleaseIntArrayElements(env,array,intarray,0);
 //釋放intarray指針
 (*env)->SetIntArrayRegion(env,data, 0,length,buff);
 //將buff的數據複製到data中
 return data;
}

5、將本地代碼編譯成動態庫

關於動態庫的說明:
即將上面的Hello.c變成Hello.dll等動態庫文件;
windows對應動態庫爲*.dll ,linux/unix對應動態庫爲 *.so ,mac os x對應動態庫爲 *.jnilib 
下面以windows環境來說明如何實現xx.c-->xx.dll文件的轉變
操作:
cl -I %JAVA_HOME%\include -I %JAVA_HOME%\include\win32 -LD Hello.c -FeHello.dll
備註:
在cmd中輸入是沒有該命令的,推薦進入《VS2012 X64工具命令提示》輸入cl是有該命令的;
-I指定包含編譯的頭文件,所在的目錄
-LD 指定被編譯動態庫的目標文件
-Fe 指定生成的動態鏈接庫的文件名;千萬注意Fe和後面的名字不能有空格!!!
結果:
得到文件Hello,dll

6、運行Java程序

注意:運行java程序前需要確保java程序能夠找得到對應的dll文件
法一:
java -Djava.library.path=./ com.evan.Hello
備註:
-D設置環境變量java.library.path的值,因爲我們上面生成的Hello.dll放在當前目錄,所以我們就指定了java.library.path=./

法二:
將Hello.dll放入你的jdk安裝包的bin目錄下;
然後執行java com.evan.Hello;
補充:
你可以運行一下System.out.println(System.getProperty("java.library.path"));
然後從結果中選擇任何一個目錄放入你的Hello.dll文件,也能達到上面同樣的效果;

執行結果如下:
---------basic type test---------
5+8=13
---------String type test---------
evan hjk 3
length=10
---------Array type test---------
primitive data is:
0 1 2 3 4 5 6 7 8 9
improved data is:
3,4,5,6,7,8,9,10,11,12,
primitive data is:
0 1 2 3 4 5 6 7 8 9



補充:

native實現方法中常用的幾種數據處理
  • 基本數據 
    • 與平時的操作沒有太大的區別,只是在類型前加上一個j符號,如int變成jint; boolean變成jboolean
  • String類型數據 
    • 以函數參數(JNIEnv *env, jobject obj, jstring string)爲例
    • 獲得jstring的指針:const char* str = (*env)->GetStringUTFChars(env,string,0);
    • 釋放上面得到的指針(減少引用): (*env)->ReleaseStringUTFChars(env,string,str);
    • 獲得jstring的長度:jsize size =  (*env)->GetStringUTFLength(env,string);
    • 創建一個jstring數據(一般是作爲一個jstring返回值時才用):(*env)->NewStringUTF(env,string,buff); 
      • 上面的buff是我們在函數內部定義的形如 char buff[256]的一個對象;
      • 上面得到的jstring長度等於buff存儲的數據實際長度,不等於buff定義的長度(256);
      • 注意:return (*env)->NewStringUTF(env,string,buff); 不報錯;jstring  mstr =  (*env)->NewStringUTF(env,string,buff)報錯;
  • 數組 (下面以int數組爲例,其他數組直接將int換成對應的關鍵字即可)
    • 以函數參數(JNIEnv *env, jobject obj, jintArray array)爲例
    • 獲得jintArray的指針:jint* data = (*env)->GetIntArrayElements(env,array,0);
    • 釋放上面得到的指針(減少引用): (*env)->ReleaseIntArrayElements(env,array,data);
    • 獲得jintArray的長度:jsize size =  (*env)->GetIntArrayLength(env,array);
    • 創建一個jintArray數據(一般是作爲一個jintArray返回值時才用):jintArray data = (*env)->NewIntArray(env,100);  (與string數據不同之處)
      • 這裏開闢了一塊大小爲100的jintArray數組區間;
    • 對上面開闢的數據賦值:(*env)->SetIntArrayRegion(env, data,0,100,buff); (與string數據不同之處)
      • 上面的buff是我們在函數內部定義的形如 jint buff[256]的一個對象;
      •  括號中的0 100是限制data的 ,buff是從0開始
      • 上面語句的含義就是將buff 0-99的數據複製到 data的0-99位置
  • 對於其他類型如自定義object等,參考鏈接:http://hubingforever.blog.163.com/blog/static/17104057920115992032177/
更多方法的參數和返回值參考:XX/jdk/include/jni.h;xx爲的java jdk安裝目錄;




























發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章