关于Eclipse Plugin开发 (2)

来源:互联网 发布:舰队收藏改 淘宝 编辑:程序博客网 时间:2024/05/16 19:23

      快好了,快好了,可是又冒出一个问题,花去了半天的时间,是我太浮躁,还是我太愚钝,或者两个都有,或者这些都已经不重要了……

      1. 环境变量的设置

      Plug-In终于调出了一个相对稳定的Version,轻松的exprot出来,然后copy到eclispse目录的plugins子目录下,再次启动Eclipse,我的宝贝插件居然告诉我“chosen operation is not currently available”。

      (1)不用想,这肯定是在plug-in初始化的时候出错了。定点输出,果然Activator的构造函数没有调用。

      (2)在通过一个简单的例子测试一下自己的导出过程有没有错,结果很失望,导出没错。

      (3)接下来就自然而然地想到了那个曾经这么我很久的jni的本地库,结果就是这里的问题。


      因为以前一直觉得环境变量设置麻烦,所以就简单地把这个jni库放在项目的根目录下,每次运行调试都不会有问题。但是当把plugin导出后,由于java library path中没有这个库,自然就是失败了。


      怎么改呢?

      (1)最直接的方法就是在java里利用 System.out.println(System.getProperty("java.library.path"));把java library path全部输出,再把握的plugin copy过去一份,不考虑客户的情绪,这样做无疑最简单。然而可惜,这是服务器,一共输出九个path,全都是只读的,没办法,google!

    

      (2)以前都很少懂得网上资源是多么丰富。找到了高手的代码:
    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 = DbgServerBridge.class.getResource(FULL_NAME).openStream();
            final File dllFile = new File(new File(path), FULL_NAME);
            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 library error!", e);
        }
        System.loadLibrary(LIBRARY_PATH);
    } 

    把本大库放在classpath中,用Class.getResource(str).openStream()读取这个库,然后写到temp目录中,最后用System.load(path)来动态加载。

      关于这段程序还有两个为什么:

      第一:既然已经知道lib的url,为什么不直接加载?这是因为System.loadLibrary(path)需要的是lib的绝对路径,而且不能解析jar协议。

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

输出:

      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

      这两个前缀System.loadLibrary是不认识的。

      第二:如果把运行setProperty("java.library.path", XXX),之后再load不是更快么?

       类似于这样:

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

      这样也是不行的,这要看ClassLoader的源代码:

      // 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");
      }

      因为ClassLoader把java library path的搜索结果保存在一个静态变量中,VM在启动时装载,并且以后都不在修改,再调用setProperty是没有用的。但是就有人要修改这个静态变量,这段代码在第三个解决方案中提起。

       到这里,觉得这个第二种方法真的很大胆,居然在运行时先复制在加载,再于退出时删除。可是我的担心经过验证之后逼着我放弃这个方案,还是因为我的机器是服务器,那些java library path都是只读,不能创建temp file。

      (3)这个方案不错,就是想方设法改变读进来的path

         static {
            try{
                setLibrary(LIBRARY_PATH);
                System.out.println(System.getProperty("java.library.path"));
                System.loadLibrary(LIBRART_NAME);
           }catch(Exception e){
               MsgLog.WriteError(e);
           }
        }
   
        private static void setLibrary(String libPath) throws IOException{
            if(libPath == null){
                return;
            }
            try { 
                Field field = ClassLoader.class.getDeclaredField("usr_paths");  //保存java library paths
                field.setAccessible(true); 
                String[] paths = (String[])field.get(null); 
                for (int i = 0; i < paths.length; i++) { 
                    if (libPath.equals(paths[i])) { 
                        break; 
                    } 
                } 
                String[] tmp = new String[paths.length+1]; 
                System.arraycopy(paths,0,tmp,0,paths.length); 
                tmp[paths.length] = libPath; 
                field.set(null,tmp);
          } catch (IllegalAccessException e) { 
               throw new IOException("Failed to get permissions to set library path"); 
          } catch (NoSuchFieldException e) { 
              throw new IOException("Failed to get field handle to set library path"); 
          }          
      }

        这个方案通过反射,改变静态变量usr_paths的值,有点曲线救国的意思,不过的确是个好方法。不足之处在与usr_paths会增加程序对平台的依赖性,因为换一个VM,可能usr_paths就不是这个名字,程序就不能用了。不过权衡比较,这是觉得不错的方法,也是最终采用的方法。

      (4)修改java library path

          java library path主要包括LD_LIBRARY_PATH和JAVA_HOME下的一些路径
          linux 下LD_LIBRARY_PATH设置方法有以下三种:
          a) 临时修改,只对当前终端有效
             在terminal中执行:export LD_LIBRARY_PATH=XXPATH
          b) 让当前帐号以后都优先加载当前目录的动态库
             修改~/.bashrc,在文件末尾加上两行: export LD_LIBRARY_PATH=XXPATH,重新登录
          c) 让所有帐号从此都优先加载当前目录的动态库
             修改/etc/profile在文件末尾加上两行: export LD_LIBRARY_PATH=XXPATH,重新登录

 

          注意:$HOME 表示用户根目录    /  表示系统根目录   ./ 当前目录  ../上一级目录

                   分隔符 :

                   echo $LD_LIBRARY_PATH 回显

 

         哎,马马虎虎总结了这么多,其中多数是在网上搜集的,少许是自己的一点心得。

         发现自己还是对代码的兴趣更浓一些,总觉得设置这些繁琐无味,可是自己的编码水平有这么样呢?自己的开发效率又如何呢?

 

 

     

原创粉丝点击