注意我就是错在这个些包的名字上,导致调用失败
1,在项目根目录下建立文件夹libs/armeabi文件夹
2,将so库放入libs/armeabi文件夹注意事项:
1,如果采用静态注册的方式请注意C文件中严格按照命名规则Java_packageName_className_method()的方式命名
2,在Android项目中建立同上述命名规则中packageName中相同的包名,在此包名下建立同上述命名规则中className相同的类名
3,在className声明native方法
4,程序中加载so库System.loadLibrary(data/data/xxx.xxx.xxx/lib/xx.so)或者System.loadLibrary(xx),
例如:System.loadLibrary(data/data/com.dtBank.app.service/lib/libjnixcld.so);
一、jni简介:
JNI一直以来都很少去关注,但却是我心中的一个结,最近这几天刚好手头有点时间,因此抽空看了一下这方面的东西,整理了一份文档,JNI技术的出现主要是基于三个方面的应用需求:
1. 解决性能问题
Java具有平台无关性,这使人们在开发企业级应用的时候总是把它作为主要候选方案之一,但是性能方面的因素又大大削弱了它的竞争力。为此,提高Java的性能就显得十分重要。Sun公司及Java的支持者们为提高Java的运行速度已经做出了许多努力,其中大多数集中在程序设计的方法和模式选择方面。由于算法和设计模式的优化是通用的,对Java有效的优化算法和设计模式,对其他编译语言也基本同样适用,因此不能从根本上改变Java程序与编译型语言在执行效率方面的差异。由此,于是人们开始引入JIT(Just In Time,及时编译)的概念。它的基本原理是:首先通过Java编译器把Java源代码编译成平台无关的二进制字节码。然后在Java程序真正执行之前,系统通过JIT编译器把Java的字节码编译为本地化机器码。最后,系统执行本地化机器码,节省了对字节码进行解释的时间。这样做的优点是大大提高了Java程序的性能,缩短了加载程序的时间;同时,由于编译的结果并不在程序运行间保存,因此也节约了存储空间。缺点是由于JIT编译器对所有的代码都想优化,因此同样也占用了很多时间。
动态优化技术是提高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. 解决本机平台接口调用问题
JAVA以其跨平台的特性深受人们喜爱,而又正由于它的跨平台的目的,使得它和本地机器的各种内部联系变得很少,约束了它的功能。解决JAVA对本地操作的一种方法就是JNI。JAVA通过JNI调用本地方法,而本地方法是以库文件的形式存放的(在WINDOWS平台上是DLL文件形式,在UNIX机器上是SO文件形式)。通过调用本地的库文件的内部方法,使JAVA可以实现和本地机器的紧密联系,调用系统级的各接口方法。
3. 嵌入式开发应用
“一次编程,到处使用”的Java软件概念原本就是针对网上嵌入式小设备提出的,几经周折,目前SUN公司已推出了J2ME(Java 2 P1atform Micro Edition)针对信息家电的Java版本,其技术日趋成熟,开始投入使用。SUN公司Java虚拟机(JVM)技术的有序开放,使得Java软件真正实现跨平台运行,即Java应用小程序能够在带有JVM的任何硬软件系统上执行。加上Java语言本身所具有的安全性、可靠性和可移植性等特点,对实现瘦身上网的信息家电等网络设备十分有利,同时对嵌入式设备特别是上网设备软件编程技术产生了很大的影响。也正是由于JNI解决了本机平台接口调用问题,于是JNI在嵌入式开发领域也是如火如荼。
不失直观性,我们首先写一个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文件生成的相应目录执行命令如下:
----------------------------------------------------
E:\projects\jni\target\classes>javah HelloJni
----------------------------------------------------
得到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虚拟机识别;然后是调用者类名称(全限定的类名,其中用下划线代替名称分隔符);最后是对应的方法名称,各段名称之间用下划线分割。
JNI函数的参数也由三部分组成:首先是JNIEnv *,是一个指向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目录,执行命令如下:
-----------------------------------------------
E:\projects\jni\target\classes>java HelloJni
-----------------------------------------------
运行结果如下:
-----------------------------------------------------------------
Hello Dynamic Link Library has been calling!
Java_HelloJni_displayHelloJni method has been executed!
-----------------------------------------------------------------
但是要想在eclipse中运行helloJni.dll文件,就需要将文件拷贝到工程的根目录,或者将其放在诸如C:\WINDOWS\system32;C:\WINDOWS;等目录下。因为,eclipse在运行helloJni.dll文件时首先会去在当前根目录找,如果找不到则在path上去找,因此你还可以为了方便管理生成的dll文件,将所有工程中的dll文件都放到一个特定的目录,然后将该目录加入到你的本地path环境变量中去,这样每次只需要将生成的dll文件放入path目录下就可以访问了。注,如果需要加环境变量最好在加好以后重新启动一下eclipse,确保eclipse能够加载到最新的path环境。
接下来,对小例子进行重构:
1. 新增一个基础类
- 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。
执行结果:
------------------------------------------------------------------------------------
Java_org_danlley_jni_test_HelloJniTest_displayHelloJni has been called!
------------------------------------------------------------------------------------
例子相当简单,没有传入参数,也没有返回值,那么是不是可以让本地方法返回一些参数,同时又可以传入数据进行处理,并把处理结果返回给方法的调用者呢,先拿基本类型开刀。接下来对 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:
-----------------------------------------------------------------------
tester.getDynamicIntDataNoParam()=65535
tester.getDynamicIntData(100)=10000
Java_org_danlley_jni_test_HelloJniTest_displayHelloJni has been called!
-----------------------------------------------------------------------
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文件,运行结果:
---------------------------------------------------------------------------
tester.getDynamicIntDataNoParam()=65535
tester.getDynamicIntData(100)=10000
tester.getDynamicStringData=My first String test
Java_org_danlley_jni_test_HelloJniTest_displayHelloJni has been called!
My first String test
---------------------------------------------------------------------------
我们不仅把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]);
- }
- }
- }
运行结果:
--------------------------------------------------------------------------------
tester.getDynamicIntDataNoParam()=65535
tester.getDynamicIntData(100)=10000
tester.getDynamicStringData=My first String test
0
1
4
9
16
25
36
49
64
81
Java_org_danlley_jni_test_HelloJniTest_displayHelloJni has been called!
My first String test
--------------------------------------------------------------------------------
参考资料:
http://en.wikipedia.org/wiki/Java_Native_Interface
http://www.yesky.com/20011004/199789.shtml
http://www.21ic.com/news/html/63/show13260.htm
http://java.ccidnet.com/art/297/20060228/439729_1.html
http://www.blogjava.net/soft/archive/2006/11/13/posoft.html
http://www.blogjava.net/soft/archive/2006/11/13/80788.html
http://www.blogjava.net/soft/archive/2006/11/13/80789.html
原文链接:http://www.iteye.com/topic/295776
二、java c/cpp互相调用实例(姊妹篇之一) ----------java调用c/cpp
此文章纯粹实例操作,关于jni已经有不少人不少文章讲的很清楚了,所以就不罗列理论了。
看了kimmking的 JNI技术实践小结,又读了danlley的 Java JNI 编程进阶,这些文章中都是自己调用自己,方法很java化,而对于真正的调用dll还是不理解,心中一直存在问题:现在系统中已经有的dll我该如何去调用?如果一个大工程里需要cpp和java一起开发,cpp给出接口、SDK,我该如何处理?
带着这些疑问我决定从cpp到java的jni调用这个全过程亲自动手操作一下。
完成此示例需要下列工具/环境:
1、java环境(废话谁都知道)
2、编译c/cpp的工具。推荐用vs/vc++,我用的是vs2008
一 先制作一个系统中有的DLL文件(cpp给出的sdk接口)
既然是测试我们就把我们这个dll叫做testDll吧,为了简单其间,我只写一个add方法,就是简单的2个数字相加,对于真正的开发中我们肯定会遇到其他类型,java到c/cpp中类型需要转换,具体类型转换对应关系g一下就能得到,我也不在列举。c/cpp中一个class一般包含2个文件,一个头文件定义(*.h),一个文件主体(*.c/*.cpp)。啰嗦了这么多还是直接动手吧,先在vs2008中建立一个工程(当然你也可以直接编写不用这些IDE工具,gcc g++的命令自己g。下同,不在注释不在废话),选取win32工程

键入工程名字testDll,点击next选取DLL,然后点击完成

打开我们的testdll.cpp,添加进我们的add方法
- int add(int a,int b){
- return a+b;
- }
注意到文件列表里并没有testDll.h,因为我们要给出调用者一个接口,如果不给头文件,人家就没办法调用,所以我们就必须添加一个头文件testDll.h。
- #ifdef TEST_DLL
- #define TEST_API __declspec(dllexport)
- #else
- #define TEST_API __declspec(dllimport)
- #endif
- /* Set up for C function definitions, even when using C++ */
- #ifdef __cplusplus
- extern "C" {
- #endif
- TEST_API int add(int,int);
- /* Ends C function definitions when using C++ */
- #ifdef __cplusplus
- }
- #endif
按道理说我们的这个dll已经完成了,但是一般c/cpp给接口SDK的时候大都给.h和.lib,为了一步生成dll和lib,我们添加进一个testDll.def,有了这个文件就可以一步生成dll和lib。在source file里右键add new item ,选择Module-Definition File

键入testDll,OK了,我们可以直接build了。生成testDll.dll和testDll.lib。
把testDll.dll扔到system32目录里等待我们高大威猛的java jni调用。
二 JNI
2.1 编写java文件
为了显示我们的与众相同,我们就把我们的这个java文件命名为Demo.java顺便直接带上包名
,因为我们知道人家给我们的接口里有个add方法,所以我们就直接来个调用吧。
- package com.testJni.testDemo;
- public class Demo {
- static
- {
- //System.out.println(System.getProperty("java.library.path"));
- System.loadLibrary("testDll");
- System.loadLibrary("jniDll");
- }
- public native static int add(int a,int b);
- }
2.2 生成.h头文件
javah命令,不多讲。生成的文件com_testJni_testDemo_Demo.h这个文件的命名规则我就不多讲了,一目了然。
- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include <jni.h>
- /* Header for class com_testJni_testDemo_Demo */
- #ifndef _Included_com_testJni_testDemo_Demo
- #define _Included_com_testJni_testDemo_Demo
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * Class: com_testJni_testDemo_Demo
- * Method: add
- * Signature: (II)I
- */
- JNIEXPORT jint JNICALL Java_com_testJni_testDemo_Demo_add
- (JNIEnv *, jclass, jint, jint);
- #ifdef __cplusplus
- }
- #endif
- #endif
2.3 用c/cpp实现这个头文件
c/cpp中已经实现了这个add方法,我们只需要调用就可以啦。所以直接vs2008中建立一个dll工程,工程名我们就叫jniDll,具体过程不再多讲,方法同上面testDll的建立一样。在这个工程里kimmking把需要引用的包、文件等已经讲的很清楚了。打开jniDll.cpp,添加下面代码
- JNIEXPORT jint JNICALL Java_com_testJni_testDemo_Demo_add
- (JNIEnv *env,jclass jobject,jint a,jint b){
- return add(a,b);
- }
这个工程里我们还需要打开 stdafx.h添加
- #include <jni.h>
- #include "testDll.h"
- #include "com_testJni_testDemo_Demo.h"
在编译这个jniDll工程的时候需要引入testDll.h,com_testJni_testDemo_Demo.h,另外添加testDll.lib这个依赖。

