JNI其實是Java Native
Interface的簡稱,也就是java本地接口,。它提供了若干的API實現(xiàn)了和Java和其他語言的通信(主要是C&C++),。也許不少人覺
得Java已經(jīng)足夠強大,,為什么要需要JNI這種東西呢,?我們知道Java是一種平臺無關(guān)性的語言,,平臺對于上層的java代碼來說是透明的,所以在多數(shù)
時間我們是不需要JNI的,,但是假如你遇到了如下的三種情況之一呢,?
- 你的Java代碼,,需要得到一個文件的屬性。但是你找遍了JDK幫助文檔也找不到相關(guān)的API,。
- 在本地還有一個別的系統(tǒng),,不過他不是Java語言實現(xiàn)的,這個時候你的老板要求你把兩套系統(tǒng)整合到一起,。
- 你的Java代碼中需要用到某種算法,,不過算法是用C實現(xiàn)并封裝在動態(tài)鏈接庫文件(DLL)當中的。
對于上述的三種情況,,如果沒有JNI的話,,那就會變得異常棘手了。就算找到解決方案了,,也是費時費力。其實說到底還是會增加開發(fā)和維護的成本,。
說了那么多一通廢話,,現(xiàn)在進入正題??催^JDK源代碼的人肯定會注意到在源碼里有很多標記成native的方法,。這些個方法只有方法簽名但是沒有方
法體。其實這些naive方法就是我們說的 java native
interface,。他提供了一個調(diào)用(invoke)的接口,,然后用C或者C++去實現(xiàn)。我們首先來編寫這個“橋梁”.我自己的開發(fā)環(huán)境是
j2sdk1.4.2_15 + eclipse 3.2 + VC++
6.0,,先在eclipse里建立一個HelloFore的Java工程,,然后編寫下面的代碼。
- package com.chnic.jni;
-
- public class SayHellotoCPP {
-
- public SayHellotoCPP(){
- }
- public native void sayHello(String name);
- }
package com.chnic.jni;
public class SayHellotoCPP {
public SayHellotoCPP(){
}
public native void sayHello(String name);
}
一般的第一個程序總是HelloWorld,。今天換換口味,,把world換成一個名字。我的native本地方法有一個String的參數(shù),。會傳
遞一個name到后臺去,。本地方法已經(jīng)完成,現(xiàn)在來介紹下javah這個方法,,接下來就要用javah方法來生成一個相對應的.h頭文件,。
javah是一個專門為JNI生成頭文件的一個命令。CMD打開控制臺之后輸入javah回車就能看到j(luò)avah的一些參數(shù),。在這里就不多介紹
我們要用的是
-jni這個參數(shù),,這個參數(shù)也是默認的參數(shù),他會生成一個JNI式的.h頭文件,。在控制臺進入到工程的根目錄,,也就是HelloFore這個目錄,然后輸
入命令,。
- javah -jni com.chnic.jni.SayHellotoCPP
javah -jni com.chnic.jni.SayHellotoCPP
命令執(zhí)行完之后在工程的根目錄就會發(fā)現(xiàn)com_chnic_jni_SayHellotoCPP.h
這個頭文件,。在這里有必要多句嘴,在執(zhí)行javah的時候,,要輸入完整的包名+類名,。否則在以后的測試調(diào)用過程中會發(fā)生java.lang.UnsatisfiedLinkError這個異常,。
到這里java部分算是基本完成了,接下來我們來編寫后端的C++代碼,。(用C也可以,,只不過cout比printf用起來更快些,所以這里俺偷下
懶用C++)打開VC++首先新建一個Win32 Dynamic-Link library工程,,之后選擇An empty DLL
project空工程,。在這里我C++的工程是HelloEnd,把剛剛生成的那個頭文件拷貝到這個工程的根目錄里,。隨便用什么文本編輯器打開這個頭文
件,,發(fā)現(xiàn)有一個如下的方法簽名。
-
-
-
-
-
- JNIEXPORT void JNICALL Java_com_chnic_jni_SayHellotoCPP_sayHello
- (JNIEnv *, jobject, jstring);
/*
* Class: com_chnic_jni_SayHellotoCPP
* Method: sayHello
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_chnic_jni_SayHellotoCPP_sayHello
(JNIEnv *, jobject, jstring);
仔細觀察一下這個方法,,在注釋上標注類名,、方法名、簽名(Signature),,至于這個簽名是做什么用的,,我們以后再說。在這里最重要的是
Java_com_chnic_jni_SayHellotoCPP_sayHello這個方法簽名,。在Java端我們執(zhí)行
sayHello(String
name)這個方法之后,,JVM就會幫我們喚醒在DLL里的Java_com_chnic_jni_SayHellotoCPP_sayHello這個方
法。因此我們新建一個C++ source file來實現(xiàn)這個方法,。
- #include <iostream.h>
- #include "com_chnic_jni_SayHellotoCPP.h"
-
-
- JNIEXPORT void JNICALL Java_com_chnic_jni_SayHellotoCPP_sayHello
- (JNIEnv* env, jobject obj, jstring name)
- {
- const char* pname = env->GetStringUTFChars(name, NULL);
- cout << "Hello, " << pname << endl;
- }
#include <iostream.h>
#include "com_chnic_jni_SayHellotoCPP.h"
JNIEXPORT void JNICALL Java_com_chnic_jni_SayHellotoCPP_sayHello
(JNIEnv* env, jobject obj, jstring name)
{
const char* pname = env->GetStringUTFChars(name, NULL);
cout << "Hello, " << pname << endl;
}
因為我們生成的那個頭文件是在C++工程的根目錄不是在環(huán)境目錄,,所以我們要把尖括號改成單引號,至于VC++的環(huán)境目錄可以在
Tools->Options->Directories里設(shè)置,。F7編譯工程發(fā)現(xiàn)缺少jni.h這個頭文件,。這個頭文件可以
在%JAVA_HOME%\include目錄下找到。把這個文件拷貝到C++工程目錄,,繼續(xù)編譯發(fā)現(xiàn)還是找不到,。原來是因為在我們剛剛生成的那個頭文件
里,jni.h這個文件是被 #include <jni.h>引用進來的,,因此我們把尖括號改成雙引號#include
"jni.h",,繼續(xù)編譯發(fā)現(xiàn)少了jni_md.h文件,接著在%JAVA_HOME%\include\win32下面找到那個頭文件,,放入到工程根目
錄,,F(xiàn)7編譯成功。在Debug目錄里會發(fā)現(xiàn)生成了HelloEnd.dll這個文件,。
這個時候后端的C++代碼也已經(jīng)完成,接下來的任務(wù)就是怎么把他們連接在一起了,,要讓前端的java程序“認識并找到”這個動態(tài)鏈接庫,,就必須把這個DLL放在windows path環(huán)境變量下面,。有兩種方法可以做到:
- 把這個DLL放到windows下面的sysytem32文件夾下面,這個是windows默認的path
- 復制你工程的Debug目錄,,我這里是C:\Program Files\Microsoft Visual Studio\MyProjects\HelloEnd\Debug這個目錄,,把這個目錄配置到User variable的Path下面。重啟eclipse,,讓eclipse在啟動的時候重新讀取這個path變量,。
比較起來,第二種方法比較靈活,,在開發(fā)的時候不用來回copy
dll文件了,,節(jié)省了很多工作量,所以在開發(fā)的時候推薦用第二種方法,。在這里我們使用的也是第二種,,eclipse重啟之后打開
SayHellotoCPP這個類。其實我們上面做的那些是不是是讓JVM能找到那些DLL文件,,接下來我們要讓我們自己的java代碼“認識”這個動態(tài)
鏈接庫,。加入System.loadLibrary("HelloEnd");這句到靜態(tài)初始化塊里。
- package com.chnic.jni;
-
- public class SayHellotoCPP {
-
- static{
- System.loadLibrary("HelloEnd");
- }
- public SayHellotoCPP(){
- }
- public native void sayHello(String name);
-
- }
package com.chnic.jni;
public class SayHellotoCPP {
static{
System.loadLibrary("HelloEnd");
}
public SayHellotoCPP(){
}
public native void sayHello(String name);
}
這樣我們的代碼就能認識并加載這個動態(tài)鏈接庫文件了,。萬事俱備,,只欠測試代碼了,接下來編寫測試代碼,。
- SayHellotoCPP shp = new SayHellotoCPP();
- shp.sayHello("World");
SayHellotoCPP shp = new SayHellotoCPP();
shp.sayHello("World");
我們不讓他直接Hello,,World。我們把World傳進去,,執(zhí)行代碼,。發(fā)現(xiàn)控制臺打印出來Hello, World這句話。就此一個最簡單的JNI程序已經(jīng)開發(fā)完成,。也許有朋友會對CPP代碼里的
- const char* pname = env->GetStringUTFChars(name, NULL);
const char* pname = env->GetStringUTFChars(name, NULL);
這句有疑問,,這個GetStringUTFChars就是JNI給developer提供的API,我們以后再講,。在這里不得不多句嘴,。
- 因為JNI有一個Native這個特點,一點有項目用了JNI,,也就說明這個項目基本不能跨平臺了,。
- JNI調(diào)用是相當慢的,在實際使用的之前一定要先想明白是否有這個必要,。
- 因為C++和C這樣的語言非常靈活,,一不小心就容易出錯,比如我剛剛的代碼就沒有寫析構(gòu)字符串釋放內(nèi)存,,對于java developer來說因為有了GC 垃圾回收機制,,所以大多數(shù)人沒有寫析構(gòu)函數(shù)這樣的概念,。所以JNI也會增加程序中的風險,增大程序的不穩(wěn)定性,。
其實在Java代碼中,,除了對本地方法標注native關(guān)鍵字和加上要加載動態(tài)鏈接庫之外,
JNI基本上是對上層coder透明的,,上層coder調(diào)用那些本地方法的時候并不知道這個方法的方法體究竟是在哪里,,這個道理就像我們用JDK所提供的API一樣。所以在Java中使用
JNI還是很簡單的,,相比之下在C++中調(diào)用java,,就比前者要復雜的多了。
現(xiàn)在來介紹下JNI里的數(shù)據(jù)類型,。在C++里,,編譯器會很據(jù)所處的平臺來為一些基本的
數(shù)據(jù)類型來分配長度,因此也就造成了平臺不一致性,,而這個問題在Java中則不存在,,因為有JVM的緣故,所以Java中的基本數(shù)據(jù)類型在所有平臺下得到
的都是相同的長度,,比如int的寬度永遠都是32位,。基于這方面的原因,,java和c++的基本數(shù)據(jù)類型就需要實現(xiàn)一些mapping,,保持一致性。下面
的表可以概括:
Java類型 |
本地類型 |
JNI中定義的別名 |
int |
long |
jint |
long |
_int64 |
jlong |
byte |
signed char |
jbyte |
boolean |
unsigned char |
jboolean |
char |
unsigned short |
jchar |
short |
short |
jshort |
float |
float |
jfloat |
double |
double |
jdouble |
Object |
_jobject* |
jobject |
上面的表格是我在網(wǎng)上搜的,,放上來給大家對比一下,。對于每一種映射的數(shù)據(jù)類型,JNI的設(shè)計者其實已經(jīng)幫我們?nèi)『昧讼鄳膭e名以方便記憶,。如果想了解一些更加細致的信息,,可以去看一些jni.h這個頭文件,各種數(shù)據(jù)類型的定義以及別名就被定義在這個文件中,。
了解了JNI中的數(shù)據(jù)類型,,下面就來看這次的例子。這次我們用Java來實現(xiàn)一個前端的market(以下就用Foreground代替)用CPP來實現(xiàn)一個后端factory(以下用backend代替),。我們首先還是來編寫包含本地方法的java類,。
- package com.chnic.service;
-
- import com.chnic.bean.Order;
-
- public class Business {
- static{
- System.loadLibrary("FruitFactory");
- }
-
- public Business(){
-
- }
-
- public native double getPrice(String name);
- public native Order getOrder(String name, int amount);
- public native Order getRamdomOrder();
- public native void analyzeOrder(Order order);
-
- public void notification(){
- System.out.println("Got a notification.");
- }
-
- public static void notificationByStatic(){
- System.out.println("Got a notification in a static method.");
- }
- }
package com.chnic.service;
import com.chnic.bean.Order;
public class Business {
static{
System.loadLibrary("FruitFactory");
}
public Business(){
}
public native double getPrice(String name);
public native Order getOrder(String name, int amount);
public native Order getRamdomOrder();
public native void analyzeOrder(Order order);
public void notification(){
System.out.println("Got a notification.");
}
public static void notificationByStatic(){
System.out.println("Got a notification in a static method.");
}
}
這個類里面包含4個本地方法,一個靜態(tài)初始化塊加載將要生成的dll文件,。剩下的方法都是很普通的java方法,,等會在backend中回調(diào)這些方法。這個類需要一個名為Order的JavaBean。
- package com.chnic.bean;
-
- public class Order {
-
- private String name = "Fruit";
- private double price;
- private int amount = 30;
-
- public Order(){
-
- }
-
- public int getAmount() {
- return amount;
- }
-
- public void setAmount(int amount) {
- this.amount = amount;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public double getPrice() {
- return price;
- }
-
- public void setPrice(double price) {
- this.price = price;
- }
- }
package com.chnic.bean;
public class Order {
private String name = "Fruit";
private double price;
private int amount = 30;
public Order(){
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
JavaBean中,,我們?yōu)閮蓚€私有屬性賦值,,方便后面的例子演示,。到此為止除了測試代碼之外的Java端的代碼就全部高調(diào)了,,接下來進行生成.h
頭文件、建立C++工程的工作,,在這里就一筆帶過,,不熟悉的朋友請回頭看第一篇。在工程里我們新建一個名為Foctory的C++ source
file 文件,,去實現(xiàn)那些native方法,。具體的代碼如下。
- #include <iostream.h>
- #include <string.h>
- #include "com_chnic_service_Business.h"
-
- jobject getInstance(JNIEnv* env, jclass obj_class);
-
- JNIEXPORT jdouble JNICALL Java_com_chnic_service_Business_getPrice(JNIEnv* env,
- jobject obj,
- jstring name)
- {
- const char* pname = env->GetStringUTFChars(name, NULL);
- cout << "Before release: " << pname << endl;
-
- if (strcmp(pname, "Apple") == 0)
- {
- env->ReleaseStringUTFChars(name, pname);
- cout << "After release: " << pname << endl;
- return 1.2;
- }
- else
- {
- env->ReleaseStringUTFChars(name, pname);
- cout << "After release: " << pname << endl;
- return 2.1;
- }
- }
-
-
- JNIEXPORT jobject JNICALL Java_com_chnic_service_Business_getOrder(JNIEnv* env,
- jobject obj,
- jstring name,
- jint amount)
- {
- jclass order_class = env->FindClass("com/chnic/bean/Order");
- jobject order = getInstance(env, order_class);
-
- jmethodID setName_method = env->GetMethodID(order_class, "setName", "(Ljava/lang/String;)V");
- env->CallVoidMethod(order, setName_method, name);
-
- jmethodID setAmount_method = env->GetMethodID(order_class, "setAmount", "(I)V");
- env->CallVoidMethod(order, setAmount_method, amount);
-
- return order;
- }
-
- JNIEXPORT jobject JNICALL Java_com_chnic_service_Business_getRamdomOrder(JNIEnv* env,
- jobject obj)
- {
- jclass business_class = env->GetObjectClass(obj);
- jobject business_obj = getInstance(env, business_class);
-
- jmethodID notification_method = env->GetMethodID(business_class, "notification", "()V");
- env->CallVoidMethod(obj, notification_method);
-
- jclass order_class = env->FindClass("com/chnic/bean/Order");
- jobject order = getInstance(env, order_class);
- jfieldID amount_field = env->GetFieldID(order_class, "amount", "I");
- jint amount = env->GetIntField(order, amount_field);
- cout << "amount: " << amount << endl;
- return order;
- }
-
-
- JNIEXPORT void JNICALL Java_com_chnic_service_Business_analyzeOrder (JNIEnv* env,
- jclass cls,
- jobject obj)
- {
- jclass order_class = env->GetObjectClass(obj);
- jmethodID getName_method = env->GetMethodID(order_class, "getName", "()Ljava/lang/String;");
- jstring name_str = static_cast<jstring>(env->CallObjectMethod(obj, getName_method));
- const char* pname = env->GetStringUTFChars(name_str, NULL);
-
- cout << "Name in Java_com_chnic_service_Business_analyzeOrder: " << pname << endl;
- jmethodID notification_method_static = env->GetStaticMethodID(cls, "notificationByStatic", "()V");
- env->CallStaticVoidMethod(cls, notification_method_static);
-
- }
-
- jobject getInstance(JNIEnv* env, jclass obj_class)
- {
- jmethodID construction_id = env->GetMethodID(obj_class, "<init>", "()V");
- jobject obj = env->NewObject(obj_class, construction_id);
- return obj;
- }
#include <iostream.h>
#include <string.h>
#include "com_chnic_service_Business.h"
jobject getInstance(JNIEnv* env, jclass obj_class);
JNIEXPORT jdouble JNICALL Java_com_chnic_service_Business_getPrice(JNIEnv* env,
jobject obj,
jstring name)
{
const char* pname = env->GetStringUTFChars(name, NULL);
cout << "Before release: " << pname << endl;
if (strcmp(pname, "Apple") == 0)
{
env->ReleaseStringUTFChars(name, pname);
cout << "After release: " << pname << endl;
return 1.2;
}
else
{
env->ReleaseStringUTFChars(name, pname);
cout << "After release: " << pname << endl;
return 2.1;
}
}
JNIEXPORT jobject JNICALL Java_com_chnic_service_Business_getOrder(JNIEnv* env,
jobject obj,
jstring name,
jint amount)
{
jclass order_class = env->FindClass("com/chnic/bean/Order");
jobject order = getInstance(env, order_class);
jmethodID setName_method = env->GetMethodID(order_class, "setName", "(Ljava/lang/String;)V");
env->CallVoidMethod(order, setName_method, name);
jmethodID setAmount_method = env->GetMethodID(order_class, "setAmount", "(I)V");
env->CallVoidMethod(order, setAmount_method, amount);
return order;
}
JNIEXPORT jobject JNICALL Java_com_chnic_service_Business_getRamdomOrder(JNIEnv* env,
jobject obj)
{
jclass business_class = env->GetObjectClass(obj);
jobject business_obj = getInstance(env, business_class);
jmethodID notification_method = env->GetMethodID(business_class, "notification", "()V");
env->CallVoidMethod(obj, notification_method);
jclass order_class = env->FindClass("com/chnic/bean/Order");
jobject order = getInstance(env, order_class);
jfieldID amount_field = env->GetFieldID(order_class, "amount", "I");
jint amount = env->GetIntField(order, amount_field);
cout << "amount: " << amount << endl;
return order;
}
JNIEXPORT void JNICALL Java_com_chnic_service_Business_analyzeOrder (JNIEnv* env,
jclass cls,
jobject obj)
{
jclass order_class = env->GetObjectClass(obj);
jmethodID getName_method = env->GetMethodID(order_class, "getName", "()Ljava/lang/String;");
jstring name_str = static_cast<jstring>(env->CallObjectMethod(obj, getName_method));
const char* pname = env->GetStringUTFChars(name_str, NULL);
cout << "Name in Java_com_chnic_service_Business_analyzeOrder: " << pname << endl;
jmethodID notification_method_static = env->GetStaticMethodID(cls, "notificationByStatic", "()V");
env->CallStaticVoidMethod(cls, notification_method_static);
}
jobject getInstance(JNIEnv* env, jclass obj_class)
{
jmethodID construction_id = env->GetMethodID(obj_class, "<init>", "()V");
jobject obj = env->NewObject(obj_class, construction_id);
return obj;
}
可以看到,,在我Java中的四個本地方法在這里全部被實現(xiàn),,接下來針對這四個方法來解釋下,一些JNI相關(guān)的API的使用方法,。先從第一個方法講起吧:
1.getPrice(String name)
這個方法是從foreground傳遞一個類型為string的參數(shù)到backend,,然后backend判斷返回相應的價格。在cpp的代碼中,,
我們用GetStringUTFChars這個方法來把傳來的jstring變成一個UTF-8編碼的char型字符串,。因為jstring的實際類型是
jobject,所以無法直接比較,。
GetStringUTFChars方法包含兩個參數(shù),,第一參數(shù)是你要處理的jstring對象,第二個參數(shù)是否需要在內(nèi)存中生成一個副本對象,。將
jstring轉(zhuǎn)換成為了一個const
char*了之后,,我們用string.h中帶strcmp函數(shù)來比較這兩個字符串,如果傳來的字符串是“Apple”的話我們返回1.2,。反之返回
2.1,。在這里還要多說一下ReleaseStringUTFChars這個函數(shù),這個函數(shù)從字面上不難理解,,就是釋放內(nèi)存用的,。有點像cpp里的析構(gòu)函
數(shù),只不過Sun幫我們已經(jīng)封裝好了,。由于在JVM中有GC這個東東,,所以多數(shù)java coder并沒有寫析構(gòu)的習慣,不過在JNI里是必須的了,,否則容易造成內(nèi)存泄露,。我們在這里在release之前和之后分別打出這個字符串來看一下效果。
粗略的解釋完一些API之后,我們編寫測試代碼,。
- Business b = new Business();
- System.out.println(b.getPrice("Apple"));
Business b = new Business();
System.out.println(b.getPrice("Apple"));
運行這段測試代碼,,控制臺上打出
Before release: Apple
After release: ??
1.2
在release之前打印出來的是我們“需要”的Apple,release之后就成了亂碼了,。由于傳遞的是Apple,,所以得到1.2。測試成功,。
2. getOrder(String name, int amount)
在foreground中可以通過這個方法讓backend返回一個你“指定”的Order,。所謂“指定”,其實也就是指方法里的兩個參數(shù):name和amout,,在cpp的代碼在中,,會根據(jù)傳遞的兩個參數(shù)來構(gòu)造一個Order?;氐絚pp的代碼里,。
- jclass order_class = env->FindClass("com/chnic/bean/Order");
jclass order_class = env->FindClass("com/chnic/bean/Order");
是不是覺得這句代碼似曾相識?沒錯,,這句代碼很像我們java里寫的Class.forName(className)反射的代碼,。其實在這里
FindClass的作用和上面的forName是類似的。只不過在forName中要用完整的類名,,但是在這里必須用"/"來代替“.”,。這個方法會返
回一個jclass的對象,其實也就是我們在Java中說的類對象,。
- jmethodID construction_id = env->GetMethodID(obj_class, "<init>", "()V");
- jobject obj = env->NewObject(obj_class, construction_id);
jmethodID construction_id = env->GetMethodID(obj_class, "<init>", "()V");
jobject obj = env->NewObject(obj_class, construction_id);
拿到"類對象"了之后,,按照Java RTTI的邏輯我們接下來就要喚醒那個類對象的構(gòu)造函數(shù)了。在JNI中,,包括構(gòu)造函數(shù)在內(nèi)的所有方法都被看成Method,。每個method都有一個特定的ID,我們通過GetMethodID這個方法就可以拿到我們想要的某一個java 方法的ID,。GetMethodID需要傳三個參數(shù),,第一個是很顯然jclass,第二個參數(shù)是java方法名,,也就是你想取的method ID的那個方法的方法名(有些繞口
),,第三個參數(shù)是方法簽名。
在這里有必要單獨來講一講這個方法簽名,,為什么要用這個東東呢,?我們知道,在Java里方法是可以被重載的,,比如我一個類里有public
void a(int arg)和public void a(String
arg)這兩個方法,,在這里用方法名來區(qū)分方法顯然就是行不通的了,。方法簽名包括兩部分:參數(shù)類型和返回值類型;具體的格式:(參數(shù)1類型簽名
參數(shù)2類型簽名)返回值類型簽名,。下面是java類型和年名類型的對照的一個表
Java類型 |
對應的簽名 |
boolean |
Z |
byte |
B |
char |
C |
shrot |
S |
int |
I |
long |
L |
float |
F |
double |
D |
void |
V |
Object |
L用/分割包的完整類名; Ljava/lang/String; |
Array |
[簽名 [I [Ljava/lang/String; |
其實除了自己對照手寫之外,,JDK也提供了一個很好用的生成簽名的工具javap,cmd進入控制臺到你要生成簽名的那個類的目錄下。在這里用
Order類打比方,,敲入: javap -s -private Order,。
所有方法簽名都會被輸出,關(guān)于javap的一些參數(shù)可以在控制臺下面輸入 javap -help查看,。(做coder的 畢竟還是要認幾個單詞的)
啰嗦了一大堆,,還是回到我們剛剛的getMethodID這個方法上,。因為是調(diào)用構(gòu)造函數(shù),,JNI規(guī)定調(diào)用構(gòu)造函數(shù)的時候傳遞的方法名應該為<init>
,通過javap查看 我們要的那個無參的構(gòu)造函數(shù)的方法簽是()V。得到方法簽名,,最后我們調(diào)用NewObject方法來生成一個新的對象,。
拿到了對象,之后我們開始為對象jobject填充數(shù)值,,還是首先拿到setXXX方法的Method
ID,,之后調(diào)用Call<Type>Method來調(diào)用java方法。這里的<Type>所指的是方法的返回類型,,我們剛剛調(diào)用
的是set方法的返回值是void,,因此這里的方法也就是CallVoidMethod,這個方法的參數(shù)除了前兩個要傳入jobject和
jmethodID之外還要傳入要調(diào)用的那個方法的參數(shù),,而且要順序必須一致,,這點和Java的反射一模一樣,在這里就不多解釋,。(看到這一步是不是對
java 反射又有了自己新的理解,?)
終于介紹完了第二個方法,下來就是測試代碼測試,。
- Order o = b.getOrder("Watermelom", 100);
- System.out.println("java: " + o.getName());
- System.out.println("java: " + o.getAmount());
Order o = b.getOrder("Watermelom", 100);
System.out.println("java: " + o.getName());
System.out.println("java: " + o.getAmount());
控制臺打出
java: Watermelom
java: 100
就此,,我們完成了第二個方法的測試。
3.getRamdomOrder()
這個方法會從backend得到一個隨機的Order對象(抱歉這里“Random”拼錯了),,然后再調(diào)用java中相應的通知方法來通知
foreground,。getRamdomOrder方法沒有參數(shù),但是所對應的C++方法里卻有兩個參數(shù),,一定有人會不解,。其實細心的朋友一定會發(fā)
現(xiàn),JNI里所有對應Java方法的C++
方法都會比Java方法多兩個參數(shù),,第一個參數(shù)是我們很熟悉的JNIEnv*指針,,第二個參數(shù)有時是jobject有時是個jclass。針對這第二個參
數(shù)在這里有必要多廢話兩句。
其實第二個參數(shù)傳遞的是包含了native本地方法的對象或者類對象,,我們知道非靜態(tài)的方法是屬于某一個對象的,,而靜態(tài)方法是屬于類對象的,所以靜
態(tài)方法可以被所有對象共享,。有這個對象/類對象,,我們就可以很方便的操作包含了native方法的對象的一些函數(shù)了。(這句話有點繞口,,沒看明白的建議多
讀兩遍),。
廢話完了言歸正傳,因為getRamdomOrder不是靜態(tài)的,,所以C++相對應的參數(shù)中傳遞來的是一個jobject對象,。
- jclass business_class = env->GetObjectClass(obj);
jclass business_class = env->GetObjectClass(obj);
這一句不難理解,GetObjectClass方法可以得到一個對象的類對象,,這句有點像Java中的Object.class,。不熟悉的朋友建議
再去看一下Java反射機制。接下來的幾句C++代碼應該在之前的方法1和方法2中都解釋過,。早backend端會發(fā)一個“消息”給
foreground,,之后new一個新的Order類出來。接下來的三句有必要再廢話一下,。
- jfieldID amount_field = env->GetFieldID(order_class, "amount", "I");
- jint amount = env->GetIntField(order, amount_field);
- cout << "amount: " << amount << endl;
jfieldID amount_field = env->GetFieldID(order_class, "amount", "I");
jint amount = env->GetIntField(order, amount_field);
cout << "amount: " << amount << endl;
之前我為Order這個Javabean的amount的屬性設(shè)置了一個初始值為30,,其實就是為了在這里演示如何在C++中拿一個Java對象的
屬性,拿的方法和我們之前說過的調(diào)用Java方法的程序差不多,,也要先拿到一個jfieldID,,之后調(diào)用Get<type>Field方法
來取得某一個對象中的某一個屬性的數(shù)值,最后cout把他打印出來,。我們編寫測試代碼來看一下最終效果,。
- Business b = new Business();
- Order o2 = b.getRamdomOrder();
- System.out.println(o2.getName());
Business b = new Business();
Order o2 = b.getRamdomOrder();
System.out.println(o2.getName());
運行上述的測試代碼之后,控制臺上打出了
Got a notification.
amount: 30
Fruit
和我們想要的結(jié)果是一樣的,,測試成功,。
4.analyzeOrder(Order order)
這是一個靜態(tài)方法,foreground會通過這個方法傳一個Order的對象到backend去,,然后再由CPP端進行“analyze”,。在這
里我們?nèi)〕鰜韨鬟f過來的Order對象的name屬性,然后打印到控制臺上,。因為這個方法是靜態(tài)static方法,,所以相對應的C++方法中的第二個參數(shù)
也變成了jclass對象,也就是Business.class這個類對象,。第三個參數(shù)是一個jobject對象,,很明顯就是我們傳遞過來的order對
象,。
前5句代碼應該不難理解,就是調(diào)用getName這個方法,,然后打印出來,。因為JNI的API中并沒有提供CallStringMethod這個方
法,所以我們用CallObjectMethod這個方法來取得name這個字符串(String很明顯也是一個Object),,然后再轉(zhuǎn)型成為
jstring,。也就是下面這句代碼。
- jstring name_str = static_cast<jstring>(env->CallObjectMethod(obj, getName_method));
jstring name_str = static_cast<jstring>(env->CallObjectMethod(obj, getName_method));
取到了name這個字符串之后cout打印出來,,之后調(diào)用Business這個類對象中的靜態(tài)方法notificationByStatic來通知
foreground,。調(diào)用的流程以及方法和非靜態(tài)都是一樣的,只不過注意JNI中調(diào)用靜態(tài)方法的API所傳遞的一個參數(shù)是一個jclass而非
jobject(這個也不難理解,,因為靜態(tài)方法是屬于class類對象的)
還是編寫測試代碼測試這個方法
- Business b = new Business();
- Order o = b.getOrder("Watermelom", 100);
- Business.analyzeOrder(o);
Business b = new Business();
Order o = b.getOrder("Watermelom", 100);
Business.analyzeOrder(o);
控制臺上打印出
Name in Java_com_chnic_service_Business_analyzeOrder: Watermelom
Got a notification in a static method.
第一句是C++中cout打印出來的,,第二句則是Java中的靜態(tài)方法打印出來的,和我們想要的結(jié)果是一致的,。
呼~好不容易介紹完了4個方法,,最后總結(jié)一下吧。
- JNI中所提供的API遠遠不止這4個方法中所使用的API,。上面介紹的都是比較常用的,本人也不可能羅列出所有的API,。
- 了解了JNI編程更加有利于深入了解Java中的反射機制,,反之亦然。
因此如果有對JNI編程有興趣或者有更深入的需要,,可以參考一下sun的相關(guān)文檔,。在這里上傳sun提供的JNI的API手冊,還有上面例子中所用的演示代碼給大家參考,。