Java加载JNI的动态库,有两种方式:
public static void load(String filename),从作为动态库的本地文件系统中以指定的文件名加载代码文件。文件名参数必须是完整的路径名。调用 System.load(name) 实际上等效于调用:Runtime.getRuntime().load(name)。
public static void loadLibrary(String libname),加载由 libname 参数指定的系统库。将库名映射到实际系统库的方法取决于系统。调用 System.loadLibrary(name) 实际上等效于调用:Runtime.getRuntime().loadLibrary(name)。
jni加载classpath中的动态链接库
1// 系统自己会判断扩展名是dll还是so
2System.loadLibrary("test");
jni加载jar包中的动态链接库
部署应用的时候要加载jar包中的动态链接库文件,可以将本地库拷贝到环境变量path指定的路径中。一般在windows平台上直接copy到
“C:\WINDOWS\System32”目录下了事,但这样需要用户做额外操作,有时候当前系统用户也没有权限拷贝库文件到指定目录。
有人可能会想到,在Java代码中利用System.setProPerty设置lib path,指向动态库文件所在路径。不过此法不可行,因为一旦Java虚拟机启动以后,lib path就是只读的,就不能再设置进去值了。
这个问题可以这样解决:
把dll放在classpath中,用Class.getResource(str).openStream()读取这个dll,
拷贝到classpath中,用System.loadLibrary(name)加载;
如果没有权限拷贝到指定目录,也可以拷贝到temp目录中,用System.load(path)加载。
1. 拷贝到classpath中,用System.loadLibrary(name)加载
1static {
2InputStream in =null;
3FileOutputStream out =null;
4try {
5String libpath = System.getProperty("java.library.path");
6if (libpath ==null || libpath.length() ==0)
7throw new RuntimeException("java.library.path is null");
8String path =null;
9String pathSeparator = System.getProperty("path.separator");
10StringTokenizer st =new StringTokenizer(libpath, pathSeparator);
11if (st.hasMoreElements())
12path = st.nextToken();
13else
14throw new RuntimeException("can not split library path : " + libpath);
15in = Foo.class.getResource("foo.dll").openStream();
16File fooDll =new File(new File(path),"foo.dll");
17out =new FileOutputStream(fooDll);
18byte[] buffer =new byte[2048];
19int len;
20while ((len = in.read(buffer)) != -1)
21out.write(buffer,0, len);
22out.close();
23fooDll.deleteOnExit();
24System.loadLibrary("foo");
25}catch (Throwable e) {
26e.printStackTrace();
27}finally {
28// 流的判空和关闭
29}
30}
2. 拷贝到temp目录中,用System.load(path)加载
1static {
2InputStream in =null;
3FileOutputStream out =null;
4try {
5in = Foo.class.getResource("/foo.dll").openStream();
6File temporaryDll = File.createTempFile("foo",".dll");
7out =new FileOutputStream(temporaryDll);
8byte[] buffer =new byte[2048];
9int len;
10while ((len = in.read(buffer)) != -1)
11out.write(buffer,0, len);
12out.close();
13temporaryDll.deleteOnExit();
14System.load(temporaryDll.getPath());
15}catch (Throwable e) {
16e.printStackTrace();
17}finally {
18// 流的判空和关闭
19}
20}
为什么上面通过getResource取得了URL不直接去加载呢?因为如果把dll和class一起打成jar包,ClassLoader还是不能加载本地库,因为System.load(path)需要的是dll的完整路径,但并不支持jar协议。ClassLoader中用new File(name),当然会找不到文件。
1URL url = Foo.class.getResource("/java/lang/String.class");
2System.out.println(url);
3System.out.println(url.toExternalForm());
4System.out.println(url.getFile());
以上代码输出结果如下:
jar:file:/E:/Java/jdk1.6.0_31/jre/lib/rt.jar!/java/lang/String.class
jar:file:/E:/Java/jdk1.6.0_31/jre/lib/rt.jar!/java/lang/String.class
file:/E:/Java/jdk1.6.0_31/jre/lib/rt.jar!/java/lang/String.class
jni卸载动态库文件
事实证明以上调用deleteOnExit()方法并不能在系统退出后删除动态库文件,由于程序占用而导致无法删除,所以要在程序退出时卸载动态库文件,这个样在程序退出时就可以删除动态临时创建的动态库文件了。我们在程序中加个hook,让程序退出时卸载动态链接库:
1Runtime.getRuntime().addShutdownHook(new Thread() {
2public void run() {
3// unloadAllNativeLibs();
4unloadNativeLibs(temporaryDll.getName());
5}
6});
那么如何在程序推出时卸载动态库文件呢?可以通过反射调用私有属性和私有方法来卸载:
1public static synchronized void unloadAllNativeLibs() {
2try {
3ClassLoader classLoader = Foo.class.getClassLoader();
4Field field = ClassLoader.class.getDeclaredField("nativeLibraries");
5field.setAccessible(true);
6Vector libs = (Vector) field.get(classLoader);
7Iterator it = libs.iterator();
8while (it.hasNext()) {
9Object object = it.next();
10Method finalize = object.getClass().getDeclaredMethod("finalize");
11finalize.setAccessible(true);
12finalize.invoke(object);
13}
14}catch (Throwable th) {
15th.printStackTrace();
16}
17}
18
19public static synchronized void unloadNativeLibs(String libName) {
20try {
21ClassLoader classLoader = Foo.class.getClassLoader();
22Field field = ClassLoader.class.getDeclaredField("nativeLibraries");
23field.setAccessible(true);
24Vector libs = (Vector) field.get(classLoader);
25Iterator it = libs.iterator();
26while (it.hasNext()) {
27Object object = it.next();
28Field[] fs = object.getClass().getDeclaredFields();
29for (int k =0; k 30if (fs[k].getName().equals("name")) {
31fs[k].setAccessible(true);
32String dllPath = fs[k].get(object).toString();
33if (dllPath.endsWith(libName)) {
34Method finalize = object.getClass().getDeclaredMethod("finalize");
35finalize.setAccessible(true);
36finalize.invoke(object);
37}
38}
39}
40}
41}catch (Throwable th) {
42th.printStackTrace();
43}
44}
【注】unloadNativeLibs(String libName)这个 libName 不是那个“foo.dll”,而是一个“foo”+Long类型的随机数+“.dll”。