JNI編程(二) —— 讓C++和Java相互調用(1)

本身在外面偷偷的算了下,又有將近兩個月沒更新過blog了。趁着今天有興致,來更新JNI編程的第二篇文章。在第一篇裏,大概介紹了JNI的特色、用途和優劣。而且作一個最簡單的JNI的例子,不過說實話那個例子在實際的開發中沒有太大的價值,實際開發中所須要的JNI程序要遠遠比那個複雜。因此這一篇就來介紹下如何經過JNI實現java和C++的相互通訊,來知足實際開發的須要。前端

 

所謂」通訊「,其實說白了無非也就是咱們所說的方法調用,在上一篇的例子裏介紹瞭如何在Java中調用本地的DLL,其實在Java代碼中,除了對本地方法標註native關鍵字和加上要加載動態連接庫以外,JNI基本上是對上層coder透明的,上層coder調用那些本地方法的時候並不知道這個方法的方法體到底是在哪裏,這個道理就像咱們用JDK所提供的API同樣。因此在Java中使用JNI仍是很簡單的,相比之下在C++中調用java,就比前者要複雜的多了。java

 

如今來介紹下JNI裏的數據類型。在C++裏,編譯器會很據所處的平臺來爲一些基本的數據類型來分配長度,所以也就形成了平臺不一致性,而這個問題在Java中則不存在,由於有JVM的緣故,因此Java中的基本數據類型在全部平臺下獲得的都是相同的長度,好比int的寬度永遠都是32位。基於這方面的緣由,java和c++的基本數據類型就須要實現一些mapping,保持一致性。下面的表能夠歸納:ios

 

    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

 

上面的表格是我在網上搜的,放上來給你們對比一下。對於每一種映射的數據類型,JNI的設計者其實已經幫咱們取好了相應的別名以方便記憶。若是想了解一些更加細緻的信息,能夠去看一些jni.h這個頭文件,各類數據類型的定義以及別名就被定義在這個文件中。c++

 

瞭解了JNI中的數據類型,下面就來看此次的例子。此次咱們用Java來實現一個前端的market(如下就用Foreground代替)用CPP來實現一個後端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.");  
    }  
}  

 

 

這個類裏面包含4個本地方法,一個靜態初始化塊加載將要生成的dll文件。剩下的方法都是很普通的java方法,等會在backend中回調這些方法。這個類須要一個名爲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;  
    }  
}  

 

 

JavaBean中,咱們爲兩個私有屬性賦值,方便後面的例子演示。到此爲止除了測試代碼以外的Java端的代碼就所有高調了,接下來進行生成.h頭文件、創建C++工程的工做,在這裏就一筆帶過,不熟悉的朋友請回頭看第一篇。在工程裏咱們新建一個名爲Foctory的C++ source file 文件,去實現那些native方法。具體的代碼以下。app

#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中的四個本地方法在這裏所有被實現,接下來針對這四個方法來解釋下,一些JNI相關的API的使用方法。先從第一個方法講起吧:dom

 

1.getPrice(String name)函數

 

這個方法是從foreground傳遞一個類型爲string的參數到backend,而後backend判斷返回相應的價格。在cpp的代碼中,咱們用GetStringUTFChars這個方法來把傳來的jstring變成一個UTF-8編碼的char型字符串。由於jstring的實際類型是jobject,因此沒法直接比較。工具

GetStringUTFChars方法包含兩個參數,第一參數是你要處理的jstring對象,第二個參數是否須要在內存中生成一個副本對象。將jstring轉換成爲了一個const char*了以後,咱們用string.h中帶strcmp函數來比較這兩個字符串,若是傳來的字符串是「Apple」的話咱們返回1.2。反之返回2.1。在這裏還要多說一下ReleaseStringUTFChars這個函數,這個函數從字面上不難理解,就是釋放內存用的。有點像cpp裏的析構函數,只不過Sun幫咱們已經封裝好了。因爲在JVM中有GC這個東東,因此多數java coder並無寫析構的習慣,不過在JNI裏是必須的了,不然容易形成內存泄露。咱們在這裏在release以前和以後分別打出這個字符串來看一下效果。

 

粗略的解釋完一些API以後,咱們編寫測試代碼。

 

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。所謂「指定」,其實也就是指方法裏的兩個參數:name和amout,在cpp的代碼在中,會根據傳遞的兩個參數來構造一個Order。回到cpp的代碼裏。

 

Java代碼   收藏代碼
  1. 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);  

 

 

拿到"類對象"了以後,按照Java RTTI的邏輯咱們接下來就要喚醒那個類對象的構造函數了。在JNI中,包括構造函數在內的全部方法都被當作Method。每一個method都有一個特定的ID,咱們經過GetMethodID這個方法就能夠拿到咱們想要的某一個java 方法的ID。GetMethodID須要傳三個參數,第一個是很顯然jclass,第二個參數是java方法名,也就是你想取的method ID的那個方法的方法名(有些繞口 ),第三個參數是方法簽名。

 

 在這裏有必要單獨來說一講這個方法簽名,爲何要用這個東東呢?咱們知道,在Java裏方法是能夠被重載的,好比我一個類裏有public void a(int arg)和public void a(String arg)這兩個方法,在這裏用方法名來區分方法顯然就是行不通的了。方法簽名包括兩部分:參數類型和返回值類型;具體的格式:(參數1類型簽名 參數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。 全部方法簽名都會被輸出,關於javap的一些參數能夠在控制檯下面輸入 javap -help查看。(作coder的 畢竟仍是要認幾個單詞的)

 

囉嗦了一大堆,仍是回到咱們剛剛的getMethodID這個方法上。由於是調用構造函數,JNI規定調用構造函數的時候傳遞的方法名應該爲<init> ,經過javap查看 咱們要的那個無參的構造函數的方法籤是()V。獲得方法簽名,最後咱們調用NewObject方法來生成一個新的對象。

 

拿到了對象,以後咱們開始爲對象jobject填充數值,仍是首先拿到setXXX方法的Method ID,以後調用Call<Type>Method來調用java方法。這裏的<Type>所指的是方法的返回類型,咱們剛剛調用的是set方法的返回值是void,所以這裏的方法也就是CallVoidMethod,這個方法的參數除了前兩個要傳入jobject和jmethodID以外還要傳入要調用的那個方法的參數,並且要順序必須一致,這點和Java的反射如出一轍,在這裏就很少解釋。(看到這一步是否是對java 反射又有了本身新的理解?)

 

終於介紹完了第二個方法,下來就是測試代碼測試。

Order o = b.getOrder("Watermelom", 100);  
System.out.println("java: " + o.getName());  
System.out.println("java: " + o.getAmount());  

 

 

控制檯打出

 

java: Watermelom
java: 100

 

就此,咱們完成了第二個方法的測試。

 

鑑於篇幅的關係,第三個 第四個方法的解釋放到下一篇裏解釋。忽然發現這篇寫的貌似有點長了,再寫下去的話臭雞蛋和番茄就飄過來了 , 具體的代碼也會在下一篇裏上傳。

相關文章
相關標籤/搜索