日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

Step By Step_Java通过JNI调C程序执行

發(fā)布時間:2025/1/21 152 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Step By Step_Java通过JNI调C程序执行 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

文章為本人編纂,轉(zhuǎn)載請聯(lián)系作者并注明出處。

在日常項目中,我們可能會遇到需要用Java去命令行執(zhí)行命令或執(zhí)行shell腳本的情況,但有時可能又會因為某些環(huán)境或者權(quán)限等無法排查的原因調(diào)用失敗,這時候就可以通過一個中間介質(zhì)C來執(zhí)行。尤其是在對某些項目代碼(已經(jīng)過廣泛測試或需要訪問特定設(shè)備)進(jìn)行重寫,Java恐怕有些力不從心,而Sun公司定義的JNI規(guī)范,規(guī)定了Java對本地方法的調(diào)用規(guī)則,這就大可不必廢棄舊有代碼。

以下將以一個實際例子展示Java通過JNI調(diào)用C打印“Hello World!”主要記錄實現(xiàn)的過程和方法,對其中的一些原理和規(guī)范不做具體展開。想深入了解的可以參考Oracle的官方文檔,貼上地址:
JNI Interface Functions and Pointers


環(huán)境介紹

操作系統(tǒng):Ubuntu Gnome 16.04 LTS

Java:Java 1.8.0_111

C:gcc version 5.4.0

實現(xiàn)步驟

Hello World

1、定義一個Java類——JavaCallC.java

首先定義一個Java類JavaCallC.java,在類中實現(xiàn)一個SayHello方法,并用關(guān)鍵字native為本地方法編寫本地聲明;

public native void SayHello();

然后在類中的靜態(tài)代碼塊顯示地加載本地代碼庫;

static {System.loadLibrary("hello"); //加載本地共享庫 }

再加上main方法和一些必要的異常處理程序,就生成以下源文件(當(dāng)然,也可以將本地方法放在另外一個單獨的類中)。

package com.jni.c;public class JavaCallC {/*** java通過JNI調(diào)用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());}}}
2、生成 Java 本地接口頭文件

P.S. 如果沒有使用IDE的,需先用 javac 將類編譯為 .class 文件。
要為以上定義的類生成 Java 本地接口頭文件,需使用 javah,Java 編譯器的 javah 功能將根據(jù) JavaCallC 類生成必要的聲明,此命令將生成一個 .h 后綴的頭文件,我們在共享庫的代碼中要包含它。在工程項目的編譯文件 bin 目錄(也可能是build)下執(zhí)行如下命令():

javah -jni [package.class]

執(zhí)行命令后生成了一個 com_jni_c_JavaCallC.h 頭文件,頭文件的內(nèi)容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* 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
3、創(chuàng)建共享庫C文件

在與 com_jni_c_JavaCallC.h 相同的路徑下創(chuàng)建一個 .c 文件 hello.c ,在C文件中引入該頭文件 ,并使用和頭文件中一致的方法來聲明函數(shù)。內(nèi)容如下:

記得要為 JNIEnv * 指針和 jobject 對象定義變量,習(xí)慣上將這兩個變量定義為 env 和 obj 。
env 指針是任意一個本地方法的第一個參數(shù),它指向一個函數(shù)指針表。jobject 指向在此 Java 代碼中實例化的 Java 對象 LocalFunction 的一個句柄,相當(dāng)于 this 指針。

#include "com_jni_c_JavaCallC.h" #include <stdio.h>JNIEXPORT void JNICALL Java_com_jni_c_JavaCallC_SayHello (JNIEnv * env, jobject obj) {printf("Hello World! \n");return; }
4、編譯生成共享庫文件

編譯文件時,需要告知 GCC 編譯器在何處查找Java本地方法的支持文件 jni.h 和 jni_md.h ,這兩個文件一般是在 ../jdk/include 和 ../jdk/include/linux 兩個目錄下(AIX在 ../jdk/include/aix 目錄;Windows在 ../jdk/include/win32 目錄),在我的環(huán)境中按如下過程編譯。

  • 先生成 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

5、運行Java程序

由于我未配置 $LD_LIBRARY_PATH 環(huán)境變量,所以程序無法加載到共享庫 hello ,因而我改寫成通過全路徑的方式來加載共享庫。

//System.loadLibrary("hello"); //加載本地共享庫 System.load("/usr/lib/jdk1.8.0_111/jre/lib/amd64/server/libhello.so");

運行結(jié)果如下:

傳遞參數(shù)

接下來看一下Java如何通過JNI向C傳遞參數(shù)。本文中僅以 String 字符串為例,其他類型的參數(shù)的處理可參考文首提供的Oracle官方文檔,方法大體上是一致的。

1、定義本地方法參數(shù)

先在聲明的本地方法中定義參數(shù):

public native void SayHello(String strName1, String strName2);

然后在 main 方法中調(diào)用它并傳遞參數(shù):

public static void main(String[] args) {JavaCallC call = new JavaCallC();call.SayHello("Info", "Xiaosong"); }
2、編譯并生成頭文件

生成頭文件的方法同上,這時候看一下生成的頭文件有何區(qū)別。

/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* 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

可以看到函數(shù)聲明里多了兩個 jstring ,這就對應(yīng)于我們要傳遞的兩個 String 參數(shù)。
其他數(shù)值型參數(shù)和數(shù)組型參數(shù)對照如下:

3、創(chuàng)建共享庫C文件

同樣地,在編寫 hello.c 文件時,我們需要為傳遞的兩個參數(shù)定義變量;

JNIEXPORT void JNICALL Java_com_jni_c_JavaCallC_SayHello (JNIEnv * env, jobject obj, jstring instring1, jstring instring2)

對于字符串型參數(shù),因為在本地代碼中不能直接讀取 Java 字符串,而必須將其轉(zhuǎn)換為 C /C++ 字符串或 Unicode。此處C的寫法和C++的寫法略微不同;

/*** C 寫法*/ //從instring字符串取得指向字符串UTF編碼的指針; const char *info = (*env)->GetStringUTFChars(env, instring1, 0); /*** C++ 寫法*/ const char *info = env->GetStringUTFChars(instring1, 0);

//通知虛擬機(jī)本地代碼不再需要通過 info 訪問Java字符串;

/*** C 寫法*/ (*env)->ReleaseStringUTFChars(env, instring1, info); /*** C++ 寫法*/ env->ReleaseStringUTFChars(instring1, info);

再加上一些簡單的異常處理,完整的含參的 hello.c 如下:

#include "com_jni_c_JavaCallC.h" #include <stdio.h> #include <string.h>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("參數(shù)缺失!\n");}else {printf("%s : Hello %s \n", info, name);};//通知虛擬機(jī)本地代碼不再需要通過str訪問java字符串(*env)->ReleaseStringUTFChars(env, s1, str);(*env)->ReleaseStringUTFChars(env, s2, user);return; }
4、編譯生成共享庫文件

方法和操作同上

5、運行Java程序

以下是調(diào)用 call.SayHello("Information", "Xiaosong"); 執(zhí)行的結(jié)果:

以下是調(diào)用 call.SayHello("Information", ""); 執(zhí)行的結(jié)果:


至此,Java通過JNI調(diào)C的例子全部結(jié)束,當(dāng)中如有什么不足或錯誤還請指正。

總結(jié)

以上是生活随笔為你收集整理的Step By Step_Java通过JNI调C程序执行的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。