在java中使用和创建自定义的native方法

来源:互联网 发布:巴西经济危机 知乎 编辑:程序博客网 时间:2024/04/30 05:35
    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 __cplusplusextern "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 3length=10---------Array type test---------primitive data is:0 1 2 3 4 5 6 7 8 9improved 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安装目录;




























1 0
原创粉丝点击