java本地接口調用基礎篇一(共四篇)


java代碼訪問本地代碼(c/c++

JNI: Java Native Interface(調用c/c++/其他本地代碼,該接口提供了javaos本地代碼互相調用的功能。

  >首先在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++中表示屬性和方法,JNIjNI.h頭文件中定義了jfieldID,jmethodID類型來分別代表java端的屬性和方法

.我們在訪問,或是設置java屬性的時候,首先就要先在本地代碼取得代表該java屬性的jfieldID,然后才能在本地代碼進行java屬性操作。同樣的,我們需要呼叫java端的方法是,也是需要取得代表該方法jmethodID才能進行java方法的調用。

.可以使用JNIEnvGetFieldID()/GetMethodIDGetStaticFieldID/GetStaticMethodID來取得相應的就fieldIDjmethodID。(jmethodIDjfieldID取得普通的屬性和方法)

 

相關說明:

clazz為指定的java類;name為指定的java類的屬性或方法名稱;其實這類似Reflect(反射),需要指定類跟屬性、方法名來取得相應的類、屬性、方法。而sign就是指定取得具體屬性和方法的[參數類型和返參]類型。

 

 

 

 

 

 

GetMethodID也能夠取得構造函數的jmethodID。創建一個java對象可以調用指定的構造方法。

1env->GetMethodID(data_Clazz, “<init>”, “()V”);

2package 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簽名:可以使用JDKjavap命令,其使用方式跟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一類的類型,如:IntShort等。

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>MethodCallStatic<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變量時使用的。第三種是當調用這個函數的時候有一個指向jvaluejvalue數組的指針時用的。

例 1jmethodID max_id = env->GetMethodID(clazzNativeCode, "max", "(DD)D");

  jdouble maxValue = env->CallDoubleMethod(obj, max_id, 3.14, 3.15 );

  cout<<maxValue<<endl;

   例 2java 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();

  }

}注意:FatherChild類沒有粘貼進來。

     jclass  clazzNativeCode = env->GetObjectClass(ogj);

  jfieldID id_p = env->GetFieldID(clazzNativeCode, "p", "LFather;");//p這個對象被聲明在了javaNativeCode中了,所以先要獲得這個屬性的id

  jobject p = env->GetObjectField(ogj, id_p);//因為pFather類的對象,所以此處定義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);

(篇一完)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM