【再探JNI】

  一次了解了一点JNI,然后不甘心的找到了JNI的官方文档。(官方文档绝对是一个最好的学习资料),百度找出来的一些资料大多数是比较零零碎碎的,不具有系统学习的可能,对于我这样的初学者,先全面的了解一个技术比往一个死角里钻研要好很多。并且百度出来的部分资料估计就是跟我这样的半吊子水平还不到的人的一些心得体会呢。因此,个人建议是看官方文档去全面了解一项技术,然后不理解的地方去再去搜集资料。加上自己的理解和实践,这样会进步的快一点。

  好了,闲话少说。进入今天的真题。《再探JNI》

  (1)什么时候用JNI

  (2)JNI之HelloWorld

  (3)再议JNI的参数

  这里的开始还是要啰嗦一下上一篇说过的一点。因为缺乏JAVA基础,因此这两天看了一部分JAVA基础的资料。同时在JNI官方文档中也提到了一个知识点:java平台是有java VM 和java API 组成。而这个平台的开发也就是为了使得java应用程序具有可移植性。那么当一个java平台部署到某一个特定的主机平台时,比如我们常用的windows版的java平台。那么java应用程序和windows本身打交道就是很正常的事了。所以这时候伟大的JNI诞生了。

  

  这是官方文档的一张图。可以说很直接了当的明白了各个层的关系。同时要注意java并不是单纯的一个接口。更应该理解成一套成文的使用规则。具体规则也还在研究中,但是一开始说了,这时候我并不在于怎么去扣细节。先看整体的。相信以后工作中遇到某个功能肯定会搞定的。到目前,JNI已经具有很完善的功能了。

   说了这些JNI的好处,那么,只要和主机平台有信息交互是不是就应该使用JNI呢? 答案肯定不是。

  1、如果java代码和一个弱类型的语言一起使用了,那么java代码安全性就降低了

  2、Java类中的所有方法和属性对JNI都是可见的,那么丧失了java的封装性。

  3、JNI使得两者代码在同一个进程中,程序稳定性存在隐患

因此除非必须要和在用一个进程下本地代码进行交互,那么尽量不用JNI。有一个一般性原则是native methods尽量定义在尽可能少的类里。

  按常理出牌,这里写一个HelloWorld的JNI调用。

JAVA代码

class HelloWorld {
	private native void print();//声明native方法,

	public static void main(String[] args) {
		new HelloWorld().print();//调用这个print方法
	}	

	static {
	System.loadLibrary("HelloWorld");//系统调用,运行且只运行一次的静态方法。去加载一个名字为“HelloWorld”的动态库
	}
}

  “在Java代码中声明本地方法必须有"native"标识符,native修饰的方法,在Java代码中只作为声明存在。在调用本地方法前,必须首先装载含有该方法的本地库. 如HelloWorld.java中所示,置于static块中,在Java VM初始化一个类时,首先执行这部分代码,这可保证调用本地方法前,装载了本地库。 ”所引用的这段话非常清晰的讲清楚了这个java类的执行。这也就是一个JNI工作起来的样子。

将java先生成HelloWorld.class---------------javac HelloWorld.java

HelloWorld.class生成HelloWorld.h-------------javah -jni HelloWorld

这里也将.h文件也给贴出来

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */

#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloWorld
 * Method:    print
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloWorld_print
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

 HelloWorld.h写相应的.C文件

C代码(也就是需要编译成dll的,在文末补充一下编译dll。)

#include <jni.h>
#include <stdio.h>
#include "HelloWorld.h"

JNIEXPORT void JNICALL
Java_HelloWorld_print(JNIEnv *env, jobject obj)
{
    printf("Hello World!
");
    return;
}

 

 然后要做的也就是将下面的C代码编译成dll库,将生成的dll拷贝到java目录,运行的时候就会打印出Helloworld!。这样也就是一个JNI的最基本实现。

细心的朋友也许会发现,这个生成的函数声明,两个参数都没有用到,怎么分析?

那再来一个例子

//java代码

//Array.java
class Array { private static native int[][] initArray(int size); public static void main(String[] args) { int [][] i2arr = initArray(3); for(int i = 0; i<3;i++) { for(int j = 0; j<3;j++) { System.out.print(" " + i2arr[i][j]); } System.out.println(); } } static { System.loadLibrary("ObjectArrayTest"); } }

 

C代码

#include <jni.h>
#include <stdio.h>
#include "Array.h"

JNIEXPORT jint JNICALL Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr) {   jint buf[10];   jint i, sum = 0;   (*env)->GetIntArrayRegion(env, arr, 0, 10, buf);   for (i = 0; i < 10; i++)
  {     sum += buf[i];   }   return sum; }

   参数解释:JNIEnv *env,  jobject obj

  至于第二个例子中的第三个参数大家先不考虑。这是JNI的一种数据类型,第二个例子作用就是初始化二维数组,其中用到的GetIntArrayRegion这样的函数也先不关心。先来看第二个例子中的(*env),通过他可以取出GetIntArrayRegion,那么编码的时候也可以看到自动提示是会取出别的东西。那么可以知道这一定是一个结构。在文档中有一个这样的图

  

所以第一个参数是JNI的一个灵魂它其中有一个接口函数的函数表,也就是说要使用JNI的标准规范,就一定要有这个参数,那么也一定要有jni.h这个头文件。这个文件也肯定在C:Program FilesJavajdkxxxxx里面,第二个参数jobject类型,代码中还是没有体现具体用处,(代码写的太少,同志仍需努力)。第二个对象相当于对象本身,才C++里面的this。但如果这个本地方法声明成一个静态方法时,第二个参数就是对象本身了。

  补充:dll大家可以用命令行编译,反正我嫌麻烦,直接用的VS2013编的。新建一个项目-- 添加一个.c文件--写代码--项目属性更改配置属性中的配置类型为动态库dll,然后更改包含目录,将jdk目录包含进来,还有生成的.h文件也包含进来,要么自己拷贝到vs工程的当前目录。值得注意的是如果是64位的java环境,那么编译dll的时候记得在配置管理器中改成X64再生成dll。不然默认情况下是生成32位的。那样是加载不进去的。  

  最近也兼顾着在琢磨android。关于JNI的第二个参数的具体例子和一些接口函数与数据类型,下篇再具体描述。共勉,晚安!

原文地址:https://www.cnblogs.com/ZrBlog/p/4388873.html