1. 程式人生 > >使用JNI技術實現Java和C/C++互調(C/C++調Java)

使用JNI技術實現Java和C/C++互調(C/C++調Java)

    上一篇部落格裡我們講了怎樣通過JNI和動態連結庫實現從Java調C/C++,今天我們來講怎麼在C/C++中獲取Java的資料型別以及呼叫Java的方法。

    接著上一篇所講,我們通過在Java程式碼中宣告native方法,再用javah工具生成標頭檔案,然後編寫標頭檔案中的函式,實現功能,再編譯成庫檔案,又Java虛擬機器在執行時動態載入和呼叫,那麼這次的重點就是如何編寫實現函式。

    在這之前,我先講一下關於Java的native方法在編譯成函式時的命名重整規則。熟悉C++朋友可能知道,C++支援函式過載,函式名可能是相同的,但引數不同,或者是類內函式,或者是名字空間內函式,有或者兼而有之。這就是通過命名重整做到得,系統在搜尋一個符號(函式)的地址時,無法區分兩個名字相同的符號,而在C語言中,函式名字就是其符號,這就導致了C語言無法過載相同名字的函式。而C++通過一套重整規則,把名字空間路徑,類名,返回值,函式名,引數,常量值等都作為命名的一部分,這就形成了一個函式獨有的函式簽名,也就有了唯一的標示。雖然因為編譯器對這套規則沒有統一的標準而引發很多問題,但卻能為我們今天要講的Java方法的命名帶來一些啟發。下面我們來看一段Java程式碼:

public class Hello {
	public native static void hello();
	public native static void hello(String s);
	public native void hello(int i);
	public native String hello(char c);
}
    在這個類中,我分別定義了幾個函式名相同,但函式簽名不同的方法。接下來我們生成對應的標頭檔案:
#include <jni.h>
/* Header for class Hello */

#ifndef _Included_Hello
#define _Included_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     Hello
 * Method:    hello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_Hello_hello__
  (JNIEnv *, jclass);

/*
 * Class:     Hello
 * Method:    hello
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_Hello_hello__Ljava_lang_String_2
  (JNIEnv *, jclass, jstring);

/*
 * Class:     Hello
 * Method:    hello
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_Hello_hello__I
  (JNIEnv *, jobject, jint);

/*
 * Class:     Hello
 * Method:    hello
 * Signature: (C)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_Hello_hello__C
  (JNIEnv *, jobject, jchar);

#ifdef __cplusplus
}
#endif
#endif

    在註釋中可以看到函式對應的類名,方法名和引數返回值。可以看出Java函式是以Java_為字首,類名(包含包路徑),函式名,引數(類名或者型別名),過載限定符號__作為函式名。這裡要注意沒有返回值,因為返回值本質上不影響函式簽名,所以在重整的時候沒有納入。這裡我想說的是,JVM實際上就是根據這個命名去搜索需要呼叫的本地函式,所以我們編寫的庫中儘量不要包含相同名字的庫,也不要使用相近的命名方法,以防編譯錯誤或者執行時的不正常。這算是我們和JVM的一個約定。

    下面我們繼續看上面標頭檔案的程式碼,可以看到每個函式的第一個引數都是一個名為JNIEnv的型別,這個引數實際上就是呼叫本地函式的虛擬機器執行緒的環境資訊,也是JNI技術中底層程式碼呼叫Java方法的關鍵介面,我們在編寫JNI時最常用到的介面。但是現在我們先跳過它,晚點再講。可以看到第二個引數有jobject或者是jclass,從名字我們可以看出來,jobject就是一個具體的Java物件,而jclass是一個Java類的物件,就有點像Java中的Class類(即類的類),為什麼會有兩種不同的引數,其實看看Java程式碼就知道了,jobject的函式是類內函式(成員函式),jclass的函式是靜態函式,靜態函式是對整個類而言,而成員函式是對類的一個具體物件作用的。這有點像C++中的成員函式中隱式的傳進了一個this指標,而靜態函式沒有。而之後的引數就是真正的函式的形參對應的實參。可以看到一些像jint,jchar,jstring的型別。下面我們先開啟jni.h來看看這些型別是怎麼定義的,先看看基本型別:

typedef unsigned char   jboolean;
typedef unsigned short  jchar;
typedef short           jshort;
typedef float           jfloat;
typedef double          jdouble;

typedef jint            jsize;

    這幾行程式碼我就不解釋了,但可能眼尖的朋友會提出一個問題,為什麼沒有jint和jlong?這就要從作業系統的資料模型說起,對於不同位數的CPU,我們通常會安裝等於或小於其位數的作業系統,而我們的資料模型就是由這些作業系統定義的。其中用的最多的就是LP和LLP型別。為什麼jni標頭檔案沒有直接對int和long(或者說long long)定義的原因就在此。對於不同的資料型別,int有可能是16位,32位,甚至64位的,而long有可能是32位或64位的。這就導致了不一致性,Java為了避免這種狀況,把這些不一致的地方分在了jni_md.h這個標頭檔案中,讓我們來看看:
typedef int jint;
#ifdef _LP64 /* 64-bit Solaris */
typedef long jlong;
#else
typedef long long jlong;
#endif

    上面的程式碼是我的64位Linux上的程式碼,因為這個型別的系統int型別確定是32位,所以沒有分開定義,但是jlong卻不一樣,有的系統中long是32位(LLP64,比如Windows和一些類Unix系統),所以jlong就會被定義為long long型別(或者__int64,在Windows系統上),反之如果是LP64的就會被定義為long型別。

    在回到jni.h這個檔案中,我們下面來看一下類的定義:

