簡單JNI的使用--在Java中調用C庫函數


  在Android Framework中,需要提供一種媒介或橋梁,將Java層(上層)與C/C++(底層)有機地聯系起來,使得它們相互協調,共同完成某些任務。在這兩層之間充當連接橋梁這一角色的就是Java本地接口(JNI,Java Native Interface),它允許Java代碼與基於C/C++編寫的應用和庫進行交互操作。

  JNI提供了一系列接口,允許Java類與使用C/C++等其它編程語言(在JNI中,這些語言被稱為本地語言)編寫的應用程序、模塊、庫進行交互操作。比如,在Java類中使用C語言庫中中的特定函數,或在C語言里面使用Java類庫,都需要借助JNI來完成。

  通常會在下列幾種情況下使用JNI

  • 注重處理速度:如果對某段程序的執行速度有較高的要求,建議使用C/C++編寫代碼,而后在Java層通過JNI調用基於C/C++編寫的部分代碼。
  • 硬件控制:為了更好地控制硬件,硬件控制代碼通常使用C語言編寫,借助JNI將其與Java層連接起來,從而實現對硬件的控制。
  • 已有C/C++代碼的復用:在編寫程序的過程中,常常會使用已經編寫好的C/C++代碼,既提高了編程效率,又確保了程序的安全性和健壯性。在復用這些C/C++代碼時,就要通過JNI來實現。

 

在Java代碼中通過JNI調用C函數的步驟如下:

  • 第一步:編寫Java代碼
  • 第二步:編譯Java代碼(javac Java文件)
  • 第三步:生成C代碼頭文件(javah java類名,自動生成)
  • 第四步:編寫C代碼(實現C代碼頭文件里面的函數)
  • 第五步:生成C共享庫(使用工具編譯生成C共享庫,win下面為dll文件,Linux下面為so文件)
  • 第六步:運行Java程序(java 類名)

 

第一步:編寫Java代碼

       首先編寫調用C語言的Java源代碼HelloJNI.java     

 public class HelloJNI {
          native void printHello();                      1
          native void printString(String str);
         static{
            System.loadLibrary("hellojni");        2
        }
          public static void main(String[] args) {
             // TODO Auto-generated method stub
             HelloJNI myJNI = new HelloJNI();
               myJNI.printHello();
             myJNI.printString("Hello world form printString function!");
          }
}

說明:

     1在Java類中,使用”native”關鍵字,聲明本地方法,該方法與用C/C++編寫的JNI本地函數相對應。”native”關鍵字告知Java編譯器,在Java代碼中帶有該關鍵字的方法只是聲明,具體由C/C++等其它語言編寫實現。

       2在Java類中聲明了本地方法之后,接下來,調用System.loadLibrary()方法,加載具體實現本地方法的C運行庫(在Java中加載本地運行庫通常使用靜態塊(static block))。System.loadLibrary()方法加載由字符串參數指定的本地庫,在不同操作系統平台下,加載的C運行庫不同。在Window下面,調用System.loadLibrary(“hellojni”),則hellojni.dll會被加載;在Linux下面,則會加載libhellojni.so文件。

 

第二步:編譯Java代碼

       使用如下命令編譯java源代碼:

              javac HelloJNI.java

       編譯好HelloJNI.java后,生成HelloJNI.class文件。如果此時直接運行java程序,就是拋出異常。

 

       由於尚未創建加載到Java代碼中的hellojni.dll庫文件,無法找到Java虛擬機要加載的C運行庫。

       接下來,創建hellojni.dll庫文件。

 

第三步:生成C代碼頭文件

       若想創建本地方法的映射C函數,必須先生成函數原型,函數原型存在於C/C++頭文件中。Java提供了javah工具,位於JAVA JDK的安裝目錄的bin目錄下面,用來生成包含函數原型的C/C++頭文件,使用方法如下:

       javah <包含以native關鍵字聲明方法的Java類名稱>

       運行javah命令,會在當前目錄下生成與Java類名(即javah命令的參數)相同名稱的C語言頭文件。在生成的C頭文件中,定義了與Java本地方法相鏈接的C函數原型。

       以下為生成的HelloJNI.h文件內容:

/* DO NOT EDIT THIS FILE - it is machine generated */          1

#include <jni.h>

/* Header for class HelloJNI */

 

#ifndef _Included_HelloJNI

#define _Included_HelloJNI

#ifdef __cplusplus

extern "C" {

#endif

/*

 * Class:     HelloJNI

 * Method:    printHello

 * Signature: ()V

 */

JNIEXPORT void JNICALL Java_HelloJNI_printHello  

  (JNIEnv *, jobject);

 

/*

 * Class:     HelloJNI                                                                          2

 * Method:    printString

 * Signature: (Ljava/lang/String;)V

 */

JNIEXPORT void JNICALL Java_HelloJNI_printString

  (JNIEnv *, jobject, jstring);

 

#ifdef __cplusplus

}