好了做好这些后,build下,生成了我们期待已久的jniDll.dll,把这个dll同样扔到system32下。
三 测试
本人特懒,不想写多余的class,所以直接修改Demo.java 这也是刚才为什么讲暂时如此的原因
- package com.testJni.testDemo;
- public class Demo {
- static
- {
- //System.out.println(System.getProperty("java.library.path"));
- System.loadLibrary("testDll");
- System.loadLibrary("jniDll");
- }
- public native static int add(int a,int b);
- public static void main(String[] args) {
- System.out.println(add(7,2));
- }
- }
四 最后补充
如果系统已经加载过c/cpp的dll,我们就不用再System.loadLibrary("testDll")了,加载一遍就可以了,因为我们刚才写的testDll系统没有加载,所以我就加载了一下。对于多个dll可以写多个System.loadLibrary去加载,修改static{}里面的内容不需要重新生成dll,除非你多加了一个调用方法,如果你看清楚规则,就不用javah命令就可以直接编写头文件,用javah太麻烦了。
【参考文章】
http://www.iteye.com/topic/304594 JNI技术实践小结--原理分析和详细步骤截图说明
http://www.iteye.com/topic/295776?page=1 Java JNI 编程进阶
原文链接:http://www.iteye.com/topic/459005
三、java c/cpp互相调用实例(姊妹篇之二)
本文是 javaJNI调用c/cpp实例(姊妹篇之一)的续集c/cpp调用java。
计划第三篇写一个java安装程序实例(客户端无jre环境的安装包),以解决java程序(软件)安装不方便的问题,使java程序安装也傻瓜化。
直接进入正题:
完成本实例需要下列工具/环境:
1、java环境
2、c/cpp编辑器。windows下推荐用vs/vc++,我用的是vs2008。linux下gcc/g++
从 C/CPP 程序调用 Java 代码需要四个步骤 :
一 编写 Java 代码。
二 编译 Java 代码。
三 编写 C/C++ 代码。
四 运行本机 C/C++ 应用程序。
1、编写java代码
为了达到示范作用,java方法我用两个,一个是静态方法,一个是普通方法。
C2java.java
- package com.testJni.testDemo;
- public class C2java {
- public C2java(){
- super();
- }
- public static int add(int a,int b) {
- return a+b;
- }
- public boolean judge(boolean bool) {
- return !bool;
- }
- }
静态方法的好处是我不用实例化,直接可以调用方法。调用起来比较简单,不容易出错。
2、编译java代码
javac 命令。(略)
3、编写 C/C++ 代码
我想在c/cpp中直接生成一个exe然后窗口输出结果,所以我就建立一个exe工程。编辑器jni环境是上篇已经搭建好的,所以这里只需要少量配置就可以了。好了,我们先建立一个工程:
打开vs2008,新建一 win32 console App 工程

