1. 介绍
在java编程中,有时候为了克服内存管理和性能限制,可能需要从java代码中去调用native的代码(像C/C++)。Java中的JNI就提供了这种调用机制。
JNI有点难,因为它涉及到两种编程语言的调用和运行。
JNI编程需要的基本知识有:
- Java.
- C/C++ 和GCC编译器(GCC and Make)
- Gygwin or MinGW
- Eclipse C/C++ Development Tool(CDT)
2. 开始
JNI和C
第一步:编译一个使用C代码的Java类 HelloJNI.java
public class HelloJNI { static { System.loadLibrary("hello"); // hello.dll (Windows) or libhello.so (Unixes) } // A native method that receives nothing and returns void private native void sayHello(); public static void main(String[] args) { new HelloJNI().sayHello(); // invoke the native method } }
当这个类加载时, 静态初始化加载器去调用了System.LoadLibrary()用来加载native库 "Hello",这个库中包括一个native的方法sayHello()。它在Windows系统中运行时,加载的库是“Hello.dll”,在Unix系统中运行时,加载的库是“libhello.so”。这个库必须加到Java的库路径中,或者在java运行时,使用java.library.path来告诉虚拟机它的路径。否则虚拟机将会抛出异常UnstisfiedLinkError。所以在Eclipse中执行时,在VM argument选项中加入参数 -Djava.library.path=path_to_lib.
接着,我们使用native关键字声明了sayHello方法作为一个native的方法,他表示这个方法在其他语言中被实现了。所以在java代码中native方法是没有被实现,仅有一个声明。SayHello在native库加载的是时候被加载。
第二步:创建C/C++的头文件 HelloJNI.h
使用java提供的命令javah就可以自动创建一个C/C++的头文件。
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class HelloJNI */ #ifndef _Included_HelloJNI #define _Included_HelloJNI #ifdef __cplusplus extern "C" { #endif /* * Class: HelloJNI * Method: sayHello * Signature: ()V */ JNIEXPORT void JNICALL Java_HelloJNI_sayHello (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif
这个头文件声明了一个C的函数JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIENV *, jobject).函数名约定的方式是Java_{package_and_classname}_{function_name}(JNI arguments). 在包名中的'.'将被下划线代替。
参数JNIEnv*代表JNI的环境,可以让你访问所有的JNI函数。
参数jobject代表java中的this
extern "C" 表示能被C++编译器识别,提醒C++编译器,这些函数时C的命名协议。C/C++拥有不同的命名协议和方式。C++ 支持函数重载,使用一个叫做name mangling scheme来区分重载的函数。
第三步: C 的代码实现
#include <jni.h> #include <stdio.h> #include "HelloJNI.h" JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) { printf("Hello World!\n"); return; }
保存文件,命名为HelloJNI.c,这个程序仅仅只是打印Hello World,编译这个程序在windows系统上使用MinGW GCC命令:
gcc -Wl,--add-stdcall-alias -I"<JAVA_HOME>\include" -I"<JAVA_HOME>\include\win32" -shared -o hello.dll HelloJNI.c
解释:
The compiler options used are: -Wl: The -Wl to pass linker option --add-stdcall-alias to prevent UnsatisfiedLinkError (symbols with a stdcall suffix (@nn) will be exported as-is and also with the suffix stripped). (Some people suggested to use -Wl,--kill-at.) -I: for specifying the header files directories. In this case "jni.h" (in "<JAVA_HOME>\include") and "jni_md.h" (in "<JAVA_HOME>\include\win32"), where <JAVA_HOME> denotes the JDK installed directory. Enclosed the directory in double quotes if it contains spaces. -shared: to generate share library. -o: for setting the output filename "hello.dll".
也可以让编译和链接分两步来:
// Compile-only with -c flag. Output is HElloJNI.o > gcc -c -I"<JAVA_HOME>\include" -I"<JAVA_HOME>\include\win32" HelloJNI.c // Link into shared library "hello.dll" > gcc -Wl,--add-stdcall-alias -shared -o hello.dll HelloJNI.o
使用nm可以查找dll中的函数 nm hello.dll|grep say
对于在Windows系统上的Gygwin GCC, 需要定义使用-D _int64="long long"来定义__int64为long long
对于gcc-3,它提供了-mno-cygwin选项来编译DLL文件,而且不依赖Cygwin的库文件。
gcc-3 -D __int64="long long" -mno-cygwin -Wl,--add-stdcall-alias -I"<JAVA_HOME>\include" -I"<JAVA_HOME>\include\win32" -shared -o hello.dll HelloJNI.c
第四步: 运行
> java HelloJNI or > java -Djava.library.path=. HelloJNI
原文来之于http://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html