在Java程序中使用JNI调用平台相关函数
前一阵子遇到的一个问题是,如何在Java程序中调用Windows DLL的函数。查了查资料,得知使用名为JNI(Java Native Interface)的方式可以让Java调用DLL中的函数。JNI的使用方法如下:
- 在Java中定义JNI调用,并撰写使用该JNI调用的相关代码。
- 根据定义好的JNI调用生成相应的C语言头文件。
- 利用上一步生成的头文件,使用C书写Windows平台上的代码。
- 编译C代码生成DLL库文件。
- 执行Java程序中JNI调用的代码。
我们可以看到,第三步使用C语言书写代码时,须遵从JNI的规范,这就为我们带来了一个问题:如果我们想使用的DLL文件是已经编译好的,它未必遵从JNI规范,怎么办?另一个问题是,在Java程序中需要定义JNI调用,但是我们显然不希望在业务逻辑代码中定义JNI调用。于是产生了下面的结构: #ref(2006/09/jni.png,nolink,JNI结构图)
最上方的Java Application是业务逻辑的实现部分,最下方的Windows DLL是我们拥有的DLL函数库,而中间虚线框的部分即为使用JNI进行开发的部分。
我们假设Java Application部分的代码如下。文件名为JavaApp.java。
package jnisample;
public class JavaApp {
public static final void main(String args[]) {
JNIWrapper.Hello();
}
}
这里JNIWrapper类为我们即将进行开发的JNI Wrapper的类名。
我们先创建一个空的JNIWrapper.java以使得JavaApp.java能够正常编译。
package jnisample;
class JNIWrapper {
static void Hello() {}
}
然后我们编译这两个文件:
javac -d . JNIWrapper.java
javac -d . JavaApp.java
编译完成之后我们将在jnisample目录下得到JNIWrapper.class和JavaApp.class两个文件。
我们再假设现有的DLL函数库的实现代码如下:(文件名HelloDll.c)
#include <windows.h>
__declspec(dllexport) BOOL __stdcall Hello();
BOOL __stdcall Hello() {
MessageBox(NULL, "Helo, world!", "Hello", MB_OK | MB_ICONINFORMATION);
return TRUE;
}
然后我们使用Microsoft Visual Studio 6.0自带的编译器 cl 进行编译:
cl HelloDll.c -MD -LD User32.lib
这样我们将得到HelloDll.dll文件。将该文件与jnisample目录放到同一层次上。
下面我们来看一下如何使用JNI在JavaApp.java中调用HelloDll.c中的Hello函数。首先我们将JNI Wrapper模块的JNIWrapper.java改成下面的样子:
package jnisample;
class JNIWrapper {
static native void _dll_Hello();
static {
try {
System.loadLibrary("DllWrapper");
} catch (Exception e) {
System.out.println("DllWrapper.dll load errer");
}
}
static void Hello() {
_dll_Hello();
}
}
然后再次进行编译:
javac -d . JNIWrapper.java
下一步利用J2SE提供的工具来生成必要的头文件。执行下列命令:
javah -jni jnisample.JNIWrapper
这样我们得到一个名为jnisample_JNIWrapper.h的文件。打开这个文件之后可以看到,里面定义了这样一个函数:
JNIEXPORT void JNICALL Java_jnisample_JNIWrapper__1dll_1Hello(JNIEnv *, jclass);
这就是我们应当在DLL Wrapper模块中实现的函数。
创建DllWrapper.c文件,内容如下:
#include "jnisample_JNIWrapper.h"
#include <windows.h>
typedef BOOL (*FUNCHELLO)();
JNIEXPORT void JNICALL Java_jnisample_JNIWrapper__1dll_1Hello(JNIEnv * jenv, jclass jcls)
{
HANDLE hDll;
FUNCHELLO fnHello;
hDll = LoadLibrary("HelloDll.dll");
if (hDll) {
fnHello = (FUNCHELLO)GetProcAddress(hDll, "_Hello@0");
if (fnHello) fnHello();
FreeLibrary(hDll);
}
}
然后进行编译:
cl DllWrapper.c -I"C:\Program Files\java\jdk1.5.0_04\include" -I"C:\Program Files\Java\jdk1.5.0_04\include\win32" -MD -LD
生成DllWrapper.dll文件。到此为止我们的目录结构如下:
DllWrapper.dll
HelloDll.dll
jnisample/JNIWrapper.class
jnisample/JavaApp.class
执行以下命令即可看到执行结果了。
java jnisample.JavaApp
以上简单地阐述了如何利用JNI来调用平台相关的本地代码。在实际应用中还会出现很多问题,例如Java和C语言之间的数据类型转换等,这里就不一一详述了。
(示例程序下载) jnisample.zip