键入工程名字c2java,点击OK,出来窗口点击next,选取console app

点击完成。到这里先不忙编码实现,我们先把环境搭建好,右键工程属性,选取 linker -->input,在右边窗口添加依赖jvm.lib,这个lib的位置在你%JAVA_HOME%/lib 下。如果你的路径中同我一样包含空格(例如Program Files)记得用引号括起来。

打开stdafx.h文件添加
- #include <iostream>
- #include <jni.h>
- #ifdef _WIN32
- #define PATH_SEPARATOR ';'
- #else
- #define PATH_SEPARATOR ':'
- #endif
打开c2java.cpp,键入下面的代码
- using namespace std;
- int main()
- {
- JavaVMOption options[1];
- JNIEnv *env;
- JavaVM *jvm;
- JavaVMInitArgs vm_args;
- long status;
- jclass cls;
- jmethodID mid;
- jint square;
- jboolean not;
- jobject jobj;
- options[0].optionString = "-Djava.class.path=.";
- vm_args.version = JNI_VERSION_1_2;
- vm_args.nOptions = 1;
- vm_args.options = options;
- status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
- if (status != JNI_ERR)
- {
- cls = env->FindClass("com/testJni/testDemo/C2java");
- if(cls !=0)
- {
- mid = env->GetStaticMethodID( cls, "add", "(II)I");
- if(mid !=0)
- {
- square = env->CallStaticIntMethod( cls, mid, 5,5);
- std::cout << square << std::endl;
- }
- mid = env->GetMethodID(cls,"<init>","()V");
- if(mid !=0)
- {
- jobj=env->NewObject(cls,mid);
- std::cout << "init ok" << std::endl;
- }
- mid = env->GetMethodID( cls, "judge","(Z)Z");
- if(mid !=0)
- {
- not = env->CallBooleanMethod(jobj, mid, 1);
- if(!not){
- std::cout << "Boolean ok" << std::endl;
- }
- }
- }
- jvm->DestroyJavaVM();
- return 0;
- }
- else
- return -1;
- }
下面解释下上面的代码:
JavaVMOption options[] 具有用于 JVM 的各种选项设置。声明的 JavaVMOption options[] 数组足够大,就能容纳我们希望使用的所有选项。在本实例中,我们使用的选项就是类路径选项。
JNIEnv *env 表示 JNI 执行环境。
JavaVM *jvm 是指向 JVM 的指针。我们主要使用这个指针来创建、初始化和销毁 JVM。JavaVMInitArgs vm_args 表示可以用来初始化 JVM 的各种 JVM 参数。
设置参数后,创建我们的jvm :
status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
成功返回0,不成功返回JNI_ERR。
创建完成后,我们就可以查找我们的class了,因为我们的java类一般都有包,所以我们也要加上包路径com/testJni/testDemo/C2java
在这里我们会使用到java的一个命令javap ,这个命令有什么用那,我们用javap -s -p C2java看看

