先将所有的JNI的本地方法封装成一个类
public class JniRes { static { System.loadLibrary("JNI");//加载C动态库 } //JAVA中只是声明本地方法,实现在C、C++中实现 public native void display(String str); public native int add(int a, int b); public native void changeObj(JavaObj myByte); }
JavaObj是用来测试JNI的一个类 为了在C++库的JNI函数中拿到成员以及修改的操作
class JavaObj { public byte[] m_buff = new byte[32]; public int m_data = 0; } public class MyTest { public static void main(String[] args) { JniRes jni = new JniRes(); System.out.println(jni.add(100, 200)); String str1 = new String("中文 &English\n"); jni.display(str1);//中文编码问题 JavaObj obj = new JavaObj(); //将字节数组赋初始值 obj.m_buff[0] = 'A'; obj.m_buff[1] = 'B'; obj.m_buff[2] = 'C'; obj.m_buff[3] = '\n'; jni.changeObj(obj);//使用byte数组在C/C++中转成char* 打印不会有中文编码错误 String str = new String(obj.m_buff); System.out.println(str); System.out.println(obj.m_data); System.out.println("运行结束"); } }
使用javah命令生成C/C++使用的JNI头文件包含了JAVA中声明的native函数的C/C++的函数声明和导出方式
我在eclipse中配置了javah可以方便的生成不用命令窗口
配置如下:
C:\Program Files\Java\jdk1.8.0_05\bin\javah.exe
${project_loc}
-v -classpath "${project_loc}/bin" -d "${project_loc}/jni" ${java_type_name}
生成的时候在光标在有native方法的类中 成功后控制台会有提示
[Overwriting file RegularFileObject[D:\workspace\Java\JNITest\jni\JniRes.h]]
以下就是生成的C/C++的JNI头文件
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class JniRes */ #ifndef _Included_JniRes #define _Included_JniRes #ifdef __cplusplus extern "C" { #endif /* * Class: JniRes * Method: display * Signature: (Ljava/lang/String;)V */ JNIEXPORT void JNICALL Java_JniRes_display (JNIEnv *, jobject, jstring); /* * Class: JniRes * Method: add * Signature: (II)I */ JNIEXPORT jint JNICALL Java_JniRes_add (JNIEnv *, jobject, jint, jint); /* * Class: JniRes * Method: changeObj * Signature: (LJniByte;)V */ JNIEXPORT void JNICALL Java_JniRes_changeObj (JNIEnv *, jobject, jobject); #ifdef __cplusplus } #endif #endif
这个头文件不需要改动,函数声明的形式是 Java_(Java中的类名)_(java中的native函数名) 构成
如果java中声明native方法的类是在一个包中的话 还有一个包名
下面就是实现这个头文件 并且编译成动态库让java调用
#include "JniRes.h" #include <stdio.h> #include <string.h> JNIEXPORT jint JNICALL Java_JniRes_add(JNIEnv *env, jobject obj, jint a, jint b) { return a+b; } JNIEXPORT void JNICALL Java_JniRes_display(JNIEnv *env, jobject obj, jstring str) { //将中间类型jstring转成char*类型 就可以使用c的函数进行处理 const char* pStr = env->GetStringUTFChars(str,0); if(!pStr) { return; } printf(pStr); fflush(stdout);//如果不加这句在eclipse的控制台上打印不会实时刷新 会到最后才打印 env->ReleaseStringUTFChars(str,pStr);//需要释放 } JNIEXPORT void JNICALL Java_JniRes_changeObj(JNIEnv *env, jobject obj, jobject that) { jfieldID fid1 = 0;//字段描述符 jfieldID fid2 = 0; //jclass代表java中的对象 jclass cls = env->GetObjectClass(that); //使用反射获得java类中的成员字段描述符 [代表数组,B代表基本类型byte fid1 = env->GetFieldID(cls, "m_buff","[B"); if(!fid1) { printf("GetFieldID Fail!\n"); fflush(stdout); return; } fid2 = env->GetFieldID(cls, "m_data","I"); if(!fid2) { printf("GetFieldID Fail!\n"); fflush(stdout); return; } //通过java对象(在JNI里应该是一个句柄?)和字段描述符获得中间类型 jbyteArray buffArray = (jbyteArray)env->GetObjectField(that,fid1); //拿到原来byte数组中的内容 char* data = (char*)env->GetByteArrayElements(buffArray, 0);//转成C类型 printf("获得到的实参内容:%s",data); fflush(stdout); char src[100] = "HelloJava"; /* 参数1 需要修改的中间类型对象 参数2 起始位置 参数3 修改字节长度 参数4 来源 */ env->SetByteArrayRegion(buffArray,0,32,(jbyte*)src); env->SetIntField(that,fid2,512); env->SetObjectField(that,fid1,buffArray);//将修改完的数据通过JNI函数将JAVA虚拟机对应内存中的对象真正修改 }
如果编译会报找不到头文件 那需要在工程中包含附加库目录
C:\Program Files\Java\jdk1.8.0_05\include
C:\Program Files\Java\jdk1.8.0_05\include\win32
因为我是windows系统上使用的是VS2008开发环境附加库目录是以上两个 一般都是在JDK的安装目录下
编译好后就可以放在Eclipse的工程目录下使用了
运行结果如下
////////////////////////////////////////////////
300
涓枃 &English
获得到的实参内容:ABC
HelloJava
512
运行结束
///////////////////////////////////////////////
JNI程序调试方式
因为这是两种语言合作完成的项目所以调试没有直接在IDE中这么容器,其实也不麻烦。
先在eclipse中调用native方法的地方设置断点 当java运行到那里时候到VS中选 工具-附加到进程
选择javaw.exe这个进程 然后在VS的函数中设置断点
回到eclipse中全速运行 程序就会跳到VS中的断点处就可以调试了
其他IDE也是类似的方法
备注 中文乱码的原因
◆ java内部是使用的16bit的unicode编码(utf-16)来表示字符串的,无论英文还是中文都是2字节; ◆ jni内部是使用utf-8编码来表示字符串的,utf-8是变长编码的unicode,一般ascii字符是1字节,中文是3字节; ◆ c/c++使用的是原始数据,ascii就是一个字节,中文一般是GB2312编码,用2个字节表示一个汉字。