博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

jni中关于dll的装载问题

Posted on 2006-11-23 11:05  daniel-shen  阅读(1755)  评论(1编辑  收藏  举报

装载本地库
 (lithium的成果,纯粹作为资料保存)
搞过JNI的都知道,本地库要放到系统path中,这样,Java进程在运行中才能找到本地库并动态加载。我们可以通过环境变量System.getProperty("java.library.path")来查看当前JVM搜索本地库的路径。

这时,就会遇到一个问题,部署应用的时候要记住将本地库拷贝到环境变量path指定的路径中。一般在windows平台上直接copy到C:\WINDOWS\System32目录下了事。但要换一台机器部署怎么办?除了要把Java程序拿过去,还要记的把本地库也copy到正确的目录,真麻烦。于是想看看有什么好办法来解决这个问题。

首先,最容易想到的是,把本地库和class文件放在一起,利用Class.getResource(str)找到路径,然后加到环境java.library.path中:
java代码: 

URL url = Foo.class.getResource("Foo.class");
String path = (new File(url.getPath())).getParent();
System.setProperty("java.library.path", path);
 

看上去很好,但却不能工作。查了一下ClassLoader的源代码,原来它把搜索路径定义为静态变量并只初始化一次,后面再设置java.library.path就没有用了。ClassLoader代码片断:
java代码: 

// The paths searched for libraries
static private String usr_paths[];
static private String sys_paths[];
...
if (sys_paths == null) {
        usr_paths = initializePath("java.library.path");
        sys_paths = initializePath("sun.boot.library.path");
}
 

正在一筹莫展是,翻看JACOB的源代码,忽然有了惊喜的发现。
java代码: 

try
{
        //Finds a stream to the dll. Change path/class if necessary
    InputStream inputStream = getClass().getResource("/jacob.dll").openStream();
    //Change name if necessary
    File temporaryDll = File.createTempFile("jacob", ".dll");
    FileOutputStream outputStream = new FileOutputStream(temporaryDll);
    byte[] array = new byte[8192];
    for (int i = inputStream.read(array); i != -1; i = inputStream.read(array)) {
                outputStream.write(array, 0, i);
        }
    outputStream.close();
    temporaryDll.deleteOnExit();
    System.load(temporaryDll.getPath());
    return true;
}
catch(Throwable e)
{
        e.printStackTrace();
    return false;
}
 

高,真是好办法。和我一样,把dll放在classpath中,用Class.getResource(str).openStream()读取这个dll,然后写到temp目录中,最后用System.load(path)来动态加载。多说一句,为什么得到了jacob.dll的URL不直接去加载呢?想想看,如果把dll和class一起打成Jar包,ClassLoader还是不能加载本地库,因为System.load(path)需要的是dll的完整路径,但并不支持jar协议。还不明白,看看下面的代码:
java代码: 

URL url = Foo.class.getResource("/java/lang/String.class");
System.out.println(url.toExternalForm());
System.out.println(url.getFile());
 

在我的机器上的运行结果是:
java代码: 

jar:file:/D:/jdk1.5.0_06/jre/lib/rt.jar!/java/lang/String.class
file:/D:/jdk1.5.0_06/jre/lib/rt.jar!/java/lang/String.class
 

ClassLoader中用new File(name),当然会找不到文件。同时,看看我的第一种方法,就算能设置成功环境java.library.path,如果dll是在jar包中,还是加载不了。

到现在,你是不是觉得问题已经解决了?还没呢!jacob的很多源文件中已经写了下面的代码:
java代码: 

static {
        System.loadLibrary("jacob");
}
 

除非我去掉这一句重新编译jacob的源代码,否则系统还是会报错。不过,既然有了上面的想法,稍微变通一下,就可以巧妙的解决。首先找到环境java.library.path,然后把dll拷贝到其中一个路径中就行了。
java代码: 

static {
        try {
                String libpath = System.getProperty("java.library.path");
                if ( libpath==null || libpath.length() == 0 ) {
                        throw new RuntimeException("java.library.path is null");
                }
                       
                String path = null;
                StringTokenizer st = new StringTokenizer(libpath, System.getProperty("path.separator"));
                if ( st.hasMoreElements() ) {
                        path = st.nextToken();
                } else {
                        throw new RuntimeException("can not split library path:" + libpath);
                }
                       
                InputStream inputStream = Foo.class.getResource("jacob.dll").openStream();
                final File dllFile = new File(new File(path), "jacob.dll");
                if (!dllFile.exists()) {
                        FileOutputStream outputStream = new FileOutputStream(dllFile);
                        byte[] array = new byte[8192];
                        for (int i = inputStream.read(array); i != -1; i = inputStream.read(array)) {
                                outputStream.write(array, 0, i);
                        }
                        outputStream.close();
                }
                //dllFile.deleteOnExit();       
                Runtime.getRuntime().addShutdownHook(new Thread(){
                        public void run() {
                                if ( dllFile.exists() ) {
                                        boolean delete = dllFile.delete();
                                        System.out.println("delete : " + delete);
                                }
                        }
                });
                } catch (Throwable e) {
                        throw new RuntimeException("load jacob.dll error!", e);
        }
}
 

唯一的美中不足,在系统关闭的时候删除dll总是不能成功,试了两种办法都不行。想想也对,dll正被程序使用,当然不能删除。翻了一下API,Java好像没用提供unload本地库的功能,只好做罢。




自己在项目中的代码:
package jp.co.nec.necst.conference.util;
import java.io.*;
import java.util.*;
public class RegistryUtil {

 static {
        try {
                String libpath = System.getProperty("java.library.path");
                if ( libpath==null || libpath.length() == 0 ) {
                        throw new RuntimeException("java.library.path is null");
                }
                       
                String path = null;
                StringTokenizer st = new StringTokenizer(libpath, System.getProperty("path.separator"));
                if ( st.hasMoreElements() ) {
                        path = st.nextToken();
                } else {
                        throw new RuntimeException("can not split library path:" + libpath);
                } 
                Class thisClass = RegistryUtil.class;
                InputStream inputStream = thisClass.getResource("RegistryUtil.dll").openStream();
                File dllFile = new File(new File(path), "RegistryUtil.dll");
                if (!dllFile.exists()) {
                        FileOutputStream outputStream = new FileOutputStream(dllFile);
                        byte[] array = new byte[8192];
                        for (int i = inputStream.read(array); i != -1; i = inputStream.read(array)) {
                                outputStream.write(array, 0, i);
                        }
                        outputStream.close();
                       // System.loadLibrary("RegistryUtil");
                }
               
                System.loadLibrary("RegistryUtil");
              
                } catch (UnsatisfiedLinkError e) {
                    throw e;
                }catch (Throwable e) {
                        throw new RuntimeException("load RegistryUtil.dll error!", e);
        }
}
 
 public static native String getRegValue(String regDir, String regName);
}