打开jni.h我们会发现,Signature就是sig,也就是GetStaticMethodID( cls, "add", "(II)I")方法的第三个参数。GetStaticMethodID表示调用static方法,GetMethodID调用普通方法。下面就是传入参数,打出结果。
在看jni.h的时候我们注意到有 CallStaticXXXMethod() 和 CallXXXMethod() 之类的方法。这些方法分别代表调用静态方法和成员方法,用方法的返回类型(例如,Object、Boolean、Byte、Char、Int、Long 等等)代替变量 XXX。
静态方法和普通方法不同之处就是普通方法必须要先实例化一个java对象,调用构造器的时候方法的名称为“<init>”。
下面的代码就不用我再解释了,先是new一个实例出来,然后调用实例的方法。
最后记得销毁jvm。
代码解释完了,我们build下这个工程,生成c2java.exe。
4、运行exe
因为我们生成的exe需要调用jvm.dll初始化,为了使运行的exe不报错误,我们把%JAVA_HOME%/jre/bin/server也加进path目录。方便系统自动搜索jvm.dll。
运行结果:

最后补充:本实例并没有涉及到java的异常、java c/cpp的编码转换问题,对于异常问题,C里没有异常,请使用jni的异常处理函数。编码转换问题上篇已有介绍,此处略去。
五 、Linux 下C通过jni调用java
编译环境:
fedora16
gcc (GCC) 4.6.3 20120306 (Red Hat 4.6.3-2)
java version "1.6.0_31"
Java(TM) SE Runtime Environment (build 1.6.0_31-b04)
Java HotSpot(TM) Server VM (build 20.6-b01, mixed mode)
准备工作:
首先需要安装jdk和gcc(或者其他c编译器也可以)并配置相应的环境变量(可自行在网上搜索,这个资料很多的),配好之后在命令行运行gcc --version和java -version出现版本号就代表安装配置成功了:
- [roysong@roysong c]$ gcc --version
- gcc (GCC) 4.6.3 20120306 (Red Hat 4.6.3-2)
- Copyright © 2011 Free Software Foundation, Inc.
- 本程序是自由软件;请参看源代码的版权声明。本软件没有任何担保;
- 包括没有适销性和某一专用目的下的适用性担保。
- [roysong@roysong c]$ java -version
- java version "1.6.0_31"
- Java(TM) SE Runtime Environment (build 1.6.0_31-b04)
- Java HotSpot(TM) Server VM (build 20.6-b01, mixed mode)
然后需要将jdk中的虚拟机库配置到环境路径中,我是在/etc/ld.so.conf.d/下面新建了一个jni-i386.conf文件,内容如下:
- /usr/java/jdk1.6.0_31/jre/lib/i386/client
然后运行一下ldconfig –v,让配置文件起作用,也可以直接编辑/etc/ld.so.conf文件,将这个路径加入进去.这个路径的含义就是$JAVA_HOME/jre/lib/i386/client,其中
$JAVA_HOME换成你自己的jdk安装路径就可以了.这是为了找到libjvm.so.因为我是32位的机器,所以路径中是i386,
我估计64位的机器这个目录是不一样的,根据libjvm.so文件的路径自行替换即可.
程序编写和编译:
程序就分成了c的部分和java的部分,首先我们先看看java的部分,我是把在同一目录下新建了两个文件夹分别叫c和java,
作为c程序的路径和java的classpath,然后在java文件夹下面新建包com.test.base64,java程序的目录结构即:
java/com/test/base64.接着,我们在base64目录下面新建一个Hello.java:
- package com.test.base64;
- public class Hello{
- public String hello (String name){
- return "hello,"+name;
- }
- }
结构非常简单,不过有参数有返回值就可以代表大部分情况了.然后我们使用javac对这个文件进行编译以产生一个class
文件:
- [roysong@roysong java]$ cd com/test/base64/
- [roysong@roysong base64]$ javac Hello.java
- [roysong@roysong base64]$ ls
- Hello.class Hello.java
编译成功后,在base64目录下会出现Hello.class这个文件.
然后我们回到c文件夹,新建一个c源文件,我的是cfjIns.c,开始编写c的程序:
- #include <stdio.h>
- #include <jni.h>
- /**
- *初始化jvm
- */
- JNIEnv* create_vm() {
- JavaVM* jvm;
- JNIEnv* env;
- JavaVMInitArgs args;
- JavaVMOption options[1];
- args.version = JNI_VERSION_1_6;
- args.nOptions = 1;
- options[0].optionString = "-Djava.class.path=../java";
- args.options = options;
- args.ignoreUnrecognized = JNI_FALSE;
- JNI_CreateJavaVM(&jvm, (void **)&env, &args);
- return env;
- }
这是cfjIns.c的第一部分,头文件声明
- #include <jni.h>
非常重要,这是引用的$JAVA_HOME/include/jni.h,一会儿我们在编译时会加上这个路径.
其次就是
- args.version = JNI_VERSION_1_6;
这是jni的版本信息,需要跟你自己的jdk中jni版本对应,jdk1.6和jdk1.7的jni版本都是上面这个.
- options[0].optionString = "-Djava.class.path=../java";
这个参数是指明你自己java程序的类路径(classpath),因为我的c文件夹和java文件夹在同一目录下,所以我用了一个
相对路径来指明classpath.
cfjIns.c中获取类定义的函数:
- /**
- * 根据全限定类名来获取类的定义
- */
- jclass create_class(JNIEnv* env,char *className){
- jclass cls = (*env)->FindClass(env,className);
- if(cls == 0){
- printf("class-[%s] find error\n",className);
- return;
- }
- return cls;
- }
这个函数有两个参数,第一个就是我们上面通过create_vm函数创建的jvm环境,第二个是全限定的类名字符串,
比如:"com/test/base64/Hello",返回值即对应的类.
cfjIns.c中获取类实例的函数:
- /**
- *通过无参构造函数来获取对应类的实例
- */
- jobject getInstance(JNIEnv* env, jclass obj_class)
- {
- jmethodID construction_id = (*env)->GetMethodID(env,obj_class, "<init>", "()V");
- jobject obj = (*env)->NewObject(env,obj_class, construction_id);
- if(obj == 0){
- printf("java class instance failed\n");
- return;
- }
- return obj;
- }
这个函数的第一个参数是jvm环境,第二个是通过上面的create_class函数获取的类定义.
cfjIns.c中获取方法定义的函数:
- /**
- * 根据类\方法名\返回值\参数获取类中对应的方法定义
- */
- jmethodID get_method(JNIEnv* env,jclass cls,char *methodName,char *key){
- jmethodID mid = (*env)->GetMethodID(env,cls,methodName,key);
- if(mid == 0){
- printf("method-%s is not found\n",methodName);
- return;
- }
- return mid;
- }
这儿我们需要注意的是最后一个参数字符串key,这个是方法签名,用于指明方法的参数和返回值类型,格式是
"(参数类型声明)返回值类型声明".类型声明的值参照下面的对应表:
Java类型 | 对应的签名 |
boolean | Z |
byte | B |
char | C |
shrot | S |
int | I |
long | L |
float | F |
double | D |
void | V |
Object | L用/分割包的完整类名; Ljava/lang/String; |
Array | [签名 [I [Ljava/lang/String; |
举个例子,如果方法的参数是int,返回值是void,那么方法的签名就是"(I)V";如果方法的参数是String,返回值
也是String,则方法的签名就是"(Ljava/lang/String;)Ljava/lang/String;",注意,如果是引用类型,类名后面必须
带有一个分号.
我们也可以通过javap来查看类中方法参数和返回值的签名:
- [roysong@roysong c]$ cd ../java/com/test/base64/
- [roysong@roysong base64]$ javap -s -private Hello
- Compiled from "Hello.java"
- public class com.test.base64.Hello extends java.lang.Object{
- public com.test.base64.Hello();
- Signature: ()V //构造函数的签名
- public java.lang.String hello(java.lang.String);
- Signature: (Ljava/lang/String;)Ljava/lang/String; //hello方法的签名
- }
然后是一个将c语言中的字符串转换为java中String的工具函数:
- /**
- * 转换c中的字符串为java.lang.String,这个方法是从网上找到的,感谢原作者<a href="http://home.cnblogs.com/u/liangwind/">天末凉风</a>
- */
- jstring stoJstring(JNIEnv* env, const char* pat)
- {
- jclass strClass = (*env)->FindClass(env,"Ljava/lang/String;");
- jmethodID ctorID = (*env)->GetMethodID(env,strClass, "<init>", "([BLjava/lang/String;)V");
- jbyteArray bytes = (*env)->NewByteArray(env,strlen(pat));
- (*env)->SetByteArrayRegion(env,bytes, 0, strlen(pat), (jbyte*)pat);
- jstring encoding = (*env)->NewStringUTF(env,"utf-8");
- return (jstring)(*env)->NewObject(env,strClass, ctorID, bytes, encoding);
- }
有了上面的例子,这个函数就很好理解了,首先获取到java.lang.String的类定义,然后获取构造函数,然后将c中的字符串
转化为字节流并设置编码格式,最后产生一个新的java.lang.String对象并返回.
这下我们就准备齐全了,开始编写调用函数:
- void invoke(){
- JNIEnv* env = create_vm(); //初始化java虚拟机
- jclass cls = create_class(env,"com/test/base64/Hello");//根据类名找到对应的类
- jobject obj = getInstance(env,cls);//然后根据类获取对应的实例
- jmethodID hello = get_method(env,cls,"hello","(Ljava/lang/String;)Ljava/lang/String;");//根据类\方法名和签名获取到对应的方法
- jstring name_str = (*env)->CallObjectMethod(env,obj,hello,stoJstring(env,"a"));//传入参数调用方法
- const char* pname = (*env)->GetStringUTFChars(env,name_str, NULL);//将返回的java字符串转换为c字符串
- printf("the result is:%s\n",pname);//打印出调用的结果
- }
调用函数编写完成后,用一个main函数来运行一下查看结果:
- int main(int argc, char **argv) {
- invoke();
- }
至此,c的源代码cfjIns.c就编写完成了,我们开始编译cfjIns.c:
- [roysong@roysong c]$ gcc -o cfj cfj.c -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -L$JAVA_HOME/jre/lib/i386/client -ljvm
- [roysong@roysong c]$ ls
- cfjIns cfjIns.c
编译命令中我们使用-I选项加入了两个头文件路径,$JAVA_HOME/include这个是为了引用到jni.h,而
$JAVA_HOME/include/linux这个为了引用到jni_md.h,因为jni.h中有对jni_md.h的引用.我的操作系统是fedora,所以
jni_md.h在$JAVA_HOME/include的linux文件夹下面,其他操作系统可能路径不同,找到jni_md.h文件的路径进行替换
即可.使用-L选项是为了引用到java虚拟机的库文件libjvm.so,-l选项(注意,这是小写的L,而不是大写的i)是声明具体引用
的库jvm.如果libjvm.so文件的路径与我的不同,找到libjvm.so文件的路径进行替换即可.
编译成功后,目录下面会出现一个可运行的cfj文件,如果一切顺利,我们运行它就可以得到预期的结果:
- [roysong@roysong c]$ ./cfjIns
- hello,a
显示正常,调用完成
最近在做的工作要用到本地方法,需要在Java中加载不少动态链接库(以下为方便延用Windows平台下的简写dll,但并不局限于Windows)。刚刚把程序跑通,赶紧把一些心得写出来,mark。也希望对大家的类似工作有所帮助.
首先,应当明确,dll有两类:(1)Java所依赖的dll和,(2)dll所依赖的dll。正是由于第(2)种dll的存在,才导致了java中加载dll的复杂性大大增加,许多说法都是这样的,但我实验的结果却表明似乎没有那么复杂,后面会予以详细阐述。
其次,Java中加载dll的方式也有两种:(1)通过调用System.loadLibrary(String filename)和,(2)通过调用System.load(String filename)方法。其底层都是通过使用ClassLoader中的loadLibrary(Class fromClass, String name, boolean isAbsolute)方法来实现的,区别仅在于(1)中的filename必须是绝对路径,(2)中的filename只能是dll名,不允许包含文件夹。
再者,Eclipse是一个相当强大的平台,其提供的BundleClassLoader的强大是一个很重要的原因,对于dll的加载也有自己一套很别致的做法,值得我们采纳。
根据上面的介绍,分两部分阐述Java中加载dll面临的主要问题和解决途径。
1. 在一般Java程序中加载dll
我所做的工作,需要加载的dll如下:
DigitDll.dll
DsivsAcct.dll
DsivsComm.dll
DsivsTrans.dll
JBPack.dll
XCodeDll.dll
ImageDllCtrl.dll
yhfiche.dll
yhocr.dll
yhbill.dll
TSealSvrDll.dll
TImg.dll
TImage.dll
直接调用的是TImage中的若干方法,列表中TImage之前的所有其直接或间接依赖的,不仅要把所有的dll load全,更要注意他们之间的依赖关系,被依赖dll一定要先加载,否则就会报错:UnsatisfiedLinkError。故而,首先应理清dll之间的依赖关系,上面的列表已经是处理过的了。
接下来是设置JVM的搜索路径,使其能够找到你的dll。JVM的搜索路径由java.library.path系统属性决定,其默认值为系统环境变量中的PATH内容。因此,可以通过修改PATH变量来达到设置java.library.path属性的目的(改变之后Eclipse需要重新启动),一般的方法是在PATH中加入dll所在文件夹的绝对路径。另一种方法是在Java命令的参数中加入“-Djava.library.path=dll所在文件夹的绝对路径”来设置(可以用;分开多个路径)。对于Eclipse开发环境上的应用程序,可以通过修改其启动参数,在VM arguments编辑框中加入前述参数。对于打包出来的Eclipse安装包,可编辑其启动目录下的application.ini(假设其启动文件为application.exe),在-vmargs后加入前述参数来设置java.library.path的值。需要注意的是,一旦JVM已经启动,则无法再修改java.library.path的内容了,也就是说,通过:
System.setProperty("java.library.path", "c:/mylib");
这样的方式是无法达到目的的,因为该属性是只读的。Sun公司的论坛上曾经讨论过如何在代码中修改java.library.path的问题,结论是:不能通过代码修改!如果嫌"java -Djava.library.path=c:/mylib"这样的方式写得太死,也只能是通过shell编程之类的方法对路径进行预处理,以改善其灵活性了。
如果你的dll是封装在jar包中的,则需要首先将之解压缩到一个临时路径上,然后再将该路径加入到Djava.library.path中,或者干脆将其解压缩到系统路径上。
2. 在Eclipse平台上加载dll
上面提到,Java中对本地库路径的设置方式做得太死,这也是我自己的切身体会,但令人感到欣慰的是我们的Eclipse平台的提供了一套比较灵活的做法,通过eclipse提供的BundleClassLoader,你可以将dll封在plugin中,既不需要在使用时解压缩,也不需要额外设置java.library.path属性,BundleClassLoader会自行到以相对plugin根目录的指定目录下去查找你的dll,这些目录是:ws/win32/, os/win32/x86/, os/win32/, nl/zh/CN/, nl/zh/,见org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader和org.eclipse.core.runtime.internal.adaptor.EclipseClassLoadingHook。
我的目录设置是:
.classpath
.cvsignore
.project
build.properties
classes
CVS
lib
META-INF
os
plugin.xml
src
我把所有的dll都放到了os下面的win32目录内,同样可以建立ws/win32等目录用于放置本地库。如此处理之后,不用再修改任何系统变量就可以顺利加载本地库了。
另外,Eclipse还在MANIFEST文件中提供了Bundle-NativeCode的设置项,也是用于加载本地库的,有待进一步研究