java代碼訪問本地代碼(c/c++)
JNI: Java Native Interface(調用c/c++/其他本地代碼,該接口提供了java與os本地代碼互相調用的功能。
>首先在java類中聲明一個native的方法。
>使用javah命令生成包含native方法定義的c/c++頭文件。
.不會使用命令可以直接在命令行中敲入,例如:javac -help 【回車】 javah -help就會列出這個命令詳細參數的用法。
.利用javah編譯已經編譯的.class文件並且class文件含有本地(native)的方法才可被解釋。
.cmd切換到當前class文件所在的目錄(不包括當前所在的包)javah 包名\class文件名【enter】
>按照生成的c/c++頭文件來編寫c/c++源文件。
本地代碼(C/C++)訪問java代碼:在被調用的c/c++函數中也可以反過來訪問java程序中的類。
javah工具生成c/c++函數聲明中,可以看到有兩個參數:
JNIEXPORT void JNICALL 包_類名(JNIEnv* env, jobject obj){};注:jobject指向java類中的對象引用(非靜態)或類class(靜態)的引用。
.JNIEnv類型實際上代表了java環境。通過這個JNIEnv*指針,就可以對java端的代碼進行操作。例如,創建java類對象,調用java對象的方法,獲取java對象的屬性等的。JNIEnv的指針會被JNI傳入到本地方法的實現函數中來對java端的代碼進行操作。
.JNIEnv類中有很多函數可以用:
NewObject/NewString
New<TYPE>Array
Get/Set<TYPE>Field
Get/SetStatic<TYPE>Field
Call<TYPE>Method/CallStatic<TYPE>Method等等好多函數。
Java的類型在c/c++中的映射關系:
Jclass的獲取:.為了能夠在c/c++中使用java類,JNI.h頭文件定義了jclass類型來表示Java中的Class類。
.JNIEnv類中有如下幾個簡單的函數可以取得:
1. jclass FindClass(const char* clsName);
2. jclass GetObjectClass(jobject obj);
3. jclass GetSuperClass(jclass obj);返回指定類的父類
注意:FindClass會在classpath系統環境變量下尋找類;傳入完整的類名,此時包與包之間是用‘/’而不是‘.’。如:jclass cls_string = env->FindClass(“java/lang/String”);
.在c/c++本地代碼中訪問java端的代碼,一個常見的應用就是獲取類的屬性和類的方法。為了在c/c++中表示屬性和方法,JNI在jNI.h頭文件中定義了jfieldID,jmethodID類型來分別代表java端的屬性和方法。
.我們在訪問,或是設置java屬性的時候,首先就要先在本地代碼取得代表該java屬性的jfieldID,然后才能在本地代碼進行java屬性操作。同樣的,我們需要呼叫java端的方法是,也是需要取得代表該方法jmethodID才能進行java方法的調用。
.可以使用JNIEnv的GetFieldID()/GetMethodID和GetStaticFieldID/GetStaticMethodID來取得相應的就fieldID和jmethodID。(jmethodID、jfieldID取得普通的屬性和方法)
|
相關說明: clazz為指定的java類;name為指定的java類的屬性或方法名稱;其實這類似Reflect(反射),需要指定類跟屬性、方法名來取得相應的類、屬性、方法。而sign就是指定取得具體屬性和方法的[參數類型和返參]類型。 |
GetMethodID也能夠取得構造函數的jmethodID。創建一個java對象可以調用指定的構造方法。
如1:env->GetMethodID(data_Clazz, “<init>”, “()V”);
如2:package cn.itcast;
Public class TestNative{
public int property;
public int function(int foo, Date date, int[] arr);
Public void function( int i) {…}
Public void function( double d){…}
}
本地方法的實現:
JNIEXPORT void Java_Hello_test(JNIEnv* env, jobect obj) {
Jclass hello_clazz = env->GetObjectClass(obj);
jfieldID fieldID_prop = env->GetMethodID(hello_clazz, “property”, “I”);
jmethodID methodID_func = env->GetMethodID(hello_clazz, “function”,”(IL java/util/Date; [I)I”;//這里怕寫錯sign簽名:可以使用JDK的javap命令,其使用方式跟javah一樣。只不過這個目的是為了生成sign的全部簽名。Javap常用的命令:javap –s –p[full className],-s表示輸出簽名信息; -p同-private,輸出包括private訪問權限的信息。
env->CallIntMethod(obj, methodID_func, 0L, NULL, NULL);//invoke
然后再c/c++代碼中需要調用其中一個function方法的話…
//first:找到方法、屬性所在的類。
Jclass clazz_TestNative = env->FindClass(“cn/itcast/TestNative”);
//second:取得jmethodID方可調用。
jmethodID id_func = env->GetMethodID(clazz_TestNative, “function”, ??);
//??這樣寫程序不知道怎么調用,因為有兩個相同的方法。因此,引出sign的作用:
就是要用於指定要取得的屬性或方法的類型。比如:(D)V 表示的是返回的函數返回值是void,參數是int。(即sign簽名)
獲得java屬性后就可以設定相應的java屬性值:取得了相應屬性的jfield之后就可以用Set<TYPE>Field,Get<TYPE>Field,SetSatic<TYPE>Field,GetStatic<TYPE>Field等函數來對java屬性進行操作。注:這里<TYPE>不是指代泛型之類的,TYPE一類的類型,如:Int、Short等。
Get<TYPE>Field方法有兩個參數:jobject obj, jfieldID fieldID
Set<TYPE>Field方法有三個參數:jobject obj, jfieldID fieldID, jlong val
GetStatic<TYPE>Field方法有兩個參數:jclass clazz, jfieldID fieldID
SetStatic<TYPE>Field方法有三個參數:jclass clazz, jfieldID fieldID, jint value
.怎么樣獲得數組屬性呢?我們可以使用GetObjectField來取得數組類型的屬性。
#include "stdafx.h"
#include <iostream>
#include "JavaNative.h" //<>這個表示調用系統庫中的函數,””這個表示調用本地自己編寫或其他人編寫的庫函數。
#include "jni_md.h"
using namespace std;
JNIEXPORT void JNICALL Java_JavaNative_sayhello(JNIEnv * env, jobject ogj)
{
jclass clazzNativeCode = env->GetObjectClass(ogj);//得到當前類class對象
jfieldID number_id = env->GetFieldID(clazzNativeCode, "number", "I");//獲得jfieldID對象,其就是標識java類中定義了number的這個屬性的ID。
jint number = env->GetIntField(ogj,number_id);//獲得number的值。
cout<<number<<endl;
env->SetIntField(ogj, number_id, 100L);//設置number的值。
} 注意:調用java相關屬性步驟的順序<jclass ->jfieldID ->get/set…>
c/c++中調用java中的方法:
.JNIEnv提供了很多的Call<TYPE>Method跟CallStatic<TYPE>Method,還有CallNonvirtual<TYPE>Method函數,需要通過GetMethodID取得相應方法的jmethodID來傳入到上述函數的參數中。
.調用實例方法的三種形式:
Call[Static]<TYPE>Method(jobect obj, jmethodID id, …);
Call[Static]<TYPE>MethodV(jobject obj, jmethodID id, va_list lst);
Call[Static]<TYPE>MethodA(jobject obj, jmethodID id, jvalue* v);
第一種是最常用的方式;第二種是當調用這個函數的時候有一個指向參數表的valist變量時使用的。第三種是當調用這個函數的時候有一個指向jvalue或jvalue數組的指針時用的。
例 1:jmethodID max_id = env->GetMethodID(clazzNativeCode, "max", "(DD)D");
jdouble maxValue = env->CallDoubleMethod(obj, max_id, 3.14, 3.15 );
cout<<maxValue<<endl;
例 2:java code:
public class Father {
Public void father() {…}
}
Public class Child extends Father {
Public void father() {…}//重寫了Father類中的father方法。
}
如果執行: Father f = new Child();
f.father();這里是不會去調用父類的father方法的,而是調用子類的。如果硬要執行父類的該怎么辦了(除了Father f = new Father()外),有什么方法。希望知道的人說下你們的建議。我在下面利用JNI中提供的一個方法CallNonvirtual()可以在本地代碼中實現這種功能,但是這一種治標不治本的方法。(僅針對java編譯器)
C++ code:
Class Father {
[Virtual]Void father() {…}
}
Class Child : Father {
Void father() {…}
}
如果執行: Father* f = new Child();
f->father();這里是會去調用父類的father方法的,而不會去調用子類的。但是c++中有個虛函數(virtual)的,可以讓其執行子類的相應方法。看上面[]中的。
JNI中定義了CallNonvirtual<TYPE>Method方法就能夠實現子類對象調用父類方法的功能。如果想要調用一個對象的父類的方法,而不是子類的方法,就可以使用CallNonvirtual<TYPE>Method方法。
要使用它,首先要取得父類及要調用的父類的方法的JmethodID,然后傳入到這個函數就能夠通過子類對象呼叫被復寫(override)的父類方法。
public class JavaNative {
public int property;
public int number=10;
public native void sayhello();//調用本地的c/c++代碼
// public void function(int foo, Date date, int[] arr) {
// System.out.println("i execute!");
// }
public double max(double num1, double num2) {
return num1>num2? num1: num2;
}
public Father p = new Child();
public static void main(String [] args) {
System.loadLibrary("nativeCode");//調用c/c++編寫的dll文件
JavaNative na = new JavaNative();
na.sayhello();
}
}注意:Father和Child類沒有粘貼進來。
jclass clazzNativeCode = env->GetObjectClass(ogj);
jfieldID id_p = env->GetFieldID(clazzNativeCode, "p", "LFather;");//p這個對象被聲明在了java的NativeCode中了,所以先要獲得這個屬性的id。
jobject p = env->GetObjectField(ogj, id_p);//因為p是Father類的對象,所以此處定義jobject類型和getobjectField()返回相應的對象。
jclass clazz_father = env->FindClass("Father");//因為p要調用father方法,而這個方法在Father類中,子類只不過是繼承了,所以此處必須要加載Father類到本地。
jmethodID id_Father_father = env->GetMethodID(clazz_father, "father", "()V");
env->CallVoidMethod(p,id_Father_father);//調用了子類復寫的父類的father方法。
env->CallNonvirtualVoidMethod(p, clazz_father, id_Father_father);//調用了父類的father方法。
目的:
在c/c++本地代碼中創建java的對象—NewObject
.使用函數jobject NewObject(jclass clazz, jmethodID methodID, ...)可以用來創建java對象
>.Clazz表示要指定的java類;jmethodID表示要指定的構造器的ID,…表示要指定具體參數,沒有的話可以不填,即這個是可選項。GetMethodID能夠取得構造器的jmethodID,如果傳入方法名稱設定為“<init>“就能夠取得相應的構造器。
>.構造器的返回值類型簽名始終為void。
例如:jclass clazz_date = env->FindClass(“java/../Naivecode”);
jmethodID mid_date = env->GetMethod(clazz_date,”<init>”,”()V”);
jobject now = env->NewObject(clazz_date, mid_date);生成好了動態鏈接庫后,就需要調用本地方法,然后就需設置path,可以直接通過java代碼設置,也可以手動設置(這種不可取,僅供測試)。Path的路徑應該是dll文件的路徑后面再加上分號。
. 另一方法:Java對象的創建—AllocObject
>.使用函數AllocObject可以根據傳入的jclass創建一個java對象,但是其狀態是非初始化的,在使用這個對象之前絕對要用CallNonvirtualVoidMethod來調用該jclass的構造器。這樣可以延遲構造器的調用。(不常用)
例如:jclass clazz_str = env->FindClass(“java/lang/String”);
jmethodID methodID_str = env->GetMethodID(clazz_str, “<init>”, “([c)V”);
jobject string = env->AllocObject(clazz_str);
jcharArray arg = env->NewCharArray(4);
env->SetCharArrayRegion(arg, 0, 4, L”woshishui”);
env->CallNonvirtualVoidMethod(string, clazz_str, methodID_str, arg);
jclass clazz_this = env->GetObjectClass(obj);
//這里STATIC_STR為類中的一個屬性。
jfieldID fieldID_this = env->GetStaticFieldID(clazz_this, “STATIC_STR”, “Ljava/lang/String;”);
env->SetStaticObjectField(clazz_str, field_str, string);
(篇一完)