前言

在java调用dll的项目中,之间的部署的方式,是需要手动提前将所需的dll,替换放在jdkbin文件夹或者C:\Windows\System32文件夹的下,后续开发的过程中,dll文件需要不断更新,一方面dll版本维护成为比较麻烦的事情,还有部署方式略显繁琐,经过一段的摸索后,在项目jar包启动的时候实现dll自动部署的方案。

原理

JNI的加载方式分为两种一种是动态加载就是在JDKbin文件或者\System32文件夹的下,寻找dll加载,一种是利用绝对路径去加载,之前利用绝对路径去加载的时候,会遇到一个问题,因为dll在jar包里面,获取的绝对路径中间都会包含“XXX.jar!”,导致dll加载失败,想到的思路是,在项目启动的时候,自动将dll输出到jar所在的同级目录下,然后再根据新输出dll的绝对路径去加载即可。

项目中dll的位置

代码 

核心代码示例

    @PostConstruct
    private void initDll() throws IOException {
        String dllPath = this.getClass().getResource("/dll/").getPath() + "DwgOperInterface.dll";
        if (dllPath.indexOf(".jar") > 0) {
            dllPath = dllPath.substring(0, dllPath.lastIndexOf(".jar"));
            dllPath = dllPath.substring(6, dllPath.lastIndexOf("/"));
            dllPath = File.separator + dllPath + "/DLL";
            copyDir(dllPath);
            System.out.println("dll加载路径:" + dllPath);
            System.load(dllPath + "/DwgOperInterface.dll");
        } else {
            System.load(dllPath);
        }
    }

    /**
     * 复制文件夹下所有文件
     *
     * @author tarzan LIU
     * @date 2020/10/22
     */
    private void copyDir(String javaUrl) throws IOException {
        URL url = JNIMapUtil.class.getClassLoader().getResource("dll/");
        String jarPath = url.toString().substring(0, url.toString().indexOf("!/") + 2);
        URL jarURL = new URL(jarPath);
        JarURLConnection jarCon = (JarURLConnection) jarURL.openConnection();
        JarFile jarFile = jarCon.getJarFile();
        Enumeration<JarEntry> jarEntrys = jarFile.entries();
        while (jarEntrys.hasMoreElements()) {
            JarEntry entry = jarEntrys.nextElement();
            String name = entry.getName();
            if (name.contains("dll/") && !entry.isDirectory()) {
                String[] strs = name.split("/");
                copyFile(strs[strs.length - 1], javaUrl);
                log.info("file name is :" + strs[strs.length - 1] + " finish copy!!");
            }
        }
    }

    /**
     * 复制一份dll放到系统临时文件夹中,处理jar包,dll无法读取问题(代替loadLibrary)
     *
     * @author tarzan LIU
     * @date 2020/10/16
     */
    private synchronized static void copyFile(String libName, String javaUrl) throws IOException {
        InputStream in = null;
        FileOutputStream writer = null;
        File make = new File(javaUrl);
        if (!make.exists() && !make.isDirectory()) {
            make.mkdirs();
        }
        File extractedLibFile = new File(javaUrl + File.separator + libName);
        try {
            String dllUrlInJar = "/dll/";
            in = JNIMapUtil.class.getResourceAsStream(dllUrlInJar + libName);
            if (in == null)
                in = JNIMapUtil.class.getResourceAsStream(libName);
            JNIMapUtil.class.getResource(libName);
            BufferedInputStream reader = new BufferedInputStream(in);
            writer = new FileOutputStream(extractedLibFile);
            byte[] buffer = new byte[1024];
            while (reader.read(buffer) > 0) {
                writer.write(buffer);
                buffer = new byte[1024];
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (in != null)
                in.close();
            if (writer != null)
                writer.close();
        }
    }

相关知识

Java Native Interface(JNI)是Java平台提供的一种机制,用于在Java应用程序中与本地代码进行交互。通过JNI,Java程序可以调用本地代码(通常是C或C++编写的),并且本地代码也可以调用Java代码。在接下来,我将为您详细解释JNI的主要概念、用途和基本用法。

JNI的主要概念:

  1. Native方法:Native方法是指在Java类中声明但不实现的方法,它使用关键字native修饰。这些方法的实现由本地代码提供,并通过JNI接口与Java代码进行交互。
  2. JNI接口:JNI提供了一组函数和数据结构,用于在Java代码和本地代码之间进行通信。开发者可以使用JNI接口访问Java虚拟机(JVM)内部的数据和功能。
  3. JNI环境:JNI环境是一个指向JNI接口函数表的指针,它提供了访问Java运行时环境的能力,如获取对象引用、调用Java方法等。
  4. 本地库:本地库是包含本地代码的动态链接库文件,通常使用C/C++编写。Java程序需要加载本地库才能调用其中的本地方法。

JNI的主要用途:

  1. 提供底层功能:JNI允许Java程序调用本地代码,以实现一些底层功能,如访问操作系统API、硬件设备驱动程序等。
  2. 性能优化:JNI允许将性能关键的任务委托给本地代码,以提高执行效率。本地代码通常比Java代码更接近底层硬件和操作系统,可以通过优化算法或使用与平台相关的特性来实现更高的性能。
  3. 跨平台支持:JNI提供了一种跨平台的机制,使得Java程序可以在不同的操作系统上调用适当的本地库,从而实现平台无关性。

基本使用方法:

  1. 编写本地方法的实现:使用C/C++编写包含本地方法的实现代码。这些方法需要遵循JNI规范,并使用合适的数据类型和函数进行交互。

  2. 生成本地库文件:使用本地编译器(如gcc或Visual Studio)将本地方法的实现代码编译为本地库文件(例如动态链接库.so或.dll文件)。

  3. 在Java类中声明本地方法:在Java类中使用native关键字声明与本地方法对应的方法。方法签名必须与本地方法的实现一致。

  4. 加载本地库:在Java代码中使用System.loadLibrary()方法加载本地库。该方法接受库文件名作为参数,会搜索并加载具有相应名称的本地库。

  5. 调用本地方法:通过普通Java代码调用声明的本地方法。在运行时,JVM将通过JNI接口将方法调用传递给本地库,执行对应的本地代码。

简单的示例:

下面是一个简单的示例,展示了如何使用JNI调用本地方法:

1.编写本地方法的实现(C代码):

#include <jni.h>

JNIEXPORT jstring JNICALL Java_com_example_MyClass_getHello(JNIEnv *env, jobject obj) {
    return (*env)->NewStringUTF(env, "Hello from native code!");
}

2.生成本地库文件:

gcc -shared -fpic -o libmylib.so MyLib.c

3.在Java类中声明本地方法:

public class MyClass {
    public native String getHello();
}

4.加载本地库:

System.loadLibrary("mylib");

5.调用本地方法:

MyClass myObj = new MyClass();
String result = myObj.getHello();
System.out.println(result);

总结: JNI是Java平台提供的一种机制,用于在Java应用程序中与本地代码进行交互。通过JNI,我们可以调用本地代码并让本地代码调用Java代码,从而实现底层功能、性能优化和跨平台支持。使用JNI涉及编写本地方法实现、生成本地库文件、声明本地方法并加载本地库。JNI是一项强大的技术,可以将Java与本地代码紧密结合,扩展Java的能力和应用场景。请注意,以上解释只是对JNI的基本介绍,如果您有特定的问题或需求,请进一步详细了解JNI的官方文档和资料。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