锁定老帖子 主题:Java JNI 编程进阶
该帖已经被评为良好帖
|
|
---|---|
作者 | 正文 |
发表时间:2008-12-17
最后修改:2008-12-17
JNI一直以来都很少去关注,但却是我心中的一个结,最近这几天刚好手头有点时间,因此抽空看了一下这方面的东西,整理了一份文档,JNI技术的出现主要是基于三个方面的应用需求:
1. 解决性能问题
动态优化技术是提高Java性能的另一个尝试。该技术试图通过把Java源程序直接编译成机器码,以充分利用Java动态编译和静态编译技术来提高Java的性能。该方法把输入的Java源码或字节码转换为经过高度优化的可执行代码和动态库 (Windows中的. dll文件或Unix中的. so文件)。该技术能大大提高程序的性能,但却破坏了Java的可移植性。 JNI(Java Native Interface, Java本地化方法)技术由此闪亮登场。因为采用JNI技术只是针对一些严重影响Java性能的代码段,该部分可能只占源程序的极少部分,所以几乎可以不考虑该部分代码在主流平台之间移植的工作量。同时,也不必过分担心类型匹配问题,我们完全可以控制代码不出现这种错误。此外,也不必担心安全控制问题,因为Java安全模型已扩展为允许非系统类加载和调用本地方法。根据Java规范,从JDK 1. 2开始,FindClass将设法找到与当前的本地方法关联的类加载器。如果平台相关代码属于一个系统类,则无需涉及任何类加载器; 否则,将调用适当的类加载器来加载和链接已命名的类。换句话说,如果在Java程序中直接调用C/C++语言产生的机器码,该部分代码的安全性就由Java虚拟机控制。
2. 解决本机平台接口调用问题
3. 嵌入式开发应用
不失直观性,我们首先写一个JNI小例子:
public class HelloJni { public native void displayHelloJni(); static { System.loadLibrary("helloJni"); } public static void main(String[] args) { //System.out.println(System.getProperty("java.library.path")); new HelloJni().displayHelloJni(); } }
在class文件生成的相应目录执行命令如下:
得到C++文件HelloJni.h /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class HelloJni */ #ifndef _Included_HelloJni #define _Included_HelloJni #ifdef __cplusplus extern "C" { #endif /* * Class: HelloJni * Method: displayHelloJni * Signature: ()V */ JNIEXPORT void JNICALL Java_HelloJni_displayHelloJni (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif
JNI函数名称分为三部分:首先是Java关键字,供Java虚拟机识别;然后是调用者类名称(全限定的类名,其中用下划线代替名称分隔符);最后是对应的方法名称,各段名称之间用下划线分割。
编写C++文件HelloJni.h的实现类,我是比较常用VC6.0来生成dll文件(helloJni.dll)的 #include <jni.h> #include "HelloJni.h" #include <stdio.h> JNIEXPORT void JNICALL Java_HelloJni_displayHelloJni(JNIEnv *env, jobject obj) { printf("Hello Dynamic Link Library has been calling!\n"); printf("Java_HelloJni_displayHelloJni method has been executed!\n"); return; }
其实此时,我们的工程目前还暂时不能生成我们想要的 helloJni.dll 文件,问题就出在了“#include <jni.h>”。由于VC6.0里没有我们需要的“jni.h”文件,因此就需要手动加入到VC6.0的环境中去。在JAVA_HOME路径下我们可以找到include文件夹,其中就可以找到我们需要的“jni.h”文件。为了避免以后麻烦起见,将所有的C++文件全部拿出来,放在“%CPP_HOME%\VC98\Include”路径下。然后将工程进行打包就可以得到我们需要的“helloJni.dll”文件了。
将helloJni.dll文件放置于工程classes目录,执行命令如下: 运行结果如下:
接下来,对小例子进行重构:
package org.danlley.jni.test; public class BaseClass { public BaseClass(String arg) { loadLibrary(arg); } private static void loadLibrary(String arg) { System.loadLibrary(arg); } }
2. 定义新类继承基础类
package org.danlley.jni.test; public class HelloJniTest extends BaseClass { public HelloJniTest(String arg){ super(arg); } public native void displayHelloJni(); }
3. 编写调用类 package org.danlley.jni.test; public class RunMain { public static void main(String[] args) { new HelloJniTest("helloJniTest").displayHelloJni(); } } 此次,将dll文件定义为:helloJniTest.dll。
执行结果:
例子相当简单,没有传入参数,也没有返回值,那么是不是可以让本地方法返回一些参数,同时又可以传入数据进行处理,并把处理结果返回给方法的调用者呢,先拿基本类型开刀。接下来对 HelloJniTest 继续进行改造:新增两个本地方法,如下: package org.danlley.jni.test; public class HelloJniTest extends BaseClass { public HelloJniTest(String arg){ super(arg); } public native void displayHelloJni(); public native int getDynamicIntDataNoParam(); public native int getDynamicIntData(int i); }
重新生成org_danlley_jni_test_HelloJniTest.h文件,并改写其实现类org_danlley_jni_test_HelloJniTest.cpp如下: // org_danlley_jni_test_HelloJniTest.cpp: implementation of the org_danlley_jni_test_HelloJniTest class. // ////////////////////////////////////////////////////////////////////// #include "org_danlley_jni_test_HelloJniTest.h" #include <jni.h> #include <stdio.h> JNIEXPORT void JNICALL Java_org_danlley_jni_test_HelloJniTest_displayHelloJni(JNIEnv *env, jobject obj) { printf("Java_org_danlley_jni_test_HelloJniTest_displayHelloJni has been called!\n"); return; } JNIEXPORT jint JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicIntDataNoParam(JNIEnv *env, jobject obj) { return 65535; } JNIEXPORT jint JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicIntData(JNIEnv *env, jobject obj, jint i) { i*=i; return i; }
修改 RunMain 类: package org.danlley.jni.test; public class RunMain { public static void main(String[] args) { HelloJniTest tester=new HelloJniTest("helloJniTest"); tester.displayHelloJni(); int i=tester.getDynamicIntDataNoParam(); System.out.println("tester.getDynamicIntDataNoParam()="+i); int j=tester.getDynamicIntData(100); System.out.println("tester.getDynamicIntData(100)="+j); } }
运行RunMain: ----------------------------------------------------------------------- OK,一切正常。
还是不过瘾,简单对象可以处理了,如果是一个java对象,还可以处理吗,答案是当然可以,接下来我们来继续对 helloJniTest 类进行改造。新增一个方法如下: package org.danlley.jni.test; public class HelloJniTest extends BaseClass { public HelloJniTest(String arg){ super(arg); } public native void displayHelloJni(); public native int getDynamicIntDataNoParam(); public native int getDynamicIntData(int i); public native String getDynamicStringData(String arg); }
重新生成org_danlley_jni_test_HelloJniTest.h文件: /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class org_danlley_jni_test_HelloJniTest */ #ifndef _Included_org_danlley_jni_test_HelloJniTest #define _Included_org_danlley_jni_test_HelloJniTest #ifdef __cplusplus extern "C" { #endif /* * Class: org_danlley_jni_test_HelloJniTest * Method: displayHelloJni * Signature: ()V */ JNIEXPORT void JNICALL Java_org_danlley_jni_test_HelloJniTest_displayHelloJni (JNIEnv *, jobject); /* * Class: org_danlley_jni_test_HelloJniTest * Method: getDynamicIntDataNoParam * Signature: ()I */ JNIEXPORT jint JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicIntDataNoParam (JNIEnv *, jobject); /* * Class: org_danlley_jni_test_HelloJniTest * Method: getDynamicIntData * Signature: (I)I */ JNIEXPORT jint JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicIntData (JNIEnv *, jobject, jint); /* * Class: org_danlley_jni_test_HelloJniTest * Method: getDynamicStringData * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicStringData (JNIEnv *, jobject, jstring); #ifdef __cplusplus } #endif #endif
改写org_danlley_jni_test_HelloJniTest.cpp文件: // org_danlley_jni_test_HelloJniTest.cpp: implementation of the org_danlley_jni_test_HelloJniTest class. // ////////////////////////////////////////////////////////////////////// #include "org_danlley_jni_test_HelloJniTest.h" #include <jni.h> #include <stdio.h> JNIEXPORT void JNICALL Java_org_danlley_jni_test_HelloJniTest_displayHelloJni(JNIEnv *env, jobject obj) { printf("Java_org_danlley_jni_test_HelloJniTest_displayHelloJni has been called!\n"); return; } JNIEXPORT jint JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicIntDataNoParam(JNIEnv *env, jobject obj) { return 65535; } JNIEXPORT jint JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicIntData(JNIEnv *env, jobject obj, jint i) { i*=i; return i; } JNIEXPORT jstring JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicStringData (JNIEnv *env, jobject obj, jstring arg){ //Get the native string from javaString const char *nativeString = env->GetStringUTFChars(arg, 0); printf("%s", nativeString); //DON'T FORGET THIS LINE!!! env->ReleaseStringUTFChars(arg, nativeString); return arg; }
重新对C++工程打包成dll文件,运行结果: 我们不仅把Java的一个String对象成功的传给了dll,而且还将处理后的结果返回了出来。
但是总觉得还是不够,那我们就再来个比较复杂的对象把,我们这次将一个整形数组通过java传给dll,看看是不是也可以处理,继续还是对 helloJniTest 类进行改造,新增一个方法: package org.danlley.jni.test; public class HelloJniTest extends BaseClass { public HelloJniTest(String arg){ super(arg); } public native void displayHelloJni(); public native int getDynamicIntDataNoParam(); public native int getDynamicIntData(int i); public native String getDynamicStringData(String arg); public native int[] getDynamicArrayData(int[] args); }
重新生成org_danlley_jni_test_HelloJniTest.h文件 /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class org_danlley_jni_test_HelloJniTest */ #ifndef _Included_org_danlley_jni_test_HelloJniTest #define _Included_org_danlley_jni_test_HelloJniTest #ifdef __cplusplus extern "C" { #endif /* * Class: org_danlley_jni_test_HelloJniTest * Method: displayHelloJni * Signature: ()V */ JNIEXPORT void JNICALL Java_org_danlley_jni_test_HelloJniTest_displayHelloJni (JNIEnv *, jobject); /* * Class: org_danlley_jni_test_HelloJniTest * Method: getDynamicIntDataNoParam * Signature: ()I */ JNIEXPORT jint JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicIntDataNoParam (JNIEnv *, jobject); /* * Class: org_danlley_jni_test_HelloJniTest * Method: getDynamicIntData * Signature: (I)I */ JNIEXPORT jint JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicIntData (JNIEnv *, jobject, jint); /* * Class: org_danlley_jni_test_HelloJniTest * Method: getDynamicStringData * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicStringData (JNIEnv *, jobject, jstring); /* * Class: org_danlley_jni_test_HelloJniTest * Method: getDynamicArrayData * Signature: ([I)[I */ JNIEXPORT jintArray JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicArrayData (JNIEnv *, jobject, jintArray); #ifdef __cplusplus } #endif #endif
改写org_danlley_jni_test_HelloJniTest.cpp文件: // org_danlley_jni_test_HelloJniTest.cpp: implementation of the org_danlley_jni_test_HelloJniTest class. // ////////////////////////////////////////////////////////////////////// #include "org_danlley_jni_test_HelloJniTest.h" #include <jni.h> #include <stdio.h> JNIEXPORT void JNICALL Java_org_danlley_jni_test_HelloJniTest_displayHelloJni(JNIEnv *env, jobject obj) { printf("Java_org_danlley_jni_test_HelloJniTest_displayHelloJni has been called!\n"); return; } JNIEXPORT jint JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicIntDataNoParam(JNIEnv *env, jobject obj) { return 65535; } JNIEXPORT jint JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicIntData(JNIEnv *env, jobject obj, jint i) { i*=i; return i; } JNIEXPORT jstring JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicStringData (JNIEnv *env, jobject obj, jstring arg){ //Get the native string from javaString const char *nativeString = env->GetStringUTFChars(arg, 0); printf("%s", nativeString); //DON'T FORGET THIS LINE!!! env->ReleaseStringUTFChars(arg, nativeString); return arg; } JNIEXPORT jintArray JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicArrayData (JNIEnv *env, jobject obj, jintArray args){ jint buf[10]; jint i; env->GetIntArrayRegion(args, 0, 10, buf); jint j=0; for (i = 0; i < 10; i++) { j=buf[i]; j*=j; buf[i]=j; } env->SetIntArrayRegion(args, 0, 10, buf); return args; }
改写RunMain: package org.danlley.jni.test; public class RunMain { public static void main(String[] args) { HelloJniTest tester = new HelloJniTest("helloJniTest"); tester.displayHelloJni(); int i = tester.getDynamicIntDataNoParam(); System.out.println("tester.getDynamicIntDataNoParam()=" + i); int j = tester.getDynamicIntData(100); System.out.println("tester.getDynamicIntData(100)=" + j); String str = tester.getDynamicStringData("My first String test"); System.out.println("tester.getDynamicStringData=" + str); int[] args_int = new int[10]; for (int ii = 0; ii < 10; ii++) { args_int[ii] = ii; } int[] args_arr = tester.getDynamicArrayData(args_int); for (int ii = 0; ii < 10; ii++) { System.out.println(args_arr[ii]); } } }
运行结果:
参考资料: http://en.wikipedia.org/wiki/Java_Native_Interface
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2008-12-17
好文,研究JNI的的确很少,但有时有确实要用的!
|
|
返回顶楼 | |
发表时间:2008-12-17
JNI需要特别注意内存泄露,基础接口里有对应的释放方法
|
|
返回顶楼 | |
发表时间:2008-12-17
JNI 基本就是用一种很搓的方法去实现native dll的wrapper.
|
|
返回顶楼 | |
发表时间:2008-12-18
最后修改:2008-12-18
呵呵,如果C不会,可以用Delphi
同样可以达到效果. 可惜不支持数组及一些对象,如果这些也支持就太好了. |
|
返回顶楼 | |
发表时间:2008-12-19
最后修改:2008-12-19
楼上的兄台,为何说不支持数组和一些对象,支持的挺好的啊。个人感觉JNI还是很不错的。
前段时间遇到这样一问题,就是客户端应用程序通过Com Server,调用Java类中的方法,并传一个字节数组的问题,折腾了1整天,终于搞定了,其间遇到一些问题,还是在sun的jni论坛板块找到解决方案的。 |
|
返回顶楼 | |
发表时间:2008-12-19
最后修改:2008-12-19
最后一个例子应该是针对数组的操作,楼上的楼上可以参考一下哦!
|
|
返回顶楼 | |
发表时间:2008-12-19
如果用vc 编译动态库 实现 jni 就失去跨平台特性了,
我是用 mingw +cdt 或者 mingw+netbeans 编译 动态库, 这样可以做到一套c代码,windows linux下都能跑, 但是linux下动态库是so文件,windows下是 dll文件,所以算是半个跨平台把. |
|
返回顶楼 | |
发表时间:2008-12-19
xieke 写道 如果用vc 编译动态库 实现 jni 就失去跨平台特性了,
我是用 mingw +cdt 或者 mingw+netbeans 编译 动态库, 这样可以做到一套c代码,windows linux下都能跑, 但是linux下动态库是so文件,windows下是 dll文件,所以算是半个跨平台把. 这位兄台,能不能说说你的的具体做法呢? |
|
返回顶楼 | |
发表时间:2008-12-19
xieke 写道 如果用vc 编译动态库 实现 jni 就失去跨平台特性了,
我是用 mingw +cdt 或者 mingw+netbeans 编译 动态库, 这样可以做到一套c代码,windows linux下都能跑, 但是linux下动态库是so文件,windows下是 dll文件,所以算是半个跨平台把. 那你怎么编译的呢? 不是vc或者其他东东编译dll然后gcc编译so? |
|
返回顶楼 | |