标签:关系 组件 format main port style included 设置 获取
参看:高煥堂的课程《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); } };
五、附录
1、查看Java方法签名的办法:
CMD 跳转到 .class文件所在目录,执行javap -s -p XXX即可。(其中XXX为类名)。
标签:关系 组件 format main port style included 设置 获取
原文地址:http://blog.csdn.net/xiaoxiaoyusheng2012/article/details/56672173