轉自 https://blog.csdn.net/xiaoxiaoyusheng2012/article/details/56672173
參看:高煥堂的課程《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 <jni.h>
/* 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 <stdlib.h>
#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, "<init>", "([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 <jni.h>
/* 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 <jni.h>
#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 <jni.h>
#include <stdlib.h>
class CMyPrint
{
public:
jstring char2Jstring(JNIEnv* env, const char* pat)
{
jclass strClass = env->FindClass("Ljava/lang/String;");
jmethodID ctorID = env->GetMethodID(strClass, "<init>", "([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, "<init>", "([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爲類名)。
---------------------
作者:demystify
來源:CSDN
原文:https://blog.csdn.net/xiaoxiaoyusheng2012/article/details/56672173
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!