摘要:以下將以一個實際例子展示通過調用打印主要記錄實現的過程和方法,對其中的一些原理和規范不做具體展開。指向在此代碼中實例化的對象的一個句柄,相當于指針。加載本地共享庫運行結果如下傳遞參數接下來看一下如何通過向傳遞參數。
文章為本人編纂,轉載請聯系作者并注明出處。
在日常項目中,我們可能會遇到需要用Java去命令行執行命令或執行shell腳本的情況,但有時可能又會因為某些環境或者權限等無法排查的原因調用失敗,這時候就可以通過一個中間介質C來執行。尤其是在對某些項目代碼(已經過廣泛測試或需要訪問特定設備)進行重寫,Java恐怕有些力不從心,而Sun公司定義的JNI規范,規定了Java對本地方法的調用規則,這就大可不必廢棄舊有代碼。
以下將以一個實際例子展示Java通過JNI調用C打印“Hello World!”主要記錄實現的過程和方法,對其中的一些原理和規范不做具體展開。想深入了解的可以參考Oracle的官方文檔,貼上地址:
JNI Interface Functions and Pointers
首先定義一個Java類JavaCallC.java,在類中實現一個SayHello方法,并用關鍵字native為本地方法編寫本地聲明;
public native void SayHello();
然后在類中的靜態代碼塊顯示地加載本地代碼庫;
static { System.loadLibrary("hello"); //加載本地共享庫 }
再加上main方法和一些必要的異常處理程序,就生成以下源文件(當然,也可以將本地方法放在另外一個多帶帶的類中)。
package com.jni.c; public class JavaCallC { /** * java通過JNI調用C * @author xiaosong 2017-04-03 */ public static void main(String[] args) { JavaCallC call = new JavaCallC(); call.SayHello(); } /** * 加載共享庫的本地方法 */ public native void SayHello(); static { try { System.loadLibrary("hello"); //加載本地共享庫 }catch(UnsatisfiedLinkError e) { System.err.println("無法加載共享庫:" + e.toString()); } } }
P.S. 如果沒有使用IDE的,需先用 javac 將類編譯為 .class 文件。
要為以上定義的類生成 Java 本地接口頭文件,需使用 javah,Java 編譯器的 javah 功能將根據 JavaCallC 類生成必要的聲明,此命令將生成一個 .h 后綴的頭文件,我們在共享庫的代碼中要包含它。在工程項目的編譯文件 bin 目錄(也可能是build)下執行如下命令():
javah -jni [package.class]
執行命令后生成了一個 com_jni_c_JavaCallC.h 頭文件,頭文件的內容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include/* Header for class com_jni_c_JavaCallC */ #ifndef _Included_com_jni_c_JavaCallC #define _Included_com_jni_c_JavaCallC #ifdef __cplusplus extern "C" { #endif /* * Class: com_jni_c_JavaCallC * Method: SayHello * Signature: ()V */ JNIEXPORT void JNICALL Java_com_jni_c_JavaCallC_SayHello (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif
在與 com_jni_c_JavaCallC.h 相同的路徑下創建一個 .c 文件 hello.c ,在C文件中引入該頭文件 ,并使用和頭文件中一致的方法來聲明函數。內容如下:
記得要為 JNIEnv * 指針和 jobject 對象定義變量,習慣上將這兩個變量定義為 env 和 obj 。
env 指針是任意一個本地方法的第一個參數,它指向一個函數指針表。jobject 指向在此 Java 代碼中實例化的 Java 對象 LocalFunction 的一個句柄,相當于 this 指針。
#include "com_jni_c_JavaCallC.h" #includeJNIEXPORT void JNICALL Java_com_jni_c_JavaCallC_SayHello (JNIEnv * env, jobject obj) { printf("Hello World! "); return; }
編譯文件時,需要告知 GCC 編譯器在何處查找Java本地方法的支持文件 jni.h 和 jni_md.h ,這兩個文件一般是在 ../jdk/include 和 ../jdk/include/linux 兩個目錄下(AIX在 ../jdk/include/aix 目錄;Windows在 ../jdk/include/win32 目錄),在我的環境中按如下過程編譯。
先生成 hello.o :
gcc -fPIC -I/usr/lib/jdk1.8.0_111/include -I/usr/lib/jdk1.8.0_111/include/linux -c hello.c
再生成 libhello.so :
(共享庫 .so 的文件名必須是 lib+文件名)
gcc -shared hello.o -o libhello.so
拷貝 libhello.so 到共享庫目錄:
(共享庫目錄一般為 ../jre/lib/amd64/server)
sudo cp libhello.so /usr/lib/jdk1.8.0_111/jre/lib/amd64/server
由于我未配置 $LD_LIBRARY_PATH 環境變量,所以程序無法加載到共享庫 hello ,因而我改寫成通過全路徑的方式來加載共享庫。
//System.loadLibrary("hello"); //加載本地共享庫 System.load("/usr/lib/jdk1.8.0_111/jre/lib/amd64/server/libhello.so");
運行結果如下:
接下來看一下Java如何通過JNI向C傳遞參數。本文中僅以 String 字符串為例,其他類型的參數的處理可參考文首提供的Oracle官方文檔,方法大體上是一致的。
先在聲明的本地方法中定義參數:
public native void SayHello(String strName1, String strName2);
然后在 main 方法中調用它并傳遞參數:
public static void main(String[] args) { JavaCallC call = new JavaCallC(); call.SayHello("Info", "Xiaosong"); }
生成頭文件的方法同上,這時候看一下生成的頭文件有何區別。
/* DO NOT EDIT THIS FILE - it is machine generated */ #include/* Header for class com_jni_c_JavaCallC */ #ifndef _Included_com_jni_c_JavaCallC #define _Included_com_jni_c_JavaCallC #ifdef __cplusplus extern "C" { #endif /* * Class: com_jni_c_JavaCallC * Method: SayHello * Signature: (Ljava/lang/String;Ljava/lang/String;)V */ JNIEXPORT void JNICALL Java_com_jni_c_JavaCallC_SayHello (JNIEnv *, jobject, jstring, jstring); #ifdef __cplusplus } #endif #endif
可以看到函數聲明里多了兩個 jstring ,這就對應于我們要傳遞的兩個 String 參數。
其他數值型參數和數組型參數對照如下:
同樣地,在編寫 hello.c 文件時,我們需要為傳遞的兩個參數定義變量;
JNIEXPORT void JNICALL Java_com_jni_c_JavaCallC_SayHello (JNIEnv * env, jobject obj, jstring instring1, jstring instring2)
對于字符串型參數,因為在本地代碼中不能直接讀取 Java 字符串,而必須將其轉換為 C /C++ 字符串或 Unicode。此處C的寫法和C++的寫法略微不同;
/** * C 寫法 */ //從instring字符串取得指向字符串UTF編碼的指針; const char *info = (*env)->GetStringUTFChars(env, instring1, 0);
/** * C++ 寫法 */ const char *info = env->GetStringUTFChars(instring1, 0);
//通知虛擬機本地代碼不再需要通過 info 訪問Java字符串;
/** * C 寫法 */ (*env)->ReleaseStringUTFChars(env, instring1, info);
/** * C++ 寫法 */ env->ReleaseStringUTFChars(instring1, info);
再加上一些簡單的異常處理,完整的含參的 hello.c 如下:
#include "com_jni_c_JavaCallC.h" #include#include JNIEXPORT void JNICALL Java_com_jni_c_JavaCallC_SayHello (JNIEnv * env, jobject arg, jstring instring1, jstring instring2) { //從instring字符串取得指向字符串UTF編碼的指針 const char *info = (*env)->GetStringUTFChars(env, instring1, 0); const char *name = (*env)->GetStringUTFChars(env, instring2, 0); if (strlen(info)==0 || strlen(name)==0) { printf("參數缺失! "); }else { printf("%s : Hello %s ", info, name); }; //通知虛擬機本地代碼不再需要通過str訪問java字符串 (*env)->ReleaseStringUTFChars(env, s1, str); (*env)->ReleaseStringUTFChars(env, s2, user); return; }
方法和操作同上
以下是調用 call.SayHello("Information", "Xiaosong"); 執行的結果:
以下是調用 call.SayHello("Information", ""); 執行的結果:
至此,Java通過JNI調C的例子全部結束,當中如有什么不足或錯誤還請指正。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66945.html
摘要:我們知道,發起函數調用,需要構造一個棧幀。構造棧幀的具體實現細節的選擇,被稱為調用慣例。要想完成這個函數調用邏輯,就要運行時構造棧幀,生成參數壓棧和清理堆棧的工作。目前,幾乎支持全部常見的架構。 原文:http://nullwy.me/2018/01/java...如果覺得我的文章對你有用,請隨意贊賞 遇到的問題 前段時間開發的時候,遇到一個問題,就是如何用 Java 實現 chdir...
摘要:拆解虛擬機的基本步聚如下首先,要等待到自身成為唯一一個正在運行的非守護線程時,在整個等待過程中,虛擬機仍舊是可工作的。將相應的事件發送給,禁用,并終止信號線程。 本文簡單介紹HotSpot虛擬機運行時子系統,內容來自不同的版本,因此可能會與最新版本之間(當前為JDK12)存在一些誤差。 1.命令行參數處理HotSpot虛擬機中有大量的可影響性能的命令行屬性,可根據他們的消費者進行簡...
摘要:換句話說,當一個異步過程調用發出后,調用者不會立刻得到結果。參考文章珠峰架構課墻裂推薦細說異步函數發展歷程異步編程謝謝各位小伙伴愿意花費寶貴的時間閱讀本文,如果本文給了您一點幫助或者是啟發,請不要吝嗇你的贊和,您的肯定是我前進的最大動力。知其然知其所以然,首先了解三個概念: 1.什么是同步? 所謂同步,就是在發出一個調用時,在沒有得到結果之前,該調用就不返回。但是一旦調用返回,就得到返回值了...
摘要:參考文章珠峰架構課墻裂推薦細說異步函數發展歷程異步編程謝謝各位小伙伴愿意花費寶貴的時間閱讀本文,如果本文給了您一點幫助或者是啟發,請不要吝嗇你的贊和,您的肯定是我前進的最大動力。 知其然知其所以然,首先了解三個概念: 1.什么是同步? 所謂同步,就是在發出一個調用時,在沒有得到結果之前,該調用就不返回。但是一旦調用返回,就得到返回值了。換句話說,就是由調用者主動等待這個調用的結果。此調...
閱讀 2565·2021-10-11 10:58
閱讀 1148·2021-09-29 09:34
閱讀 1486·2021-09-26 09:46
閱讀 3830·2021-09-22 15:31
閱讀 730·2019-08-30 15:54
閱讀 1458·2019-08-30 13:20
閱讀 1251·2019-08-30 13:13
閱讀 1486·2019-08-26 13:52