#endif

#endif

說明:

       1該文件由javah命令生成,為了保證JNI正常運行,請不要直接修改本文件的內容,JNI開發者只要使用C/C++語言實現定義的函數即可。

       2這是javah命令生成的兩個C函數原型,函數原型在Java類中聲明的本地方法的基礎上生成。查看各函數原型注釋,可以看到與各函數原型對應的Java代碼中的本地方法,注釋中標明了三個元素:類名、本地方法、本地方法簽名。     

       接下來分析一下函數原型:JNIEXPORT、JNICALL都是JNI關鍵字,表示此函數要被JNI調用,函數原型中必須有這兩個關鍵字,JNI才能正常調用函數。其實,JNIEXPORT、JNICALL兩個關鍵字都是宏定義,在JDK_HOME/include/win32/jni_md.h文件(在Window平台下)中。

       觀察函數原型名稱,可以發現函數名稱遵循一定的命名規則:JNI支持的函數命名形式為”Java_類名_本地方法名”。通過函數命名即可推斷出JNI本地函數與哪個Java類的哪個本地方法相對應。

       在生成的函數原型中,帶有兩個默認參數,分別為JNIEnv * 與jobject,支持JNI的函數必須包含這兩個共同參數。第一個參數JNIEnv *為JNI接口指針,用來調用JNI表中的各種JNI函數(這里的JNI函數是指JNI中提供的基本函數集);第二個參數jobject也是JNI提供的Java本地方法,用來在C代碼中訪問Java對象,此參數中保存着調用本地方法的對象的一個引用。

       在JNI編程中,Java程序與C/C++函數間經常進行數據交換,如果不提供一種方法消除兩種語言的數據類型的差異,那么程序就無法正常運行,運行的可靠性也無法保障。JNI提供了一套與Java數據類型相對應的Java本地類型,使得本地語言可以使用Java數據類型,如下表所示:

Java類型

本地類型

字節(bit)

boolean

jboolean

8,

byte

jbyte

8

char

jchar

16,

short

jshort

16

int

jint

32

long

jlong

64

float

jfloat

32

double

jdouble

64

void

void

n/a

       以上Java本地類型定義在JDK_HOME/include/jni.h文件中。此外,Java本地類型也提供了另外三種類型,分別對應於Java類、對象與字符串三種引用類型數據(除此之外還有幾種引用類型,有興趣可以可以查閱相關資料)。

Java引用類型

Java本地類型

Jclass

對象

Jobject

String

Jstring

 

第四步:編寫C代碼

       在C函數原型生成后,開始編寫hellojni.c文件,具體實現JNI本地函數。首先,把定義在HelloJNI.h頭文件中的函數原型復制到hellojni.c,注意在使用javah命名生成的頭文件中,函數的參數僅指定了參數的類型,並未給出參數的名稱,因此復制完函數原型,開始實現C函數時,必須先在參數類型后指定具體的參數名稱。

       以下為編寫好的hellojni.c代碼:

#include "HelloJNI.h"

#include <stdio.h>

 

//添加名稱為env與obj的兩個參數

JNIEXPORT void JNICALL Java_HelloJNI_printHello(JNIEnv *env, jobject obj)

{

       printf("Hello world!\n");

       return ;

}

 

JNIEXPORT void JNICALL Java_HelloJNI_printString(JNIEnv *env, jobject obj, jstring string)

{

       //將Java String轉換以C字符串

       const char * str = (*env)->GetStringUTFChars(env,string,0);

       printf("%s!\n",str);

       return ;

}

       GetStringUTFChars ()是JNI函數,用來將Java字符串轉換成C語言字符串。JNI提供了多種JNI函數,用來處理C字符串與Java字符串的轉換,具體可以參考:

第五步:生成C共享庫

       在編寫好了hellojni.c之后,使用編譯器將其編譯成hellojni.dll文件。這里使用的是Visual C++ 2008 Express Editions。安裝好之后,使用Visual Studio 2008 命令提示,輸入編譯指令:

cl -I"F:\Java\jdk1.7.0\include" -I"F:\Java\jdk1.7.0\include\win32" -LD hellojni.c -Fehellojni.dll

執行結果為:

 

指令說明:

       cl:visual c++編譯命令

       -I<dir>:添加要檢索頭文件的目錄路徑<dir>

                 為了檢索頭文件,添加如下目錄

                     jni.h(<JDK_HONE>\include)

                     jni_md.h(JDK_HONE>\include\win32)

       -LD:創建DLL

       -Fe<文件名>:指定編譯結果文件名稱

 

第六步:運行Java程序

       執行java指令,運行HelloJNI類。

      

 

附錄:

  生成JNI的DLL時提示找不到jni.h的解決辦法Cannot open include file: 'jni.h': No such file or directory

  解決方法:在Windows下面使用cygwin將含有JNI的C文件編譯成DLL文件


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM