JNI視頻教程 筆記(一)

第1課
使用JNI的步驟:
1)首先在Java類中聲明一個native的方法。
2)使用Javah命令生成包含native方法聲明的C/C++頭文件。
3)按照生成的C/C++頭文件來寫C/C++源文件
4)將C/C++源文件編譯成動態鏈接庫(DLL)。
5)將DLL路徑加入到Path環境變量中。

使用JNI的兩個弊端
1)使用了JNI那麼這個Java Application將不能跨平臺了。
2)Java是強類型語言,而C/C++不是。所以在寫JNI時必須更小心。
第2課

JNIEXPORT void JNICALL java_com_MainClass_outputDate(JNIEnv *env,jobject obj)

1)JNIEnv類型實際上代表了java環境。通過這個JNIEnv*指針,就可以對Java端的代碼進行操作。例如,創建Java類的對象,調用Java對象的方法,獲取java對象的屬性等等。
2)JNIEnv類中有很多函數可以用:
NewObject/NewString/New<TYPE>Array
Get/Set<TYPE>Field
Call<TYPE>Method/CallStatic<TYPE>Method     等很多的函數

3)第二個參數obj.要看這個方法在java中是否被定義成static類型,如果不是那這個obj就是調用這個方法的那個對象;如果是static類型,那麼這個obj就是那個class類.
使用java類
爲了能夠在C/C++中使用Java類。JNI.h頭文件中專門定義了jclass類型來表示Java中的Class類.
JNIEnv類中有幾個簡單的函數可以取得jclass
jclass FindClass(const char* clsName);
jclass GetObjectClass(jobject obj);
jclass GetSuperClass(jclass obj);
FindClass 會在classpath系統環境變量下尋找類,傳入完整類名,注意包與包之間是用'/'而不是用'.'來分隔,如
jclass cls_string=env->FindClass("java/lang/String");
使用java對象
在C/C++本地代碼中訪問Java端的代碼,一個常見的應用就是獲取類的屬性和調用 類的方法.爲了在C/C++中表示屬性和方法,JNI在Jni.h頭文件中定義了jfieldID,jmethodID類型來分別代表java端 的屬性和方法.
我們在訪問或是設置java屬性時,首先就要先在本地代碼取得代表該java屬性的jfieldID,然後才能在本地代碼進行java屬性操作.同樣的,我們需要呼叫java端的方法時,也是需要取得代表該方法的jmethodID才能進行java方法調用.
使用JNIEnv的 GetFieldID/GetMethodID 
GetStaticFieldID/GetStaticMethodID 來取得相應的jfieldID和jmethodID.
GetFieldID(jclass clazz, const char* name, const char* sign);
GetStaticFieldID(jclass clazz, const char* name, const char* sign);
GetMethodID (jclass clazz, const char* name, const char* sign);
GetStaticMethodID (jclass clazz, const char* name, const char* sign);
GetMethodID也能夠取得構造函數jmethodID,創建一個java對象進可以調用指定的構造方法,如:env->GetMethodID(data_Clazz,"<init>","()V");
而sign又是什麼呢?
例如TestNative類中有兩個重載方法:
package cn.itcast;
public class TestNative{
    public void function(int i){
        System.out.println("Interger:"+i);
    }
    public void function(double d){
        System.out.println("Interger:"+d);
    }
}
然後在C/C++代碼中需要調用其中一個function方法的話.
首先要取得調用 的方法所在的類:
jclass clazz_TestNative = env->FindClass("cn/itcast/TestNative");
取得jmethodID之後才能進行調用:
jmethodID jd_func = env->GetMethodID(clazz_TestNative,"function","??");
但到底調用的是哪個方法呢?這就是sign的作用了,它用於指定要取得的屬性/方法的類型.
這裏的sign如果爲"(I)V" 則取回的是void function(int i)的jmethodID;
        如果 是"(D)V",則取回的是void function(double)的jmethodID.

例2:使用簽名取得屬性/方法ID的例子
public class Hello
{
    public int property;
    public int function(int foo,Date date,int[] arr){
        System.out.println("function");
        return 0;
    }
    public native void test();
}
//test本地方法的實現:
JNIEXPORT void Java_Hello_test(JNIEnv* env, jobject obj)
{
    //因爲test不是static類型,所以這裏傳進來的就是調用這個函數的對象,否則就傳入一個jclass對象表示native方法所在的類.
    jclass hello_clazz=env->GetObjectClass(obj);
    jfieldID fieldID_prop=env->GetFileID(hello_clazz,"property","I");
    jmethodID methodID_func=env->GetMethodID(hello_clazz,"function","(ILjava/util/Date;[I)I");
    env->CallIntMethod(obj,methodID_func,OL,NULL,NULL);
}

