Java多进程调用dll程序和exe程序

news2024/11/25 19:23:28

🎯导读:本文介绍了使用Java调用本地DLL及EXE程序的方法。针对DLL调用,文章提供了基于Java Native Access (JNA) 库的具体实现方案,包括定义Java接口以映射DLL中的函数,并展示了如何加载DLL及调用其中的方法。对于EXE程序的调用,则提出了一种通过批处理文件(BAT)启动外部可执行文件的方式,并通过轮询检查结果文件的存在来判断计算是否完成。此外,还探讨了使用ProcessBuilder启动独立进程来运行DLL调用程序DllRunner.jar,以及如何处理子进程的输入输出流以避免阻塞。文中还提到了在不同JDK版本间编译与运行时可能遇到的兼容性问题及其解决方案。

文章目录

  • Java调用DLL程序
    • jna介绍
    • 依赖
    • 编写接口
    • 使用
  • DLL错误导致java进程退出
    • 处理方式
      • **在C++代码中加强错误处理**
      • **使用守护进程(Watchdog)**
      • Java**使用分离的进程调用DLL**
        • java调用dll的程序打包成`DllRunner.jar`
        • 使用Process新开进程调用`DllRunner.jar`
  • Java调用exe

Java调用DLL程序

jna介绍

Java Native Access (JNA) 是一个Java开发库,它允许Java程序直接调用本地操作系统API和C语言库。JNA库通过JNI (Java Native Interface) 进行工作,但是它不需要编写任何C代码或者创建本机库。

依赖

<dependency>
    <groupId>net.java.dev.jna</groupId>
    <artifactId>jna</artifactId>
    <version>5.14.0</version>
</dependency>

编写接口

在这里插入图片描述

编写接口的目的是对应dll的方法,dll的方法如下

在这里插入图片描述

import com.sun.jna.Library;
import com.sun.jna.Native;

import java.io.File;

/**
 * @Author dam
 * @create 2024/7/2 15:24
 */
public interface AlgorithmDll extends Library {
    String dllPath = System.getProperty("user.dir") + File.separator + "algorithmDll" + File.separator + "GDUT_PACK.dll";

    /**
     * 对接C++程序的方法
     * @param input 输入
     * @return 输出
     */
    String RunDLL(String input);

    public static AlgorithmDll getInstance() {
        return Native.loadLibrary(dllPath, AlgorithmDll.class);
    }
}

使用

调用dll的方法

output = AlgorithmDll.getInstance().RunDLL(input);

下面方法的作用是接收一个计算任务,然后调用dll程序进行计算,最后接收dll程序的输出进行返回

