Java与c++通过JNI的完美结合

    参看:高煥堂的课程《JNI:Java与C++的美好结合》http://edu.csdn.net/course/detail/1469

    参看:http://www.cnblogs.com/yejg1212/archive/2013/06/07/3125392.html

    参看:http://blog.csdn.net/jiangwei0910410003/article/details/17465457

一、基本介绍

1、JNI是什么?

      Java本机接口(Java Native Interface (JNI))是本机编程接口,它是JDK的一部分,JNI它提供了若干的API,实现了和Java和其他通信(主要是C&C++)。

2、JNI有什么用?

      JNI最常见的两个应用:从Java程序调用C/C++,以及从C/C++程序调用Java代码。

3、使用JNI需要什么环境?

      (1)、JDK

     工具及组件:(Java编译器:javac.exe 、JVM :java.exe 、本地方法C文件生成器:javah.exe)

     库文件和头文件:jni.h( C头文件)、jvm.lib 和jvm.dll(windows下) 或libjvm.so(linux下)。

     (2)、能够创建动态链接库的C和C++编译器

     最常见的两个C编译器是用于Windows的Visual C++ 和用于基于UNIT系统的gcc/cc。


二、Java调用C++代码的完美方法

     JNI是Java与C++之间的桥梁,它们之间的层次关系如下图所示:

JNI层是以C方式实现的,逻辑上讲还属于Java类的。

C与C++的语法是通用的,因此从理论上讲可以将JNI(C层)代码和C++层代码可以放在相同的文档中。

1、保持JNI层稳定的原则:“静态对静态,动态对动态”

     JNI层既可以创建Java层对象,也可以C++层对象。需要特别注意的是:JNI层(C层)的全局或静态(static)变量只适合存储静态的数据,例如methodID或fieldID等。把动态的Java或C++对象引用储存于JNI(C层)的全局变量,会导致JNI层(C层)的不稳定性。

     所以:“静态对静态”的原则是:JNI层的全局变量或静态变量只能存储Java层或C++层的静态数据。

      “动态对动态”的原则是:JNI层动态创建的对象只能存储在Java层或C++层中动态创建的对象中。

2、以下例子展示了如何在Java层存储JNI层动态创建的C++对象。

首先:该例的需求是在Java中使用已经在C++中实现的类。

C++层的代码如下:

#pragma once

class CFood 
{
private:
	char* name;
	double price;
public:
	CFood(char* name, double price) 
	{
		this->name = name;
		this->price = price;
	}

	~CFood() 
	{
		if(name != NULL) 
		{
			free(name);
			name = NULL;
		}
	}

	const char* getName() 
	{
		return this->name;
	}

	double getPrice() 
	{
		return this->price;
	}
};


Java层为了使用上述代码,引入一个新的类Food,如下:

public class Food {

	static {
		System.loadLibrary("jniFood");
	}
	
	// 用于存储C++层的对象指针
	private int mObject;
	
	public Food(String name, double price) {
		setFoodParam(name, price);
	}
	
	public native void setFoodParam(String name, double price);
	public native String getName();
	public native double getPrice();
	protected native void finalize();
	
	public static void main(String[] args) {
		Food f1 = new Food("面包", 1.99);
		Food f2 = new Food("牛奶", 3.99);
		
		System.out.println(String.format("食物:%s, 单价:%f", f1.getName(), f1.getPrice()));
		System.out.println(String.format("食物:%s, 单价:%f", f2.getName(), f2.getPrice()));
	}
}


其中,声明了本地方法,需要注意的是创建一个int型字段用来存放C++层对象的指针。另外需要注意的是通过本地方法finalize()来析构c++对象。

下面是JNI层的实现代码:

头文件:

#include 
/* Header for class test2_Food */

#ifndef _Included_test2_Food
#define _Included_test2_Food
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     test2_Food
 * Method:    setFoodParam
 * Signature: (Ljava/lang/String;D)V
 */
JNIEXPORT void JNICALL Java_test2_Food_setFoodParam
  (JNIEnv *, jobject, jstring, jdouble);

