在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源代码

<span style="font-size:14px;">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;
  */
 }
}
</span>

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安装目录;




























你可能感兴趣的:(jni,native,cc++,.dll)