第3課
在瞭解了獲取jfieldID和jmethodID 之後,我們就來了解如何取得java屬性和設置java屬性值。
1. 取得了相應的jfieldID之後就可以用 Set<TYPE>Field,Get<TYPE>Field, SetStatic<TYPE>Field,和 GetStatic<TYPE>Field等函數來對java屬性進行操作了。
看下它們的定義:
 jint GetIntFieldId(jobject obj,jfieldID fieldID);  //獲取obj對象的fieldID屬性。
 jint SetIntFieldId(jobject obj,jfieldID fieldID,jint val ); 
 jfloat GetStaticFloatField(jclass clazz, jfieldID fieldID);
2. 怎樣獲取數組屬性呢?
 可以使用GetObjectField來取得數組類型的屬性,
修改java的屬性:
下面用代碼來演示:在本地代碼中修改一個int型變量的值
TestNative.java
package cn.itcast;
public class TestNative
{
    public native void sayHello();
    public int number=10;    //接下來在本地代碼中修改這個值
    public static void main(String[] args) {
        System.loadLibrary("nativeCode");
        TestNative tst=new TestNative;
        tst.sayHello();
        System.out.println(tst.number); /*在這裏輸出的是123*/
    }
}
source.cpp
#include "cn_itcast_TestNative.h"
#include <iostream>
using namespace std;
 
JNIEXPORT void JNICALL Java_cn_itcast_testNative_sayHello(JNIEnv *env,jobject obj)
{
    jclass clazz_TestNative=env->GetObjectClass(obj); /*獲得一個jclass對象*/
    jfiedID id_number=env->GetFieldID(clazz_TestNative,"number","I"); /*得到name爲number變量的ID,它的類型是int ,所以簽名爲I*/
    jint numbler2=env->GetIntField(obj,id_number);
    cout<<numbler2<<endl;  /*在這裏輸出的是10*/
    env->SetIntField(obj,id_number,123L);/*jint對應到c/c++是長整數類型32位,所以加個L*/
}
3. java方法的調用
1)JNIEnv 提供了從多的Call<TYPE>Moethod跟CallStatic<TYPE>Method,還有CallNonvirtual<TYPE>Method函數,需要通過GetMethodID取得相應方法的jmethodID來傳入一雞皮疙瘩述函數的參數中。
2)調用實例 方法的三種形式:
 Call<TYPE>Moethod(jobject obj, jmethodIDid,...);  //最常用的方式
  例:java:
  boolean fun1(int i,double d,char c){.....}
調用:  env->CallBooleanMethod(obj,id_fun1,100L,3.44,L'3');
100L:因爲java中的int,對應到C/C++中是32位的,長整形,所以在加個L表示。
L'3':因爲java中的字符是兩個字節的,unicode16,所以C/C++中要使用寬字符。
 Call<TYPE>MoethodV(jobject obj, jmethodID id, va_list lst);  //當調用這個函數的時候有一個指向參數表的va_list變量時使用的
 Call<TYPE>MoethodA(jobject obj, jmethodID id, jvalue* v);  //當調用 這個函數的一個指向jvalue或jvalue數組的指針時用 的。
例:java:
boolean fun1(int i,double d,char c){.....}
調用:
jvalue *args=new jvalue[3];
args[0].i=100L;
args[1].d=3.44;
args[2].c=L'3';
env->CallBooleanMethodA(obj,id_fun1,args);
delete [] args;
代碼示例:
TestNative.java
package cn.itcast;
public class TestNative
{
    public native void sayHello();
    public int number=10;    //接下來在本地代碼中修改這個值
    double max(double num1,double num2)
    {
        return num1>num2? num1:num2;
    }
    public static void main(String[] args) {
        System.loadLibrary("nativeCode");
        TestNative tst=new TestNative;
        tst.sayHello();
        System.out.println(tst.number); /*在這裏輸出的是123*/
    }
}
source.cpp
#include "cn_itcast_TestNative.h"
#include <iostream>
using namespace std;
 