/*
 * Class:     test2_Food
 * Method:    getName
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_test2_Food_getName
  (JNIEnv *, jobject);

/*
 * Class:     test2_Food
 * Method:    getPrice
 * Signature: ()D
 */
JNIEXPORT jdouble JNICALL Java_test2_Food_getPrice
  (JNIEnv *, jobject);

/*
 * Class:     test2_Food
 * Method:    finalize
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_test2_Food_finalize
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif


源文件:

#include "stdafx.h"
#include 
#include "test2_Food.h"
#include "Food.h"

char* jstring2string(JNIEnv* env, jstring jstr)
{
	char* rtn = NULL;
	jclass clsstring = env->FindClass("java/lang/String");
	jstring strencode = env->NewStringUTF("utf-8");
	jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
	jbyteArray barr= (jbyteArray)env->CallObjectMethod(jstr, mid, strencode);
	jsize alen = env->GetArrayLength(barr);
	jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);

	if (alen > 0)
	{
		rtn = (char*)malloc(alen + 1);

		memcpy(rtn, ba, alen);
		rtn[alen] = 0;
	}
	env->ReleaseByteArrayElements(barr, ba, 0);
	return rtn;
}

jstring char2Jstring(JNIEnv* env, const char* pat)
{
	jclass strClass = env->FindClass("Ljava/lang/String;");
	jmethodID ctorID = env->GetMethodID(strClass, "", "([BLjava/lang/String;)V");
	jbyteArray bytes = env->NewByteArray(strlen(pat));
	env->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*)pat);
	jstring encoding = env->NewStringUTF("utf-8");
	return (jstring)env->NewObject(strClass, ctorID, bytes, encoding);
}

CFood* getCFood(JNIEnv *env, jobject thiz) 
{
	jclass clazz = env->GetObjectClass(thiz);
	jfieldID fid = env->GetFieldID(clazz, "mObject", "I");
	jint p = env->GetIntField(thiz, fid);
	return (CFood*)p;
}

void setFood(JNIEnv *env, jobject thiz, const CFood* pFood) 
{
	jclass clazz = env->GetObjectClass(thiz);
	jfieldID fid = env->GetFieldID(clazz, "mObject", "I");
	env->SetIntField(thiz, fid, (jint)pFood);
}

JNIEXPORT void JNICALL Java_test2_Food_setFoodParam
  (JNIEnv *env, jobject thiz, jstring name, jdouble price) 
{
	//const char* tempName = env->GetStringUTFChars(name, 0);
	char* tempName = jstring2string(env, name);
	double tempPrice = price;

	CFood* pFood = new CFood(tempName, tempPrice);
	setFood(env, thiz, pFood);
}

JNIEXPORT jstring JNICALL Java_test2_Food_getName
  (JNIEnv *env, jobject thiz)
{
	CFood* pFood = getCFood(env, thiz);
	const char* name = pFood->getName();
	return char2Jstring(env, name);
}

JNIEXPORT jdouble JNICALL Java_test2_Food_getPrice
  (JNIEnv *env, jobject thiz)
{
	CFood* pFood = getCFood(env, thiz);
	return pFood->getPrice();
}

JNIEXPORT void JNICALL Java_test2_Food_finalize
  (JNIEnv *env, jobject thiz) 
{
	CFood* pFood = getCFood(env, thiz);
	if (pFood != NULL) 
	{
		delete pFood;
		pFood = NULL;
		setFood(env, thiz, pFood);
	}
}


三、Java(Eclipse)与C++(Visual Studio)的完美联调

      将Debug版本的dll放在Java项目下,在Eclipse中设置为本地方法设置断点,启动Debug调试,同时在VS该dll项目中设置:Debug->Attach to Process ->选择javaw.exe然后点击“Attach”。

      这样本地方法就可以直接跳到VS环境下跟踪调试,本地方法调试完成后(在VS中按F5)就转到Eclipse中继续调试。


四、C++中回调Java方法(不太完美)

      如上所述,在Java中保存C++对象堪称完美,不会有任何副作用。但是在C++中存放Java对象,就比较麻烦了。据我实验测试 jobject类型并不可靠,用它在C++中保存Java对象有很大隐患。以下是我实验数据:(完整测试代码上传于:http://download.csdn.net/detail/xiaoxiaoyusheng2012/9766376)

---------------------- java中包装调用本地方法
public static void main(String[] args) {
		MyFile myFile = new MyFile();
		MyPrint myPrint = new MyPrint();
		//myFile.registerPrint(myPrint);
		myFile.setPrint(myPrint);
		myFile.setMyPrint(myPrint);
		myFile.doPrint("hello world!");
		myFile.doPrint("again!");
		myFile.doPrint("next!");
	}
-----------------------
关系:jclass clazz = env->GetObjectClass(thiz); 

序号<1> : evn = 0x004ba514   thiz = 0x0346fc6c  clazz = 0x03508a30 // init
序号<1> : evn = 0x004ba514   thiz = 0x0346fc6c  clazz = 0x03508a34 // register
序号<1> : evn = 0x004ba514   thiz = 0x0346fc9c  clazz = 0x03508a30 // doPrint 
序号<1> : evn = 0x004ba514   thiz = 0x0346fc9c  clazz = 0x03508a30 // doPrint
序号<1> : evn = 0x004ba514   thiz = 0x0346fc9c  clazz = 0x03508a30 // doPrint


------------------------java中直接调用本地方法

关系:jclass clazz = env->GetObjectClass(thiz); 
public static void main(String[] args) {
		MyFile myFile = new MyFile();
		MyPrint myPrint = new MyPrint();
		myFile.registerPrint(myPrint);
		myFile.setPrint(myPrint);
		//myFile.setMyPrint(myPrint);
		myFile.doPrint("hello world!");
		myFile.doPrint("again!");
		myFile.doPrint("next!");
	}
序号<1> : evn = 0x004fa514   thiz = 0x034ffc6c  clazz = 0x03598a30  // init
序号<2> : evn = 0x004fa514   thiz = 0x034ffc9c  clazz = 0x03598a34  // register
序号<3> : evn = 0x004fa514   thiz = 0x034ffc9c  clazz = 0x03598a30  // doPrint
序号<4> : evn = 0x004fa514   thiz = 0x034ffc9c  clazz = 0x03598a30  // doPrint
序号<5> : evn = 0x004fa514   thiz = 0x034ffc9c  clazz = 0x03598a30  // doPrint

     上述结果是:jobject的值会变化,不能保存在C++代码中,jobject的值变化的原因,我猜测与Java的垃圾回收机制有关。JVM不断地整理内存,导致Java对象的内存移动等变化。所以,网上好多文章讲jobject可以直接使用的,应该都是有很多问题的。

      如果这样,那C++该如何回调java代码,我的方法是“始终将 JNI接口参数中的JNIEnv * 和 jobject ”一起传参使用,不作保存。以下是一份实现代码:

Java层代码:

package test1;

class MyPrint {
	
	public void onPrint(String text) {
		System.out.println(text);
	}
}

public class MyFile {

	private MyPrint myPrint = null;
	
	static {
		System.loadLibrary("jniTest5");
	}
	
	private int mObject;

	public MyFile() {
		init();
	}
	
	public void onPrint(String text) {
		myPrint.onPrint(text);
	}
	
	public void setPrint(MyPrint myPrint) {
		this.myPrint = myPrint;
	}
	
	public void setMyPrint(MyPrint myPrint) {
		setPrint(myPrint);
		this.registerPrint(myPrint);
	}
	
	public void myPrint(String text) {
		this.doPrint(text);
	}
	
	public native void init();
	
	public native void registerPrint(MyPrint myPrint);
	
	public native void doPrint(String text);
	
	protected native void finalize();
	
	public static void main(String[] args) {
		MyFile myFile = new MyFile();
		MyPrint myPrint = new MyPrint();
		
		myFile.setMyPrint(myPrint);
		myFile.doPrint("hello world!");
		myFile.doPrint("again!");
		myFile.doPrint("next!");
	}
}


JNI接口层:

头文件:test1_MyFile.h

#include 
/* Header for class test1_MyFile */

