参看:的课程《JNI:Java 与 C++ 的美好结合》
参看:
参看:
一、基本介绍
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);
- }
- };
五、附录
1、查看 Java 方法签名的办法:
CMD 跳转到 .class 文件所在目录,执行 javap -s -p XXX 即可。(其中 XXX 为类名)。
来源: http://www.bubuko.com/infodetail-1964061.html