第一步,定义一个 Java 类 -- Hello. 它提供SayHello方法:
此时应注意两点:
1.为要使用的每个本地方法编写本地方法声明,其声明方式与普通 Java 方法接口没什么不同,只是必须指定 native 关键字,如下所示:
public native void SayHello(String strName);
在这个函数中,我们将根据传进的人名,向某人问好。
2.必须显式地加载本地代码库。当然要调用System.loadLibrary("hello");注 意此时不要lib,也不要.so!; 我们需在类的一个静态块中加载这个库:
static { System.loadLibrary("hello"); }
|
再加上必要的异常处理就生成如下源文件Hello.java:
public class Hello { static {
System.loadLibrary("hello"); } //声明的本地方法 public staitc native void sayHello(String strName); } |
运行命令 javac Hello.java 生成Hello.class文件。
第二步,生成本地链接库。具体过程如下:
1. 要为以上定义的类生成 Java 本地接口头文件,需使用javah,Java 编译器的 javah 功能将根据 Hello类生成必要的声明,此命令将生成Hello.h 文件,我们在共享库的代码中要包含它,javah不使默认内部命令,需要指明路径,它在JDK的bin目录下,在我的Linux环境下命令如下:
javah Hello
但是出现如下错误:
error: cannot access Hello
class file for Hello not found
javadoc: error - Class Hello not found.
Error: No classes were specified on the command line. Try -help.
原因是CLASS_PATH没有把当前目录加入其中。所以必须指定classpath 为当前目录。或者在系统CLASS_PATH加入当前路径。执行如下命令:
javah -classpath . Hello
生成的Hello.h 文件内容的第一句子为 #include <jni.h>
但是gcc里面默认环境可不知道jni.h是什么东西,jni.h在jdk 的$JAVA_HOME/include下面,可进去查看一下~
2.在与Hello.h相同的路径下创建一个CPP文件Hello.cpp。注意,自动生成的那个函数名字很长,并且 开头的 Java是大写的,大小写很致命一定要注意。内容如下:
#include "Hello.h" #include <stdio.h> //与Hello.h中函数声明相同 JNIEXPORT void JNICALL Java_Hello_sayHello (JNIEnv * env, jclass arg, jstring instring) { //从instring字符串取得指向字符串UTF编码的指针 const jbyte *str = (const jbyte *)env->GetStringUTFChars( instring, JNI_FALSE ); printf("Hello,%s/n",str); //通知虚拟机本地代码不再需要通过str访问Java字符串。 env->ReleaseStringUTFChars( instring, (const char *)str ); return; } |
所有的JNI调用都使用了JNIEnv *类型的指针,习惯上在CPP文件中将这个变量定义为env,它是任意一个本地方法的第一个参数。env指针指向一个函数指针表,在VC中可以直接 用"->"操作符访问其中的函数。
jobject 指向在此 Java 代码中实例化的 Java 对象 LocalFunction的一个句柄,相当于this指针。
后续的参数就是本地调用中有Java程序传进的参数,本例中只有一个String型参数。 对于字符串型参数,因为在本地代码中不能直接读取 Java 字符串,而必须将其转换为 C /C++字符串或 Unicode。以下是三个我们经常会用到的字符串类型处理的函数:
const char* GetStringUTFChars(jstring string,jboolean* isCopy)
3.编译生成共享库。
使用GCC时,必须通知编译器在何处查找此 Java 本地方法的支持文件,并且显式通知编译器生成位置无关的代码,在我的环境中按如下过程编译:
g++ -I /usr/lib/jvm/java-6-sun/include/linux/ -I /usr/lib/jvm/java-6-sun/include/ -fPIC -c Hello.cpp
生成Hello.o
g++ -shared -Wl,-soname,libhello.so.1 -o libhello.so.1.0 Hello.o |
生成libhello.so.1.0
接下来将生成的共享库拷贝为标准文件名
cp libhello.so.1.0 libhello.so
或者使用:
g++ -I /usr/lib/jvm/java-6-sun/include/linux/ -I /usr/lib/jvm/java-6-sun/include/ -fPIC -shared -o libLexical.so Lexical.cpp
注意在linux下,动态链接库的名字 必须是 lib****.so,必须以lib开头!
4.编写一个简单的Java程序来测试我们的本地方法。
将如下源码存为ToSay.java:
public class ToSay { public static void main(String argv[]) { Hello.sayHello("John"); } } |
用javac编译ToSay.java命令javac -cp . ToSay.java,生成ToSay.class。
向执行普通Java程序一样使用java ToSay,
java ToSay
Exception in thread "main" java.lang.NoClassDefFoundError: ToSay
Caused by: java.lang.ClassNotFoundException: ToSay
at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
Could not find the main class: ToSay. Program will exit.
原因是classpath没有当前路径。改正后执行如下命令:
java -cp . ToSay
Exception in thread "main" java.lang.UnsatisfiedLinkError: no hello in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1734)
at java.lang.Runtime.loadLibrary0(Runtime.java:823)
at java.lang.System.loadLibrary(System.java:1028)
at Hello.<clinit>(Hello.java:6)
at ToSay.main(ToSay.java:5)
依然报错。java.lang.UnsatisfiedLinkError: no HelloNative in java.library.path。这个错误很经典,原因:是java找不到库路径~: 显然: libhello.so放在当前路径 ".",只linux执行的时候却不知道在当前路径找。
a. linux下面java.library.path 和环境变脸 jdk/bin的那个个PATH不是一回事情,有另外一个默认变量 LD_LIBRARY_PATH来保存他的信息。而windows下,首先java会找当前目录,其次,它会去环境变量的地址找!
b。 由于linux的路径特殊,所以,解决方法 1-可以调用sysout(System.getProperty("java.library.path")); 来查看! 然后把libXXXX.so拷贝到那里面的目录下去
2 设置环境变量 export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
3 可以单次执行时候指定library位置:
java -Djava.library.path=. -cp . ToSay
我们会看到在屏幕上出现Hello,John。