一、JNI 的特点:
JNI有一个很重要的优点,就是在你充分利用Java的跨平台特性的前提下,你仍然可以利用其它编程语言。JNI是JVM实现很重要的一部分,是允许Java应用调用本地代码(native code)或本地代码调用Java应用的一个双向接口。下图就显示了这两者之间的关系:JNI支持两种类型的native code: 本地库和本地应用程序
1. 你可以使用JNI写出本地方法,允许Java程序调用由本地方法实现的函数,这在jvm的实现里有大量的应用。我们从jdk的源码里也可以看到大量的这种示例。
2. JNI还支持调用接口(invocation interface),它允许你在本地应用程序(通常是C/C++代码编写)里嵌入jvm的实现,本地应用可以链接实现了jvm的本地库,接着使用调用接口来调用Java中的一些类的方法。比如,使用C编写的Web浏览器,可以执行一个applet代码片段。
总之一句话,JNI建立了Java语言和native code之间的一种双向调用关系。
下面我们看看,一个简单的jni程序如何编写?
二、简单示例:
示例1: HelloWorld.java, 即调用一个C函数,打印一个简单的字符串
package
jnitest;
public class HelloWorld {
public HelloWorld() {
}
static {
System.loadLibrary("helloworld");
}
public native void displayHelloWorld();
public native void displayString(String sz);
public native String echoString(String sz);
public static void main(String[] args) {
HelloWorld helloworld = new HelloWorld();
String t = helloworld.echoString("测试一下中文");
System.out.println(t);
}
}
public class HelloWorld {
public HelloWorld() {
}
static {
System.loadLibrary("helloworld");
}
public native void displayHelloWorld();
public native void displayString(String sz);
public native String echoString(String sz);
public static void main(String[] args) {
HelloWorld helloworld = new HelloWorld();
String t = helloworld.echoString("测试一下中文");
System.out.println(t);
}
}
第一步,我们先build出class文件。
第二步,针对上边的jnitest.class文件,使用javah为其生成C代码中的头文件
cd E:/java/jbproject/jnitest/classes
javah jnitest.HelloWorld
这时我们可以看到生成的头文件名叫jnitest_HelloWorld.h,它的内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include < jni.h >
/* Header for class jnitest_HelloWorld */
#ifndef _Included_jnitest_HelloWorld
#define _Included_jnitest_HelloWorld
#ifdef __cplusplus
extern " C " {
#endif
/*
* Class: jnitest_HelloWorld
* Method: echoString
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_jnitest_HelloWorld_echoString
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
#include < jni.h >
/* Header for class jnitest_HelloWorld */
#ifndef _Included_jnitest_HelloWorld
#define _Included_jnitest_HelloWorld
#ifdef __cplusplus
extern " C " {
#endif
/*
* Class: jnitest_HelloWorld
* Method: echoString
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_jnitest_HelloWorld_echoString
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
extern "C" {
#endif
}
表示该头文件也可以用于C++代码的make和link。
下面,我们可以写出本地方法 Java_jnitest_HelloWorld_echoString的实现了。
第二步:实现本地方法.
在刚才的目录 E:/java/jbproject/jnitest/classes下边,我们新建一个文件helloworldimp.cpp。内容如下:
#include
<
jni.h
>
#include " jnitest_HelloWorld.h "
#include < stdio.h >
JNIEXPORT jstring JNICALL Java_jnitest_HelloWorld_echoString
(JNIEnv * env, jobject obj, jstring sz)
{
return sz;
}
#include " jnitest_HelloWorld.h "
#include < stdio.h >
JNIEXPORT jstring JNICALL Java_jnitest_HelloWorld_echoString
(JNIEnv * env, jobject obj, jstring sz)
{
return sz;
}
第三步:是build出动态库helloworld.dll,希望你的机器里已经安装好了VC6或者VC7/8(VS2002,2003或者2005)。这里以VC6.0为例,并且采用命令行方式来build。进入到VC的安装目录,这里是D:/msdev/VC98/Bin
编译命令cl就在此目录下边,这个目录下边有个环境变量的批处理文件VCVARS32.bat,我们拿来改一下就可以用了。
在这里,我为它专门加入了jni要使用的头文件,为了编译java代码,把java的ClassPath也加进来了。其内容如下:
@echo
off
rem
rem Root of Visual Developer Studio Common files.
set VSCommonDir = D: msdev
rem
rem Root of Visual Developer Studio installed files.
rem
set MSDevDir = D: msdev Common msdev98
rem
rem Root of Visual C++ installed files.
rem
set MSVCDir = D: msdev VC98
rem
rem VcOsDir is used to help create either a Windows 95 or Windows NT specific path.
rem
set VcOsDir = WIN95
if " %OS% " == " Windows_NT " set VcOsDir = WINNT
rem
echo Setting environment for using Microsoft Visual C ++ tools .
rem
if " %OS% " == " Windows_NT " set PATH = %MSDevDir% BIN ; %MSVCDir% BIN ; %VSCommonDir% TOOLS %VcOsDir% ; %VSCommonDir% TOOLS ; %MSVCDir% DEBUG ; %VSCommonDir% OS SYSTEM ; % PATH %
if " %OS% " == "" set PATH = " %MSDevDir%BIN " ; " %MSVCDir%BIN " ; " %VSCommonDir%TOOLS%VcOsDir% " ; " %VSCommonDir%TOOLS " ; " %windir%SYSTEM " ; " %PATH% " ; %VSCommonDir% OS SYSTEM
set INCLUDE = %MSVCDir% ATL INCLUDE ; %MSVCDir% INCLUDE ; %MSVCDir% MFC INCLUDE ; %INCLUDE%
set LIB = %MSVCDir% LIB ; %MSVCDir% MFC LIB ; %LIB%
rem set VcOsDir=
rem set VSCommonDir=
rem modified by xionghe, antpath, classpath etc.
set path = % path % ; d: /jakarta-ant /bin
set JAGUAR_JDK13 = d: /shared jdk1 . 3 . 1_11
set JAGUAR_JDK14 = d: /shared jdk1 . 4 . 2_06
set JAGUAR_JDK15 = d: /shared jdk1 . 5 . 0_01
set JAVA_HOME = %JAGUAR_JDK14%
set PATH = %JAVA_HOME% bin ; %JAVA_HOME%/ jre/ bin ; % PATH %
set OLD_CLASSPATH = %CLASSPATH%
set CLASSPATH =.; %JAVA_HOME%/ lib/ dt . jar ; %JAVA_HOME%/ lib/ tools . jar ; %JAVA_HOME%/ jre/ lib /rt . jar
set CLASSPATH = %CLASSPATH% ; %OLD_CLASSPATH%
set INCLUDE = %JAVA_HOME%/ include ; %JAVA_HOME% /include /win32 ; %INCLUDE%
start cmd
rem
rem Root of Visual Developer Studio Common files.
set VSCommonDir = D: msdev
rem
rem Root of Visual Developer Studio installed files.
rem
set MSDevDir = D: msdev Common msdev98
rem
rem Root of Visual C++ installed files.
rem
set MSVCDir = D: msdev VC98
rem
rem VcOsDir is used to help create either a Windows 95 or Windows NT specific path.
rem
set VcOsDir = WIN95
if " %OS% " == " Windows_NT " set VcOsDir = WINNT
rem
echo Setting environment for using Microsoft Visual C ++ tools .
rem
if " %OS% " == " Windows_NT " set PATH = %MSDevDir% BIN ; %MSVCDir% BIN ; %VSCommonDir% TOOLS %VcOsDir% ; %VSCommonDir% TOOLS ; %MSVCDir% DEBUG ; %VSCommonDir% OS SYSTEM ; % PATH %
if " %OS% " == "" set PATH = " %MSDevDir%BIN " ; " %MSVCDir%BIN " ; " %VSCommonDir%TOOLS%VcOsDir% " ; " %VSCommonDir%TOOLS " ; " %windir%SYSTEM " ; " %PATH% " ; %VSCommonDir% OS SYSTEM
set INCLUDE = %MSVCDir% ATL INCLUDE ; %MSVCDir% INCLUDE ; %MSVCDir% MFC INCLUDE ; %INCLUDE%
set LIB = %MSVCDir% LIB ; %MSVCDir% MFC LIB ; %LIB%
rem set VcOsDir=
rem set VSCommonDir=
rem modified by xionghe, antpath, classpath etc.
set path = % path % ; d: /jakarta-ant /bin
set JAGUAR_JDK13 = d: /shared jdk1 . 3 . 1_11
set JAGUAR_JDK14 = d: /shared jdk1 . 4 . 2_06
set JAGUAR_JDK15 = d: /shared jdk1 . 5 . 0_01
set JAVA_HOME = %JAGUAR_JDK14%
set PATH = %JAVA_HOME% bin ; %JAVA_HOME%/ jre/ bin ; % PATH %
set OLD_CLASSPATH = %CLASSPATH%
set CLASSPATH =.; %JAVA_HOME%/ lib/ dt . jar ; %JAVA_HOME%/ lib/ tools . jar ; %JAVA_HOME%/ jre/ lib /rt . jar
set CLASSPATH = %CLASSPATH% ; %OLD_CLASSPATH%
set INCLUDE = %JAVA_HOME%/ include ; %JAVA_HOME% /include /win32 ; %INCLUDE%
start cmd
cl -MD -LD helloworldimp.cpp -Fehelloworld.dll
动态库即可build成功。
于是我们可以运行java jnitest.HelloWorld,得到结果了。
稍复杂一点的例子,改变本地函数中的参数值。
三、复杂一点的例子:
的fs
我们看看下边的这个类:
public
class
Prompt
{
public Prompt() {
}
private native String getLine(String prompt);
static {
System.loadLibrary("helloworld");
}
public static void main(String[] args) {
Prompt prompt = new Prompt();
String input = prompt.getLine("Type a line: ");
System.out.println("user typed: " + input);
}
}
public Prompt() {
}
private native String getLine(String prompt);
static {
System.loadLibrary("helloworld");
}
public static void main(String[] args) {
Prompt prompt = new Prompt();
String input = prompt.getLine("Type a line: ");
System.out.println("user typed: " + input);
}
}
使用javah生成的头文件Prompt.h如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include < jni.h >
/* Header for class Prompt */
#ifndef _Included_Prompt
#define _Included_Prompt
#ifdef __cplusplus
extern " C " {
#endif
/*
* Class: Prompt
* Method: getLine
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_Prompt_getLine
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
#include < jni.h >
/* Header for class Prompt */
#ifndef _Included_Prompt
#define _Included_Prompt
#ifdef __cplusplus
extern " C " {
#endif
/*
* Class: Prompt
* Method: getLine
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_Prompt_getLine
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
#include
<
jni.h
>
#include " jnitest_HelloWorld.h "
#include " Prompt.h "
#include < stdio.h >
JNIEXPORT jstring JNICALL Java_jnitest_HelloWorld_echoString
(JNIEnv * env, jobject obj, jstring sz)
{
return sz;
}
/**
* Prompt Dll function entry
*/
JNIEXPORT jstring JNICALL Java_Prompt_getLine
(JNIEnv * env, jobject obj, jstring prompt)
{
char buf[128];
const char *str; //char is just the jbyte
str = (env)->GetStringUTFChars(prompt, NULL);
if (str == NULL) {
return NULL; /* OutOfMemoryError already thrown */
}
printf("%s", str);
(env)->ReleaseStringUTFChars(prompt, str);
/* We assume here that the user does not type more than
* 127 characters */
scanf("%s", buf);
return (env)->NewStringUTF(buf);
}
#include " jnitest_HelloWorld.h "
#include " Prompt.h "
#include < stdio.h >
JNIEXPORT jstring JNICALL Java_jnitest_HelloWorld_echoString
(JNIEnv * env, jobject obj, jstring sz)
{
return sz;
}
/**
* Prompt Dll function entry
*/
JNIEXPORT jstring JNICALL Java_Prompt_getLine
(JNIEnv * env, jobject obj, jstring prompt)
{
char buf[128];
const char *str; //char is just the jbyte
str = (env)->GetStringUTFChars(prompt, NULL);
if (str == NULL) {
return NULL; /* OutOfMemoryError already thrown */
}
printf("%s", str);
(env)->ReleaseStringUTFChars(prompt, str);
/* We assume here that the user does not type more than
* 127 characters */
scanf("%s", buf);
return (env)->NewStringUTF(buf);
}
Type a line: fjkal
user typed: fjkal
注意,这里需要指定jvm的java.library.path值,表示load动态库时的搜索路径,或者你把生成的动态库拷贝到PATH路径中的任一目录里也可以,但不推荐这么做。
还要注意一点的是C++代码和C代码里env的调用形式不太一样。
C++: (env)->ReleaseStringUTFChars(prompt, str);
而C: (env*)->ReleaseStringUTFChars(env, prompt, str);
前边的实现里,必须作出jstring类型到char*的转换,并且适时的要释放内存,否则会出现内存泄漏。
这是一些比较简单的例子。后边会有适当的篇幅介绍jni以及jvm中一些比较复杂的应用。