#ifndef _Included_test1_MyFile
#define _Included_test1_MyFile
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     test1_MyFile
 * Method:    init
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_test1_MyFile_init
  (JNIEnv *, jobject);

/*
 * Class:     test1_MyFile
 * Method:    registerPrint
 * Signature: (Ltest1/MyPrint;)V
 */
JNIEXPORT void JNICALL Java_test1_MyFile_registerPrint
  (JNIEnv *, jobject, jobject);

/*
 * Class:     test1_MyFile
 * Method:    doPrint
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_test1_MyFile_doPrint
  (JNIEnv *, jobject, jstring);

/*
 * Class:     test1_MyFile
 * Method:    finalize
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_test1_MyFile_finalize
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

源文件:

#include "stdafx.h"
#include 
#include "MyFile.h"
#include "test1_MyFile.h"

char* jstring2string(JNIEnv* env, jstring jstr)
{
	char* rtn = NULL;
	jclass clsstring = env->FindClass("java/lang/String");
	jstring strencode = env->NewStringUTF("utf-8");
	jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
	jbyteArray barr= (jbyteArray)env->CallObjectMethod(jstr, mid, strencode);
	jsize alen = env->GetArrayLength(barr);
	jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);

	if (alen > 0)
	{
		rtn = (char*)malloc(alen + 1);

		memcpy(rtn, ba, alen);
		rtn[alen] = 0;
	}
	env->ReleaseByteArrayElements(barr, ba, 0);
	return rtn;
}

CMyFile* getMyFile(JNIEnv *env, jobject thiz) 
{
	jclass clazz = env->GetObjectClass(thiz);
	jfieldID fid = env->GetFieldID(clazz, "mObject", "I");
	jint p = env->GetIntField(thiz, fid);
	return (CMyFile*)p;
}

void setMyFile(JNIEnv *env, jobject thiz, CMyFile* pFile)
{
	jclass clazz = env->GetObjectClass(thiz);
	jfieldID fid = env->GetFieldID(clazz, "mObject", "I");
	env->SetIntField(thiz, fid, (jint)pFile);
}

JNIEXPORT void JNICALL Java_test1_MyFile_init
  (JNIEnv *env, jobject thiz)
{
	CMyFile* pFile = new CMyFile();
	setMyFile(env, thiz, pFile);
}

JNIEXPORT void JNICALL Java_test1_MyFile_registerPrint
  (JNIEnv *env, jobject thiz, jobject jobj)
{
	CMyPrint* pPrint = new CMyPrint();
	CMyFile* pFile = getMyFile(env, thiz);
	pFile->registerPrint(pPrint);
}

JNIEXPORT void JNICALL Java_test1_MyFile_doPrint
  (JNIEnv *env, jobject thiz, jstring strText)
{
	CMyFile* pFile = getMyFile(env, thiz);
	char* pText = jstring2string(env, strText);
	pFile->doPrint(env, thiz, pText);
	if (pText != NULL)
	{
		free(pText);
		pText = NULL;
	}
}

JNIEXPORT void JNICALL Java_test1_MyFile_finalize
  (JNIEnv *env, jobject thiz)
{
	CMyFile* pFile = getMyFile(env, thiz);
	if (pFile != NULL) 
	{
		delete pFile;
		pFile = NULL;
		setMyFile(env, thiz, pFile);
	}
}

C++层:

// MyPrint.h

#include "stdafx.h"
#include 
#include 

class CMyPrint
{
public:
	jstring char2Jstring(JNIEnv* env, const char* pat)
	{
		jclass strClass = env->FindClass("Ljava/lang/String;");
		jmethodID ctorID = env->GetMethodID(strClass, "", "([BLjava/lang/String;)V");
		jbyteArray bytes = env->NewByteArray(strlen(pat));
		env->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*)pat);
		jstring encoding = env->NewStringUTF("utf-8");
		return (jstring)env->NewObject(strClass, ctorID, bytes, encoding);
	}

	// 如下传递JNIEnv* 和 jobject来获取Java对象,然后回调
	void onPrint(JNIEnv *env, jobject thiz, char* text) 
	{
		jclass clazz = env->GetObjectClass(thiz);
		jmethodID methodID = env->GetMethodID(clazz, "onPrint", "(Ljava/lang/String;)V");
		jstring strText = char2Jstring(env, text);
		env->CallVoidMethod(thiz, methodID, strText);
	}
};

// MyFile.h

#pragma once

#include "MyPrint.h"

class CMyFile 
{
private:
	CMyPrint* pPrint;
public:

	~CMyFile() 
	{
		if (pPrint != NULL) 
		{
			delete pPrint;
			pPrint = NULL;
		}
	}

	void registerPrint(CMyPrint* pPrint) 
	{
		this->pPrint = pPrint;
	}

	void doPrint(JNIEnv *env1, jobject thiz, char* text) 
	{
		pPrint->onPrint(env1, thiz, text);
	}
};


五、C++中回调Java方法(完美)

      上述的回调是在一个线程栈中完成的,不能称为正真的回调。在后续学习实践中发现: JNIEnv *只在当前线程中有效,所以JNIEnv*不应该被缓存。可以缓存的是JavaVM*。另外,JNI中接口的参数都是局部引用,当该方法栈执行完毕,局部引用就会被销毁,因此即使要缓存JavaVM*,也应该将JavaVM*转换为全局引用再缓存。jobject也可以转换为全局引用后缓存。

       这里讲的完美回调方法是:我们在C++层保存Java对象。具体做法就是将jobject转换为全局引用后缓存。

以下是一份测试代码:

Java层:

package test1;

class MyPrint {
	public void onPrint(String text) {
		System.out.println(text);
	}
}

public class MyFile {

	private MyPrint myPrint = null;
	
	static {
		System.loadLibrary("jniTest7");
	}
	
	private int mObject;

	public MyFile() {
		init();
	}
	
	public void setPrint(MyPrint myPrint) {
		this.myPrint = myPrint;
	}
	
	public void setMyPrint(MyPrint myPrint) {
		setPrint(myPrint);
		this.registerPrint(myPrint);
	}
	
	public native void init();
	protected native void finalize();
	public native void registerPrint(MyPrint myPrint);
	public native void doPrint(String text);
	
	public static void main(String[] args) {
		MyFile myFile = new MyFile();
		MyPrint myPrint = new MyPrint();
		myFile.setMyPrint(myPrint);
		myFile.doPrint("hello world!");
        System.out.println("等待打印结果...");
		try {
			Thread.sleep(20*1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

JNI层:

CMyFile* getMyFile(JNIEnv *env, jobject thiz) 
{
	jclass clazz = env->GetObjectClass(thiz);
	jfieldID fid = env->GetFieldID(clazz, "mObject", "I");
	jint p = env->GetIntField(thiz, fid);
	return (CMyFile*)p;
}

void setMyFile(JNIEnv *env, jobject thiz, CMyFile* pFile)
{
	jclass clazz = env->GetObjectClass(thiz);
	jfieldID fid = env->GetFieldID(clazz, "mObject", "I");
	env->SetIntField(thiz, fid, (jint)pFile);
}

JNIEXPORT void JNICALL Java_test1_MyFile_init
  (JNIEnv *env, jobject thiz)
{
	CMyFile* pFile = new CMyFile();
	setMyFile(env, thiz, pFile);
}

JNIEXPORT void JNICALL Java_test1_MyFile_finalize
  (JNIEnv *env, jobject thiz)
{
	CMyFile* pFile = getMyFile(env, thiz);
	if (pFile != NULL) 
	{
		delete pFile;
		pFile = NULL;
		setMyFile(env, thiz, pFile);
	}
}

JNIEXPORT void JNICALL Java_test1_MyFile_registerPrint
  (JNIEnv *env, jobject thiz, jobject jPrint)
{
	JavaVM* pVM = NULL;
	env->GetJavaVM(&pVM);

	// 根据局部引用生成全局引用
	JavaVM* g_pVM = (JavaVM*)env->NewGlobalRef((jobject)pVM);
	jobject g_javaPrint = env->NewGlobalRef(jPrint);

	CMyPrint* pPrint = new CMyPrint(g_pVM, g_javaPrint);
	CMyFile* pFile = getMyFile(env, thiz);
	pFile->registerPrint(pPrint);
}


JNIEXPORT void JNICALL Java_test1_MyFile_doPrint
  (JNIEnv *env, jobject thiz, jstring strText)
{
	CMyFile* pFile = getMyFile(env, thiz);
	char* pText = CMyPrint::jstring2string(env, strText);

	pFile->doPrint(pText);

	if (pText != NULL)
	{
		free(pText);
		pText = NULL;
	}
}

C++层:

typedef struct _ThreadParam
{
	JavaVM* jvm;
	jobject javaPrint;
	string text;
}ThreadParam;

DWORD WINAPI funproc(LPVOID lpparentet);

class CMyPrint
{
private:
	jobject mJavaPrintObj;
    JavaVM* jvm;
public:

	CMyPrint(JavaVM* jvm, jobject javaPrintObj) 
	{
		this->jvm = jvm;
		this->mJavaPrintObj = javaPrintObj;
	}

	~CMyPrint()
	{
		JNIEnv* pEnv = NULL;
	    jvm->AttachCurrentThread((void**)&pEnv, NULL);
		pEnv->DeleteGlobalRef(mJavaPrintObj);
		pEnv->DeleteGlobalRef((jobject)jvm);
	}

	static char* jstring2string(JNIEnv* env, jstring jstr)
    {
		char* rtn = NULL;
		jclass clsstring = env->FindClass("java/lang/String");
		jstring strencode = env->NewStringUTF("utf-8");
		jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
		jbyteArray barr= (jbyteArray)env->CallObjectMethod(jstr, mid, strencode);
		jsize alen = env->GetArrayLength(barr);
		jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);

		if (alen > 0)
		{
			rtn = (char*)malloc(alen + 1);

			memcpy(rtn, ba, alen);
			rtn[alen] = 0;
		}
		env->ReleaseByteArrayElements(barr, ba, 0);
		return rtn;
    }

	static jstring char2Jstring(JNIEnv* env, const char* pat)
	{
		jclass strClass = env->FindClass("Ljava/lang/String;");
		jmethodID ctorID = env->GetMethodID(strClass, "", "([BLjava/lang/String;)V");
		jbyteArray bytes = env->NewByteArray(strlen(pat));
		env->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*)pat);
		jstring encoding = env->NewStringUTF("utf-8");
		return (jstring)env->NewObject(strClass, ctorID, bytes, encoding);
	}

	void onPrint(char* text) 
	{
		ThreadParam* param = new ThreadParam();
		param->jvm = jvm;
		param->javaPrint = mJavaPrintObj;
		param->text = text;

	    HANDLE hander = CreateThread(NULL,0,funproc,param,0,NULL);
	}
};

DWORD WINAPI funproc(LPVOID lpparentet)  
{  
	Sleep(10*1000);

	ThreadParam* param = (ThreadParam*)lpparentet;

	JNIEnv* pEnv = NULL;
	param->jvm->AttachCurrentThread((void**)&pEnv, NULL);

	jclass clazz = pEnv->GetObjectClass(param->javaPrint);

	// 获取非静态方法ID
	jmethodID methodID = pEnv->GetMethodID(clazz, "onPrint", "(Ljava/lang/String;)V");
	
	jstring strText = CMyPrint::char2Jstring(pEnv, param->text.c_str());

	// 调用非静态方法
	pEnv->CallVoidMethod(param->javaPrint, methodID, strText);

	if (param != NULL) 
	{
		delete param;
		param = NULL;
	}
    return 0;  
}  

class CMyFile 
{
private:
	CMyPrint* pPrint;
public:

	~CMyFile() 
	{
		if (pPrint != NULL) 
		{
			delete pPrint;
			pPrint = NULL;
		}
	}

	void registerPrint(CMyPrint* pPrint) 
	{
		this->pPrint = pPrint;
	}

	void doPrint(char* text) 
	{
		pPrint->onPrint(text);
	}
};


运行结果:

等待打印结果...
hello world!


六、JNI中的字符编码方式的完美转换

参看:http://www.cnblogs.com/westblade/p/4803968.html

1、相关概念:

       (1)、Java层使用的是16bit的unicode编码(utf-16)来表示字符串,无论中文还是英文,都是两个字节。

       (2)、JNI层使用的是UTF-8编码,UTF-8是变长编码的unicode,一般ascii字符1字节,中文3字节。

       (3)、C/C++使用的是原始数据,ascii就是一个字节,中文一般是GB2312编码,用两个字节表示一个汉字。

2、字符流向

       (1)、Java ---> C/C++

       这时候,Java调用的时候使用的是UTF-16编码,当字符串传递给JNI方法时,C/C++得到的输入是jstring,这时候,JNI提供了两个函数,一个是GetStringUTFChars,该函数将得到一个UTF-8编码的字符串(char*类型),另一个函数是GetStringChars,该函数将得到一个UTF-16编码的字符串(wchar_t*类型)。无论哪种结果,得到的字符串如果含有中文,都需要进一步转换为GB2312编码。

       (2)、C/C++ ---> Java

       这时候,是JNI返回给Java字符串。C/C++首先应该负责把这个字符串变成UTF-8或UTF-16格式,然后通过NewStringUTF或者NewString来把它封装成jstring,返回给Java就可以了。

       如果字符串中不含中文字符,只是标准的ascii码,那么使用GetStringUTFChars/NewStringUTF就可以搞定了,因为这种情况下,UTF-8编码和ascii编码是一致的,不需要转换。

      如果字符串中有中文字符,那么在C/C++部分就必须进行编码转换。我们需要两个转换函数,一个是把UTf-8/-16编码转成GB2312;另一个是把GB2312转成UTF-8/-16。

      这里需要说明一下:linux和win32都支持wchar,这个事实上就是宽度为16bit的unicode编码UTF-16,所以,如果我们的c/c++程序中完全使用wchar类型,那么理论上就不需要这种转换。但是实际上,我们不可能完全用wchar来取代char的,所以就目前大多数应用而言,转换仍然是必须的。

      (3)、使用wide char类型来转换

char* jstringToWindows( JNIEnv *env, jstring jstr )
{ //UTF8/16转换成gb2312
  int length = (env)->GetStringLength(jstr );
  const jchar* jcstr = (env)->GetStringChars(jstr, 0 );

  int clen = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR)jcstr, length, NULL,0, NULL, NULL );

  char* rtn = (char*)malloc( clen ) //更正。作者原来用的是(char*)malloc( length*2+1 ),当java字符串中同时包含汉字和英文字母时,所需缓冲区大小并不是2倍关系。
  int size = 0;
  size = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR)jcstr, length, rtn,clen, NULL, NULL );
  if( size <= 0 )
    return NULL;
  (env)->ReleaseStringChars(jstr, jcstr );
  rtn[size] = 0;
  return rtn;
}

jstring WindowsTojstring( JNIEnv* env, const char* str )
{//gb2312转换成utf8/16
    jstring rtn = 0;
    int slen = strlen(str);
    unsigned short * buffer = 0;
    if( slen == 0 )
        rtn = (env)->NewStringUTF(str );
    else
    {
        int length = MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, NULL, 0 );
        buffer = (unsigned short *)malloc( length*2 + 1 );
        if( MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, (LPWSTR)buffer, length ) >0 )
            rtn = (env)->NewString(  (jchar*)buffer, length );
    }
    if( buffer )
        free( buffer );
    return rtn;
}


七、附录

1、查看Java方法签名的办法:

      CMD 跳转到 .class文件所在目录,执行javap -s -p XXX即可。(其中XXX为类名)。








你可能感兴趣的:(Java)