JNIEXPORT void JNICALL Java_cn_itcast_testNative_sayHello(JNIEnv *env,jobject obj)
{
    jclass clazz_TestNative=env->GetObjectClass(obj); /*獲得一個jclass對象*/    
    jmethodID id_max=env->GetMethID(clazz_TestNative,"max","(DD)D");
    jdouble maxValue=env->CallDoubleMethod(obj,id_max,3.14,3.15);
    cout<<maxValue<<endl;
}
這樣就在C/C++中調用了java中的max方法。
使用命令行生成簽名:
 在DOS中進入工程目錄,>javap -s -private cn.itcast.TestNative
 要先編譯這個TestNative.java纔會出現max的簽名。

4. CallNonvirtual<TYPE>Method
public class Father{
    public void fun()
    {
        System.out.println("Father:func");
    }
}
 
public class Child extends Father
{
    public void fun()
    {
        System.out.println("Child:func");
    }     
}
想想這段JAVA代碼調用的是哪個類的方法?
Father p=new Child();
p.fun();
調用的是Child的fun()方法。
再看下面這個C++代碼:
class Father{
    public:
    (virtual) void fun()
    {
        cout<<"Father:func"<<endl;
    }
};
 
 class Child :public Father
{
    public:
     void fun()
    {
        cout<<"Child:func"<<endl;
    }    
};
Father* p=new Child();
p->fun();
1)沒有virtual時,不是虛擬函數,調用的是Father的fun()方法。
2)加上virtual時,是虛擬函數,調用的是Child的fun()方法。
1) 在JNI中定義的CallNonvirtual<TYPE>Method就能夠實現子類對象調用父類方法的功能。
如果想要調用一個對象的父類的方法,而不是子類的方法的話,就可以用CallNonvirtual<TYPE>Method。
2)要使用它,首先要取得父類及要調用的父類方法的jmethodID.然後傳入到這個函數就能通過子類對象呼叫被覆寫的父類的方法了。
示例:
Father.java,Child.java
//***********************Father.java************************************//
package cn.itcast;
public class Father{
    public void fun()
    {
        System.out.println("Father:func");
    }
}
//***********************Child.java************************************//
public class Child extends Father
{
    @override
    public void fun()
    {
        System.out.println("Child:func");
    }    
}
TestNative.java
package cn.itcast;
public class TestNative
{
    public native void sayHello();
    public Father p=new Chiled();
    public static void main(String[] args) {
        System.loadLibrary("nativeCode");
        TestNative tst=new TestNative;
        tst.sayHello();    
    }
}
source.cpp
#include "cn_itcast_TestNative.h"
#include <iostream>
using namespace std;
 
JNIEXPORT void JNICALL Java_cn_itcast_testNative_sayHello(JNIEnv *env,jobject obj)
{
    jfieldID id_p=env->GetFieldID(clazz_TestNative,"p","Lcn/itcast/Father;");
    jobject p=env->GetObjectField(obj,id_p);
    jclass clazz_Father=env->FindClass("cn/itcast/Father");/*傳入完整類名*/
    jmethodID id_Father_fun=env->GetMethID(clazz_Father,"fun","()V"); /*沒有參數,所以是(),返回void,所以是V*/
    env->CallvoidMethod(p,id_Father_fun); /*在這裏打印了:Child:func,已經成功調用了該方法*/
 
    env->CallNonvirtualVoidMethod(p,clazz_Father,id_Father_fun);/*在這裏打印了:Child:func,已經成功調用了父類中的方法*/
}

第4課 NewObject   String
主要內容:
1. 在C/C++本地代碼中創建Java的對象
2. 在C/C++本地代碼中訪問Java的String對象
3. 在C/C++本地代碼中創建Java的String對象

