一、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); } }
Sysem.loadLibrary在static段里,表示在类HelloWorld初始化时,动态加载一个动态库,win32里这个库名应该叫helloworld.dll。这样任何一个HelloWorld实例都可以动态调用helloworld.dll中的方法了。 第一步,我们先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
需要说明的是,
#ifdef __cplusplus 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; }
这里采用最简单的实现,直接将参数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
我们在Run窗口里直接运行D:/msdev/VC98/Bin/vcvars32,就会新启一个带有C++以及Java JNI相关环境变量的cmd窗口。进入到刚才的class目录,运行: 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); } }
Prompt类,就是想调用C代码中的用户输入,然后把输入的东西回显出来,就这么简单,但是返回值是用户输入的串,不是参数里的prompt值。加载的动态库依然借用前边的helloworld.dll,这里不带包名,以与前例区分。 使用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
下边是完整的实现,将前例和本例的合到了一起(helloworldimp.cpp)。
#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); }
运行java -Djava.library.path=D:/msdev/MyProjects/helloworld Prompt,结果如下: 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中一些比较复杂的应用。