public static CIMSTask calculate(CIMSTask cimsTask) {
    System.out.println(cimsTask.getNestTaskCode() + "开始计算");
    String input = JSON.toJSONString(cimsTask);
    String output;
    try {
        TxtUtil.write(new File(inputPath + "input" + cimsTask.getNestTaskID() + ".json"), input, "utf-8");
        output = AlgorithmDll.getInstance().RunDLL(input);
        if ("".equals(output)) {
            throw new RuntimeException(cimsTask.getNestTaskCode() + "计算失败");
        }
        System.out.println(cimsTask.getNestTaskCode() + "计算完成");
        try {
            TxtUtil.write(new File(outputPath + "output" + cimsTask.getNestTaskID() + ".json"), output, "utf-8");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    return JSON.parseObject(output, CIMSTask.class);
}

上述代码就可以实现DLL的简单调用,但是如果要多线程调用该方法同时计算多个任务的话,会有风险,请继续阅读下一小节

DLL错误导致java进程退出

当C++的DLL在Java中调用时,如果DLL发生了严重错误导致进程退出(例如PROCESS EXIT(-1)),Java虚拟机(JVM)也会随之中断。这种情况通常是由于未捕获的异常或访问无效内存等导致的C++崩溃。由于JVM和C++共享同一个进程空间,如果C++导致整个进程崩溃,Java程序也会被迫退出。

如果多个任务在同时计算,一旦一个任务发生了错误中断,会导致所有任务都被迫中断。

处理方式

在C++代码中加强错误处理

首先,确保C++代码中所有可能引发异常或导致崩溃的地方都进行了适当的错误处理。例如,使用try-catch块捕获所有异常并妥善处理,避免异常传递到Java层导致进程崩溃。

使用守护进程(Watchdog)

编写一个守护进程监控Java应用程序,如果发现Java程序因C++崩溃而终止,可以自动重新启动它。虽然这并不能防止崩溃,但可以提供一种恢复机制。

Java使用分离的进程调用DLL

通过将DLL调用放在一个单独的进程中运行,主Java程序通过IPC(进程间通信)与这个进程进行通信。如果DLL进程崩溃,主Java程序不会受到影响。

java调用dll的程序打包成DllRunner.jar

调用dll程序如下:

import com.sun.jna.Native;
import com.sun.jna.Library;

import java.io.*;

/**
 * @Author dam
 * @create 2024/8/26 10:17
 */
public class DllRunner {

    public interface AlgorithmDll extends Library {
        String dllPath = System.getProperty("user.dir") + File.separator + "algorithmDll" + File.separator + "GDUT_PACK.dll";
        public static AlgorithmDll getInstance() {
            return Native.loadLibrary(dllPath, AlgorithmDll.class);
        }
        String RunDLL(String input);
    }

    public static void main(String[] args) throws UnsupportedEncodingException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter writer = new PrintWriter(new OutputStreamWriter(System.out, "UTF-8"), true);

        try {
            // 从标准输入读取输入数据
            String input = reader.readLine();
            // 调用DLL函数
            String output = AlgorithmDll.INSTANCE.RunDLL(input);
            // 将结果输出到标准输出
            writer.println("RESULT:" +output);
        } catch (Exception e) {
            // 处理异常并输出到标准错误
            System.err.println("Error: " + e.getMessage());
            e.printStackTrace(System.err);
            System.exit(-1);
        }
    }
}

jna-5.14.0.jar和DllRunner程序放在一个包下面

在这里插入图片描述

执行如下命令,将程序打包

javac -classpath jna-5.14.0.jar -d . DllRunner.java

jar cf DllRunner.jar -C . com/

执行报错,因为有的字符没有响应的编码,直接把注释改成英文就行,或者直接删除注释

在这里插入图片描述

打包成功之后的目录如下:

在这里插入图片描述

打包之后,可以使用命令jar tf DllRunner.jar来查看jar包里面的结构

在这里插入图片描述

如果运行子进程之后报下面这种错误,意思是使用了更高版本的JDK来编译了程序,但是运行程序的时候使用的JDK版本较低,会出现版本不兼容问题。我的电脑安装了多个JDK,默认使用JDK17来编译了代码,但是后面运行程序使用的是JDK8,所以出现了如下报错

在这里插入图片描述

解决方法很简单,在编译代码的时候,使用--release 版本号来指定就行

javac --release 8 -classpath jna-5.14.0.jar -d . DllRunner.java
jar cf DllRunner.jar -C . com/

关于javac的命令,可以在命令行用javac查看

在这里插入图片描述

使用Process新开进程调用DllRunner.jar

我们在主线程中启动了一个新的线程来处理子进程的输出流。由于 ProcessBuilder 中的流是阻塞的,会使用一个缓冲区来存储这些输出,如果子进程的输出数据量占满了缓冲区,可能会导致线程挂起或阻塞,需要使用下面的代码,将dll的输出流合并到java中

// 合并标准输出和错误输出
builder.redirectErrorStream(true); 

最终的调用DLL的方法如下

public static CIMSTask calculateWithDll(CIMSTask cimsTask) {
    System.out.println(cimsTask.getNestTaskCode() + " 开始计算");

    String input = JSON.toJSONString(cimsTask);
    AtomicReference<String> output = new AtomicReference<>();

    try {
        // 将输入数据写入文件
//            TxtUtil.write(new File(inputPath + "input" + cimsTask.getNestTaskID() + ".json"), input, "utf-8");

        // 使用ProcessBuilder启动DllRunner
        String jarPath = System.getProperty("user.dir") + File.separator + "process" + File.separator + "DllRunner.jar;" +
                System.getProperty("user.dir") + File.separator + "process" + File.separator + "jna-5.14.0.jar";
        System.out.println("jarPath:" + jarPath);
        List<String> command = Arrays.asList(
                "java",
                "-cp",
                jarPath,
                "com.dam.algorithm.DllRunner"
        );
        ProcessBuilder builder = new ProcessBuilder(command);
        // 合并标准输出和错误输出
        builder.redirectErrorStream(true);
        Process process = builder.start();

        // 向进程的标准输入写入任务数据
        OutputStream outputStream = process.getOutputStream();
        PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream, "UTF-8"), true);
        writer.println(input);
        // 确保所有数据都被写入
        writer.flush();

        // 处理子进程输出和错误流
        new Thread(() -> {
            try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = br.readLine()) != null) {
                    // 将子进程的输出打印到主进程运行窗口
                    if (line.contains("RESULT:")) {
                        output.set(line.split("RESULT:")[1]);
                    } else {
                        System.out.println("任务:" + cimsTask.getNestTaskCode() + "的子进程输出:" + line);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();

        // 等待进程结束
        int exitVal = process.waitFor();
        if (exitVal == 0) {
            System.out.println("子进程运行成功");
        } else {
            System.err.println("子进程运行出错,错误码: " + exitVal);
        }

        if (output.get() == null || output.get().isEmpty()) {
            throw new RuntimeException(cimsTask.getNestTaskCode() + " 计算失败");
        }
        System.out.println(cimsTask.getNestTaskCode() + " 计算完成");

    } catch (Exception e) {
        throw new RuntimeException(e);
    }

    CIMSTask cimsTaskRes = JSON.parseObject(output.get(), CIMSTask.class);
    cimsTaskRes.setNestTaskCode(cimsTask.getNestTaskCode());
    return cimsTaskRes;
}

Java调用exe

上面的调用方法比较麻烦,需要先打包才能调用,也可以通过调用exe来执行c++程序。调用逻辑是先生成一个bat文件,在bat文件中定位到exe程序所在位置,然后执行exe。"> " + cimsTask.getNestTaskCode() + "_output.log 2>&1"的作用是把exe的输出转移到日志文件中,防止exe进程陷入阻塞状态

/**
 * 调用exe来求解
 * 通过自旋来判断任务是否计算完成
 *
 * @param cimsTask
 * @return
 */
public static CIMSTask calculateWithExe(CIMSTask cimsTask) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    System.out.println(cimsTask.getNestTaskCode() + " 开始计算,时间:" + sdf.format(new Date()));

    String input = JSON.toJSONString(cimsTask);

    try {
        // 将输入数据写入文件
        String taskInputPath = inputPath + cimsTask.getNestTaskCode() + ".json";
        TxtUtil.write(new File(taskInputPath), input, "utf-8");
        String exeStr =
                exePath.substring(0, 1) + ":\n" +
                        "cd " + exePath + "\n" +
                        "GDUT_PACK.exe " + cimsTask.getNestTaskCode() + ".json> " + cimsTask.getNestTaskCode() + "_output.log 2>&1";
        String batPath = exePath + File.separator + "bat" + File.separator + cimsTask.getNestTaskCode() + ".bat";
        TxtUtil.write(new File(batPath), exeStr, "utf-8");
        System.out.println("batPath:" + batPath);
        // 执行bat文件,开始计算
        ProcessBuilder processBuilder = new ProcessBuilder(batPath);
        Process process = processBuilder.start();

        // 扫描获取结果
        String resultPath = outputPath + cimsTask.getNestTaskCode() + ".json";
        File resFile;
        while (true) {
            resFile = new File(resultPath);
            if (resFile.exists()) {
                System.out.println(cimsTask.getNestTaskCode() + " 计算完成,时间:" + sdf.format(new Date()));
                String resultStr = TxtUtil.read(new File(outputPath + cimsTask.getNestTaskCode() + ".json"), "utf-8");
                CIMSTask resCimsTask = JSON.parseObject(resultStr, CIMSTask.class);
                resCimsTask.setNestTaskCode(cimsTask.getNestTaskCode());
                // 删除结果文件
                resFile.delete();
                // 删除输入文件
                File taskInputFile = new File(taskInputPath);
                if (taskInputFile.exists()) {
                    taskInputFile.delete();
                }
                // 删除bat文件
                File batFile = new File(batPath);
                if (batFile.exists()) {
                    batFile.delete();
                }
                // 删除输出日志
                File logFile = new File(exePath + File.separator + cimsTask.getNestTaskCode() + "_output.log");
                if (logFile.exists()) {
                    logFile.delete();
                }
                // 停止进程
                process.destroy();
                return resCimsTask;
            }
            Thread.sleep(1000);
        }
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

这种方法实现比较方便,但是有几个缺点:

  • 主进程无法得知子进程在什么时候执行完毕,只能通过自旋的方式来判断结果文件是否生成,文件成功生成则说明exe运行结束
  • 需要将exe的输出转移到log文件中,如果进程出错进入死循环,疯狂输出,可能导致日志文件非常大。当然可以定时清空日志里面的内容来解决该问题
  • 当java程序判断出exe程序存在问题时,无法直接通过java代码中断exe程序

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2092787.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python 数据可视化:工具与实践

文章目录 数据可视化可视化库特点对比实例&#xff1a;绘制基本数据分布图评估维度 交互式可视化与静态图表实例&#xff1a;创建交互式折线图评估维度 实时数据可视化实例&#xff1a;展示实时股票数据评估维度 图表设计原则实例&#xff1a;设计适合展示销售数据的条形图评估…

论文辅助笔记:LP_BERT

1 train_task1.py 1.1 main部分 读取命令行参数&#xff0c;调用task1函数 1.2 task1 train 1.3 task1 valid 1.3 collate_fn 2 Dataset 2.1 train dataset 2.2 valid dataset 3 LPBERT 3.1 不同的embedding day-of-week embedding和time-of-day embedding X位置和Y位置的…

色彩与笔触的交响:广州米塔在线科教技术有限公司揭秘PS绘画秘籍!

在数字艺术的广阔天地里,PS无疑是一颗璀璨的明星&#xff0c;它不仅在图像处理领域独领风骚&#xff0c;更以其强大的功能成为了众多艺术家和设计师进行数字绘画的首选工具。广州米塔在线科教技术有限公司&#xff0c;作为致力于艺术教育与技术分享的平台&#xff0c;深知掌握P…

sealos快速搭建k8s集群

一&#xff0c;环境准备 1&#xff0c;三台&#xff08;搭建一主两从集群&#xff09;或五台&#xff08;三主两从集群&#xff09;虚拟机&#xff0c; 安装alimaLinux系统 &#xff0c;相同的root密码&#xff0c;不要安装docker。 如果是alimaLinux-mini版本操作系统&#xf…

PMP–知识卡片--SCQA金字塔表达

SCQA模型通过四个关键元素&#xff1a;情景冲突疑问答案&#xff0c;建立了一个精确而有逻辑的表达框架。同时&#xff0c;它也能够帮助你构建合理的逻辑链条&#xff0c;让别人更容易理解和接受你的观点。 情景&#xff1a;通过描述背景和现状引入话题&#xff0c;这个元素帮助…

两个月冲刺软考——关系模式中的候选关键字与如何分解为无损连接并保持函数依赖的解法(例题讲解,看完必会)

1. 数据库中的简单属性、多值属性、复合属性、派生属性 简单属性&#xff1a;指不能够再分解成更小部分的属性&#xff0c;通常是数据表中的一个列。例如学生表中的“学号”、“姓名”等均为简单属性。 多值属性&#xff1a;指一个属性可以有多个值。例如一个学生可能会有多个…

掌握EF Core:全方位面试指南,助你从初级到高级轻松晋级

一、前言 这份指南旨在帮助你为主要考察 Entity Framework Core (EF Core) 的面试做好准备&#xff0c;内容涵盖基础、中级和高级三个不同经验级别。每个级别包括10个高频面试题&#xff0c;附有解题思路和详细的解答示例。 二、基础级别 重点在于 EF Core 的基本概念和使用…

nginx源码编译

华子目录 准备下载nginx源码包关闭firewalld和selinux安装依赖环境 安装解压关闭nginx的debug功能执行./configure进行环境检测添加nginx系统用户使用make编译使用make install安装 进入到prefix指定的目录中查看启动nginx服务关闭nginx添加nginx环境变量卸载nginx 准备 下载n…

猫头虎 分享:Python库 SciPy 的简介、安装、用法详解入门教程

&#x1f42f; 猫头虎 分享&#xff1a;Python库 SciPy 的简介、安装、用法详解入门教程 今天猫头虎带您深入探索SciPy&#xff0c;一个在数据科学和人工智能领域必不可少的Python库&#xff01; &#x1f4dd; 摘要 在数据科学和人工智能领域&#xff0c;SciPy 是一个关键的…

【电脑小白】告别蓝屏恐慌:一步步教你排查和解决蓝屏问题,从此告别蓝屏烦恼!

在日常学习和工作中&#xff0c;电脑已经成为我们日常生活和工作中不可或缺的一部分。然而&#xff0c;电脑的蓝屏问题却成为许多朋友&#xff0c;尤其是电脑小白们的噩梦。一旦遭遇蓝屏&#xff0c;大多数人一时都会感到手足无措。 因此&#xff0c;本文将向各位朋友介绍遇到蓝…

迁移学习之领域泛化

对目标领域一无所知&#xff0c;并不是要适应到某一个特定的领域上的问题通常称为领域泛化。领 域泛化可又分成两种情况。一种情况是训练数据非常丰富&#xff0c;包含了各种不同的领域&#xff0c;测试数据 只有一个领域。如图1&#xff08;a&#xff09;所示&#xff0c;比如…

2024年8月30日(docker部署project-exam-system系统 并用Dockerfile构建java镜像)

一、回顾 1.使用harbao仓库 1. Python -- version 2. yum -y update 3. yum -y install python2-pip 4. pip install -- upgrade pip 20.3 -i https://mirrors.aliyun.com/pypi/simple 5. pip install docker-compose -i https://mirrors.aliyun.com/pypi/simple 6. source do…

向量、数量积、向量积

目录 一、向量的定义二、向量是有序的数字列表三、向量的基本分类四、向量的运算律五、向量的基本运算1、向量加法2、向量乘法&#xff08;数乘&#xff09;3、向量减法4、点积&#xff08;内积或数量积&#xff09;5、叉积&#xff08;外积或向量积&#xff09;6、向量的模&am…

RTA-OS Port Guide学习(一)-基于S32K324 OS

文章目录 前言OS Port的安装Port CharacteristicsParameters of ImplementationConfiguration ParametersStack used for C-startup(SpPreStartOS)Stack used when idle (SpStartOS)Stack overheads for ISR activation (SpIDisp)Stack overheads for ECC tasks (SpECC)Stack o…

LLM的范式转移:RL带来新的 Scaling Law

从几周前 Sam Altman 在 X 上发布草莓照片开始&#xff0c;整个行业都在期待 OpenAI 发布新模型。根据 The information 的报道&#xff0c;Strawberry 就是之前的 Q-star&#xff0c;其合成数据的方法会大幅提升 LLM 的智能推理能力&#xff0c;尤其体现在数学解题、解字谜、代…

<Rust>egui学习之小部件(三):如何为窗口UI元件设置布局(间隔、水平、垂直排列)?

前言 本专栏是关于Rust的GUI库egui的部件讲解及应用实例分析&#xff0c;主要讲解egui的源代码、部件属性、如何应用。 环境配置 系统&#xff1a;windows 平台&#xff1a;visual studio code 语言&#xff1a;rust 库&#xff1a;egui、eframe 概述 本文是本专栏的第三篇博…

TWRP 使用帮助 第三方Recovery

简介 TWRP 是国外安卓爱好者开发的一款工具全称为&#xff1a;Team Win Recovery Project。是一个由Omnirom开源团队中的Dees Troy主导开发&#xff0c;旨在打造最完美第三方recovery的开源项目。目前主要由包括Dees Troy在内的4个人共同维护。 主要作用包括刷机、备份、救砖 …

C++判断语句(基础速通)ac-wing

倍数 #include <iostream> using namespace std; int a, b; int main() {cin >> a >> b;if (a % b 0 || b % a 0) cout << "Sao Multiplos";else cout << "Nao sao Multiplos";return 0; }零食 #include <iostream>…

通用后台管理系统实战演示(Vue3 + element-plus)汇总篇一

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

菜鸟笔记-001 如何用JavaScript脚本在文字中实现向左或向右插入空格

这是一个网友在线上问我的一个问题&#xff1a; 如何用JavaScript脚本在文字中实现向左或向右插入空格 下图是在文字左边插入了1/2个空格的效果&#xff1a; 那么如何用javascript 实现向左向右插入空格&#xff1f; 其实这个功能&#xff0c;我在实际工作确实很少碰到&#xf…