JNI技术

先将所有的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可以方便的生成不用命令窗口

JNI技术_第1张图片

配置如下:

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个字节表示一个汉字。


你可能感兴趣的:(java,jni,动态库,native)