1. Java對象的創建--NewObject
1) 使用函數NewObject可以創建Java對象。
jobject NewObject(jclass clazz, jmethodID methodID,...)
第一個參數,jclass就是要創建的java對象的類型。
第二個參數,指明調用哪一個構造函數。
2)GetMethodID能夠取得構造方法的jmethodID 。如果傳入的要取得的方法名稱設定爲"<init>"就能夠得到構造方法。
3)構造方法沒有返回值,所以其返回值類型的簽名始終爲void.
jclass clazz_date = env->FindClass("java/util/Data");
jmethodID mid_date=env->GetMethodID(clazz_date,"<init>","()V");
jobject now=env->NewObject(clazz_date,mid_date);
代碼例子:
MainClass.java
public class MainClass{
    public static native void outputDate();
    public static void main(String[] args){
        System.loadlibrary("nativeCreateDataObj");
        outputDate();
    }
}
main.cpp
#include "xx.h"
JNIEXPORT void JNICALL java_com_MainClass_outputDate(JNIEnv *env,jclass jclaxx)
{
    jclass clazz_date = env->FindClass("java/util/Data");
    jmethodID mid_date=env->GetMethodID(clazz_date,"<init>","()V");/*<init>表明調用的是構造函數*/
    jobject now=env->NewObject(clazz_date,mid_date);
    jmethod mid_date_getTime=env->GetMethodID(clazz_date,"getTime","()L");
    jlong time=env->CallLongMethod(now,mid_date_getTime);
    cout<<time<<endl;
}
2.  Java字符串 <- ->  C/C++字符串
1)在Java中,使用的字符串String對象是Unicode(UTF-16)碼,即每個字符不論是中文還是英文,一個字符總是佔用兩個字節。
2)Java通過JNI接口可以將Java的字符串轉換到C/C++中的寬字符串(wchar_t *),或者傳回一個UTF-8字符串(char*)到C/C++。反過來,C/C++可以通過一個寬字符串,或是一個UTF-8編碼的字符串來創建一個java端的String對象。
GetStringChars
GetStringUTFChars
這兩個函數用來取得與某個jstring對象相關的Java字符串,分別可以取得UTF-16編碼的寬字符串(jchar *)跟UTF8編碼的字符串(char *)。
const jchar* GetStringChars(jstring str,jboolean* copied)
const char* GetStringUTFChars(jstring str,jboolean* copied)
第一個參數:傳入一個指向java中的string對象的jstring變量。
第二個參數:傳入的是一個jboolean的指針。
這兩個函數分別都會有兩個不同的動作:
1.開闢新內存,然後把java中的String拷貝到這個內存中,然後返回指向這個內存的址的指針。
2. 直接返回指向Java的String的內存的指針,這個時候千萬不要改變這個內存的內容,這將破壞String的Java中始終是常量這個原則。
第二個參數是用來標示是否對Java的String對象進行了拷貝的。
如果傳入的這個jboolean指針不是NULL,則它會給該指針所指向的內存傳入JNI_TRUE或JNI_FALSE標示是否進行了拷貝。
傳入NULL表示不關心 是否拷貝字符串,它就不會給jboolean* 指向的內存賦值。
使用這兩個函數取得的字符串,在不使用的時候,要使用ReleaseStringChars/ReleaseStringUTFChars來釋放拷貝的內存,或是釋放對Java的String對象的引用。
ReleaseStringChars(jstring jstr, const jchar * str);
ReleaseStringUTFChars(jstring jstr,const char * str);
第一個參數指定一個jstring變量,即是要釋放的本地字符串的來源。
第二個對數就是本地字符串。

GetStringCtritical
爲了增加直接會加指向Java字符串的指針的可能性(而不是拷貝),JDK1.2出了新的函數
const jchar* GetStringCtritical(jstring str,jboolean* copied)
void ReleaseStringCritical(jstring jstr, const jchar* str);
在GetStringCtritical/ReleaseStringCritical之間是一個關鍵區,在這個關鍵區之中絕對不能呼叫JNI的其它函數和會造成當前線程中斷或會讓當前線程等待的任何本地代碼。否則將造成關鍵區代碼執行期間垃圾回收器停止動作,任何觸發垃圾回收器的線程也會暫停,其它的觸發垃圾回收器的線程不能前進直到當前線程結束而激活垃圾回收器。
在關鍵區中千萬不要出現中斷操作,或是在JVM中分配任何新對象,否則會造成JVM死鎖。

GetStringRegion
GetStringUTFRegion
這個函數的動作,是把Java字符串的內容直接拷貝到C/C++的字符數組中。
GetStringUTFRegion(jstring str,jsize start ,jsize len, char* buffer);  //拷貝Java字符串並以UTF-8編碼傳入buffer
GetStringRegion(jstring str, jsize start, jsize len,jchar* buffer); //拷貝Java字符串並以UTF-16編碼傳入buffer
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章