JNI的簡單使用
以一個最簡單的HelloWorld程序來介紹一下JNI的最基本的使用方法:
1)首先要有一個HelloWorld.java。
這個是主文件,裡面包括本地方法的java聲明,一個main函數,還有一個靜態代碼段,用來導入所需要的動態連接庫(在Wndows裡是.dll)。
代碼如下:
//HelloWorld.java
class HelloWorld {
public native void displayHelloWorld();//注意關鍵字native,這就說明這個方法是用本地方法實現的。
static {//靜態代碼段裡面導入了hello.dll。
System.loadLibrary("hello");
}
public static void main(String[] args) {//調用本類的displayHelloWorld方法,(當然了方法實際上是用c語言實現的)
new HelloWorld().displayHelloWorld();
}
}
2)編譯HelloWorld.java。
使用語句為:
javac HelloWorld.java
3)使用javah命令生成一個.h文件。
使用語句為:
javah HelloWorld
這就是實現displayHelloWorld()方法的c文件的頭文件。文件名為HelloWorld.h代碼如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */
#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloWorld
* Method: displayHelloWorld
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
可以看到,這個文件裡面主要就是需要在c文件裡面實現的方法的方法聲明。這個聲明和java文件HelloWorld.java的有一點區別,原來的方法不帶參數,可是現在有了兩個參數。
這兩個是任何一個本地方法都必須有的參數。
第一個參數是JNIEnv*,它用於連接從java應用程序傳給你的本地方法的參數和對象。第二個參數是一個jobject,它指向當前對象本身,你也可以把它理解為java裡面的this變量。對於一個本地實例方法,比如這個例子裡的displayHelloWorld方法,jobject參數就是一個對象當前實例的引用。對於本地類的方法,這個參數就是一個方法類的引用。在這個例子裡面不需要使用這兩個參數。
另外一點,可以發現方法的名稱和java文件裡的不一致,這個方法名由以下幾部分組成:
java_[包名+]類名_java方法名
4)編寫實現本地方法的c文件
//本例中起名為HelloWorldImp.c
#include <jni.h>
#include "HelloWorld.h"
#include <stdio.h>
JNIEXPORT void JNICALL
Java_HelloWorld_displayHelloWorld(JNIEnv *env, jobject obj)
{
printf("Hello world!
");//這個例子中只輸出一行Hello World!
return;
}
5)建立動態連接庫
在Wndows下面使用下面的語句:
cl -Id:jdk1.3.1include -I d:jdk1.3.1includewin32 -LD HelloWorldImp.c -Fehello.dll
這裡面有幾部分。D:jdk1.3.1是本地的java home的路徑。在include和includewin32目錄下面有產生動態連接庫需要的幾個.h文件,包括jni.h(在所有的實現native方法的c文件裡面都要include這個文件)等等。
將產生的.dll文件放到環境變量path能找到的目錄下。現在運行命令:
java HelloWorld
就會看到如下輸出:
Hello World!
//**********************************************
編寫高質量代碼 改善Java程序的151個建議 PDF高清完整版 http://www.linuxidc.com/Linux/2014-06/103388.htm
Java 8簡明教程 http://www.linuxidc.com/Linux/2014-03/98754.htm
Java對象初始化順序的簡單驗證 http://www.linuxidc.com/Linux/2014-02/96220.htm
Java對象值傳遞和對象傳遞的總結 http://www.linuxidc.com/Linux/2012-12/76692.htm
Java對象序列化ObjectOutputStream和ObjectInputStream示例 http://www.linuxidc.com/Linux/2012-08/68360.htm
還有, 以下載自《JAVA如何調用C/C++方法》--acute(原作)
JAVA通過JNI調用本地C語言方法
JAVA以其跨平台的特性深受人們喜愛,而又正由於它的跨平台的目的,使得它和本地機器的各種內部聯系變得很少,約束了它的功能。解決JAVA對本地操作的一種方法就是JNI。
JAVA通過JNI調用本地方法,而本地方法是以庫文件的形式存放的(在WINDOWS平台上是DLL文件形式,在UNIX機器上是SO文件形式)。通過調用本地的庫文件的內部方法,使JAVA可以實現和本地機器的緊密聯系,調用系統級的各接口方法。
簡單介紹及應用如下:
一、JAVA中所需要做的工作
在JAVA程序中,首先需要在類中聲明所調用的庫名稱,如下:
static {
System.loadLibrary(“goodluck”);
}
在這裡,庫的擴展名字可以不用寫出來,究竟是DLL還是SO,由系統自己判斷。
還需要對將要調用的方法做本地聲明,關鍵字為native。並且只需要聲明,而不需要具體實現。如下:
public native static void set(int i);
public native static int get();
然後編譯該JAVA程序文件,生成CLASS,再用JAVAH命令,JNI就會生成C/C++的頭文件。
例如程序testdll.java,內容為:
public class testdll
{
static
{
System.loadLibrary("goodluck");
}
public native static int get();
public native static void set(int i);
public static void main(String[] args)
{
testdll test = new testdll();
test.set(10);
System.out.println(test.get());
}
}
用javac testdll.java編譯它,會生成testdll.class。
再用javah testdll,則會在當前目錄下生成testdll.h文件,這個文件需要被C/C++程序調用來生成所需的庫文件。
二、C/C++中所需要做的工作
對於已生成的.h頭文件,C/C++所需要做的,就是把它的各個方法具體的實現。然後編譯連接成庫文件即可。再把庫文件拷貝到JAVA程序的路徑下面,就可以用JAVA調用C/C++所實現的功能了。
接上例子。我們先看一下testdll.h文件的內容:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class testdll */
#ifndef _Included_testdll
#define _Included_testdll
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: testdll
* Method: get
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_testdll_get
(JNIEnv *, jclass);
/*
* Class: testdll
* Method: set
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_testdll_set
(JNIEnv *, jclass, jint);
#ifdef __cplusplus
}
#endif
#endif
在具體實現的時候,我們只關心兩個函數原型
JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass);
和
JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint);
這裡JNIEXPORT和JNICALL都是JNI的關鍵字,表示此函數是要被JNI調用的。而jint是以JNI為中介使JAVA的int類型與本地的int溝通的一種類型,我們可以視而不見,就當做int使用。函數的名稱是JAVA_再加上java程序的package路徑再加函數名組成的。參數中,我們也只需要關心在JAVA程序中存在的參數,至於JNIEnv*和jclass我們一般沒有必要去碰它。
好,下面我們用testdll.cpp文件具體實現這兩個函數:
#include "testdll.h"
int i = 0;
JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass)
{
return i;
}
JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint j)
{
i = j;
}
編譯連接成庫文件,本例是在WINDOWS下做的,生成的是DLL文件。並且名稱要與JAVA中需要調用的一致,這裡就是goodluck.dll
把goodluck.dll拷貝到testdll.class的目錄下,java testdll運行它,就可以觀察到結果了。
另外:
使用Java直接調用VB編寫的DLL是不行的,因為VB根本不能生成標准的DLL動態鏈接庫,VB制作的DLL都是基於COM的動態鏈接庫,而不是直接輸出函數。
一般的做法使用C/C++對COM DLL進行二次封裝,將其中的功能已C API的形式倒出。