#ifdef __cplusplus

class _jobject {};
class _jclass : public _jobject {};
class _jthrowable : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jobjectArray : public _jarray {};

typedef _jobject *jobject;
typedef _jclass *jclass;
typedef _jthrowable *jthrowable;
typedef _jstring *jstring;
typedef _jarray *jarray;
typedef _jbooleanArray *jbooleanArray;
typedef _jbyteArray *jbyteArray;
typedef _jcharArray *jcharArray;
typedef _jshortArray *jshortArray;
typedef _jintArray *jintArray;
typedef _jlongArray *jlongArray;
typedef _jfloatArray *jfloatArray;
typedef _jdoubleArray *jdoubleArray;
typedef _jobjectArray *jobjectArray;

    這裡最上面有一個巨集,指的是這塊是C++程式碼,因為在C中沒有類和繼承的概念,所以一這塊內容只在C++程式碼中存在,下面我們來看看C的版本:
#else

struct _jobject;

typedef struct _jobject *jobject;
typedef jobject jclass;
typedef jobject jthrowable;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jarray jobjectArray;

#endif

    熟悉C/C++程式設計的朋友應該知道這兩者是等價的。不過看到這裡應該又很多人有一個疑問,為什麼jobject,jclass和其它定義類都是空類,那麼我們在這頭拿到的不都是指向一個空類物件的指標嗎?是的,所以在C/C++中是無法直接訪問這些物件的,只能直接訪問基本型別(jint,jchar等)。那我們要如何來訪問Java物件,其實,在JVM呼叫本底方法時,就在一張本地程式登錄檔中把函式呼叫相關的引數變數物件和全域性變數註冊進去了,這張表不需要使用者自己呼叫,只要使用之前提到的JNIEnv指標去掉用就可以了,下面我們來看看這個指標的實現:
struct JNINativeInterface_;

struct JNIEnv_;

#ifdef __cplusplus
typedef JNIEnv_ JNIEnv;
#else
typedef const struct JNINativeInterface_ *JNIEnv;
#endif

struct JNINativeInterface_ {
    void *reserved0;
    void *reserved1;
    void *reserved2;

    void *reserved3;
    jint (JNICALL *GetVersion)(JNIEnv *env);

    jclass (JNICALL *DefineClass)
      (JNIEnv *env, const char *name, jobject loader, const jbyte *buf,
       jsize len);
    ......
};

struct JNIEnv_ {
    const struct JNINativeInterface_ *functions;
#ifdef __cplusplus

    jint GetVersion() {
        return functions->GetVersion(this);
    }
    jclass DefineClass(const char *name, jobject loader, const jbyte *buf,
                       jsize len) {
        return functions->DefineClass(this, name, loader, buf, len);
    }
    ......
};


    上面的程式碼很清晰的表達了JNIEnv的作用,就是作為Java層和本地庫層的一個通訊渠道,我們所需要的變數都可以通過它來獲取,關於JNIEnv的使用我只提一點,就是在C中是

    (*env)->xxx(xxx);

    而在C++中則是:

    env->xxx(xxx);

    這個差異在上面的程式碼中已經揭示,C++中的JNIEnv是JNIEnv_的型別定義,所以可以直接使用指標操作,而C中是JNINativeInterface的指標,所以要先解引用再使用。關於怎麼去呼叫JNIEnv可以參考官方文件,請記住傳進來的物件不能直接使用,因為他們只是一個標示,要獲得物件成員屬性還得依靠JNIEnv,這點是在JNI程式設計中一定要牢記並且要養成習慣的。

    今天就先講到這裡,更多關於JNI的資訊可以參考官方文件或者標頭檔案,甚至感興趣的朋友可以去下載jdk的原始碼來閱讀。只要記住一點,JNI是用來作為介面的,一般會在其他函式或者庫中實現功能。

相關推薦

使用JNI技術實現JavaC/C++調C/C++調Java

    上一篇部落格裡我們講了怎樣通過JNI和動態連結庫實現從Java調C/C++,今天我們來講怎麼在C/C++中獲取Java的資料型別以及呼叫Java的方法。     接著上一篇所講,我們通過在Java程式碼中宣告native方法,再用javah工具生成標頭檔案,然後編寫

c實現utf8gbk的

#include <iconv.h> #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include &

C# List string 的以逗號分隔

List轉字串,用逗號隔開 List<string> list = new List<string>(); list.Add("a"); list.Add("b"); list.Add("c"); //MessageBox.Show(list.);

C#GDI+自定義繪製曲線圖表控制元件DataChart 簡單實現 C#GDI+ 繪製線段實線或虛線、矩形、字串、圓、橢圓

C#GDI+自定義繪製曲線圖表控制元件DataChart 這裡只說明在計算刻度和曲線繪製的時候 只提供思路,只是做了下簡單的計算,不喜勿噴 還望見諒,高手直接飄過吧。這個要做好,還是需要研究研究演算法的,比如刻度隨著控制元件的大小發生改變的時候計算不同的值,根據刻度範圍來計算刻度以及刻度值等,這裡沒有研究,

secureCRT 實現windowslinux檔案

背景:   有一臺主機,安裝了windows10,然後伺服器安裝了ubuntu。在windows10上安裝SecureCRT來ssh連線ubuntu虛擬機器。一般在windows上面下載軟體要上傳到伺服器上使用,現在考慮使用sftp協議來直接傳輸。 方案:   使用Se

應用大資料機器學習技術實現車險全流程智慧化的方案 -理賠流程智慧化改造

一、簡要說明 本篇討論的是理賠環節用大資料和機器學習技術實現車險理賠流程的智慧化。理賠與承保不同,重點要放在風險控制方面(既包括外部風險控制,也包括內部風險控制),對於如何簡化理賠流程、提高理賠時效等提升客戶體驗等方面沒有必要採用承保減少人工干預的方法(PS:原因?自己想...)。 二、

應用大資料機器學習技術實現車險全流程智慧化的方案

應用大資料和機器學習技術實現車險全流程智慧化的方案(上) -承保流程智慧化改造   一、簡要說明 以技術替代人力的思路對車險全業務流程改造,即應用車險大資料和機器學習技術全部或部分替代承保理賠管理相關業務處理崗位,實現車險業務處理流程、風險識別與控制的智慧化。本篇只討論

base64圖片的HTML5的File實現)

剛接觸到一個內聯圖片的概念,內聯圖片即使把圖片檔案編碼成base64 看下面程式碼即是內聯問題 可以減少http的請求,缺點是不能跨域快取! ? 1 2 3 <img src="data:image/jpeg;ba

使用JAXB包實現beanxml的

前言 由於專案需要,呼叫第三方介面,介面返回格式為xml格式。遂用上了javax.xml 用於實現Bean和xml互轉 首先我們看看工具類XmlUtil /** * XML轉物件 * * @param xmlStr xml字串

用putty 連線Linux以及實現 windowslinux檔案

ssh原理 簡單說,SSH是一種網路協議,用於計算機之間的加密登入。 ssh原理參考文章 ssh客戶端工具 putty PuTTY小巧方便。但若需要向網路中的Linux系統上傳檔案,則可以使用PuTTY官方提供的PSCP工具來實現上傳。PSCP基於ss

利用ajaxJSP技術實現網頁中表單的區域性重新整理只重新整理表單資料,而不重新整理整個頁面

在web開發中有時有區域性重新整理的需求,這樣做的好處是克服了頁面整體重新整理對網路速度受限的情況。 1.MySQL資料表如下(簡單舉例): 表名:stu_info stuId                 int                    PK    NN

C++Socket通訊總結C++實現

一、Socket是什麼    Socket是應用層與TCP/IP協議族通訊的中間軟體抽象層,它是一組介面。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket介面後面,對使用者來說,一組簡單的介面就是全部,讓Socket去組織資

js實現jsonxml之

在web工程裡面,可能需要經常使用到xml和web的互轉功能, 在這裡,使用萬惡的百度之後,發現用java實現效率和效果很差,json轉成xml會出現一些類的頭,比如<o>,<array>這類的,找了很多方法都沒有能夠消除 鑑於js對json的良好支

pscp實現windowslinux之間傳檔案

PSCP (PuTTY Secure Copy client)是PuTTY 提供的檔案傳輸工具 ,通過 SSH 連線,在兩臺機器之間安全的傳輸檔案,可以用於任何 SSH(包括 SSH v1、SSH v2) 伺服器。 只需將putty的pscp.exe放到C:\Windows

C++ Socket通訊總結C++實現

  因為專案需要,服務端需要一個SOCKET來接收客戶端的請求,好吧,沒辦法度娘哇,結果很多都是linux的例子,功夫不負有心人啊,終於找到個demo,並且客戶端程式碼詳盡,記錄之,以便以後檢視。 一、Socket是什麼    Socket是應用層與TCP/IP協議族

javaoracle日期

  1、將java.util.Date 轉換為 java.sql.Date java.util.Date utilDate = new java.util.Date(); java.sql.Date sqlDate = new java.sql.Date(utilDate.

C語言 Include指令引用頭文件

clas fff const con ack style span pan har #include "one.h" #include "two.h" int main(int argc, const char * argv[]) { one(

C++類設計2Class with pointer members

delet images itl his per 復制代碼 復制 定義 行為 二 Class with pointer members(Class String) 1. 測試代碼(使用效果) int main() { String s1(), Strin

c++ 設計模式8 Factory Method 工廠方法

更改 itl logs 客戶 eos image 分享 一個 工廠方法模式 5. “對象創建”類模式 通過“對象創建”類模式繞開new,來避免對象創建(new)過程中所導致的緊耦合(依賴具體類),從而支持對象創建的穩定。它是接口抽象之後的第一步工作。 5.1 工廠方法 動機

【LeetCode-面試算法經典-Java實現】【063-Unique Paths II唯一路徑問題II

hide .text fun views [] pre ota function esp 【063-Unique Paths II(唯一路徑問題II)】 【LeetCode-面試算法經典-Java實現】【全部題目文件夾索引】 原題   Fo