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

news/2024/9/18 4:35:45/ 标签: 学习, 笔记, dll, 进程控制

🎯导读:本文介绍了使用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程序不会受到影响。

dllDllRunnerjar_111">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.javajar 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启动DllRunnerString 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.ppmy.cn/news/1520716.html

相关文章

2024全国大学省数学建模竞赛C题-优秀论文分析(2023)

​某商超蔬菜类商品动态定价与补货决策研究 摘 要 随着生鲜市场规模的持续扩大&#xff0c;蔬菜零售行业的竞争也愈加激烈。为帮助某商超 改善经营模式&#xff0c;本文基于题目所给数据信息&#xff0c;建立数学模型进行分析&#xff0c;从而制定合理 的蔬菜类商品动态定价与…

第3章-04-Python库BeautifulSoup安装与讲解

🏆作者简介,黑夜开发者,CSDN领军人物,全栈领域优质创作者✌,CSDN博客专家,阿里云社区专家博主,2023年CSDN全站百大博主。 🏆数年电商行业从业经验,历任核心研发工程师,项目技术负责人。 🏆本文已收录于专栏:Web爬虫入门与实战精讲,后续完整更新内容如下。 文章…

牛客(两个数组的交集)

NC313 两个数组的交集 题目题解(19)讨论(7)排行面经 new 简单 通过率&#xff1a;29.64% 时间限制&#xff1a;1秒 空间限制&#xff1a;256M 知识点二分哈希排序双指针 描述 给定两个整数数组分别为&#x1d45b;&#x1d462;&#x1d45a;&#x1d460;1nums1, &am…

从最浅层剖析C语言————第四节(深入了解二维数组存储以及实现二分查找)

目录 1. 前情提要 2. sizeof计算数组元素个数 3. 二维数组的创建及其初始化 3.1 二维数组的概念 3.2 二维数组的创建 3.3 二维数组的初始化 4. 二维数组的使用 4.1 二维数组的下标 4.2 二维数组在内存之中的存储 4.3 C99中的变长数组 1. 前情提要 前面一篇博客中&…

【Python机器学习】NLP分词——词的“情感”

目录 VADER&#xff1a;一个基于规则的情感分析器 朴素贝叶斯 无论NLP流水线中使用的是单个词、n-gram、词干还是词元作为词条&#xff0c;每个词条都包含了一些信息&#xff0c;这些信息中一个重要部分是词的情感&#xff0c;即一个词所唤起的总体感觉或感情。这种度量短语或…

设计模式 —— 单例模式

文章目录 一、单例模式1.1 单例模式定义1.2 单例模式的优点1.3 单例模式的缺点1.4 单例模式的使用场景 二、普通案例2.1 饿汉式单例模式(Eager Initialization Singleton)2.2 懒汉式单例模式(Lazy Initialization Singleton) 参考资料 本文源代码地址为 java-demos/singeleton-…

基于Pytorch框架的深度学习HRnet网络人像语义分割系统源码

第一步&#xff1a;准备数据 头发分割数据&#xff0c;总共有5711张图片&#xff0c;里面的像素值为0和1&#xff0c;所以看起来全部是黑的&#xff0c;不影响使用 第二步&#xff1a;搭建模型 计算机视觉领域有很多任务是位置敏感的&#xff0c;比如目标检测、语义分割、实例…

基于RDMA的nfs服务

背景 ib网卡nfs服务实现简单的存储共享&#xff0c;暂时顶替还未上线的存储设备&#xff0c;同时也解决 单纯的使用scp rsync等不支持rdma协议拷贝无法正确使用ib网络 说明 前提是系统上已配置安装好ib网卡驱动&#xff0c;且ib网络正常使用&#xff0c;配置参考 https://bl…

Ubuntu 20.04 上使用 Prometheus 和 Grafana 监控 PHP 8.0

本文方案监听php状态信息是采用php-php-exporter直接通过sock监控php-fpm信息。还可以通过nginx查询php状态信息从而监控&#xff0c;中间需要加上nginx配置。详见本文末尾 查找最新的 php-fpm_exporter 版本 访问 php-fpm_exporter 的 GitHub releases 页面 来查找最新版本。…

输电线路分布式故障诊断系统:分布式智慧网络的构建

输电线路分布式故障诊断系统&#xff1a;分布式智慧网络的构建 今天&#xff0c;就让深圳鼎信智慧科技陪大家一起走进输电线路分布式故障定位系统的世界&#xff1a; 1、系统架构&#xff1a;分布式智慧网络的构建 输电线路分布式故障定位系统主要由三大核心部分组成&#x…

苹果11月推出新款M4 Mac:Mac mini设计焕新 MacBook Pro仅例行更新

据外媒 MacRumors 报道&#xff0c;苹果公司计划在 11 月推出首批 M4 Mac&#xff0c;这一时间表与去年相似&#xff0c;当时苹果公司在同样的时间点中宣布推出搭载 M3 芯片的 MacBook Pro。 ▲ 苹果公司在 2023 年 10 月 31 日推出的 M3 MacBook Pro 同时根据古尔曼爆料称苹果…

2024年最新紫光嵌入式面试题及参考答案

Linux 设备驱动如何与硬件设备匹配? 在 Linux 系统中,设备驱动与硬件设备的匹配主要通过以下几种方式: 设备树匹配:设备树是一种描述硬件设备信息的数据结构。设备驱动可以通过解析设备树中的节点信息,来确定是否与特定的硬件设备匹配。设备树中的每个节点都包含了硬件设备…

开放式耳机真的好用吗?百元高性价比蓝牙耳机推荐指南

开放式耳机有其独特的优势&#xff0c;在很多方面表现出色&#xff0c;个人认为是很好用的。 它有以下优点&#xff1a; 佩戴舒适&#xff1a;开放式耳机不堵塞耳道&#xff0c;不会对耳道产生压迫&#xff0c;长时间佩戴耳朵也不易感到闷热或疼痛&#xff0c;比如连续使用数…

FLUX.1 Dev:采样器 + 调度器比较,哪个最适合Flux

原文链接&#xff1a;FLUX.1 Dev&#xff1a;采样器 调度器比较&#xff0c;哪个最适合Flux (chinaz.com) 国外有位大佬对Flux各种采样器和调度器进行组合测试出最适合的采样模式 直接看结论&#xff1a; 上面的表格&#xff0c;列出了FLUX模型体系瞎各种采样器和调度器的组…

《CSS 3D 变形探秘:开启网页设计新维度》

一、移动&#xff1a;精确控制元素位置 translate函数为我们提供了强大的移动能力。可以沿 x 轴、y 轴甚至 z 轴来调整元素的位置。无论是简单地在平面上平移&#xff0c;还是在三维空间中进行精准定位&#xff0c;translate都能轻松胜任。它让元素的布局更加灵活多变&#xf…

【前端面试】设计循环双端队列javascript

题目 https://leetcode.cn/problems/design-circular-deque/description/ 存储循环队列的向量空间是循环的&#xff0c;用通俗的话来讲&#xff0c;就是我们在做next或者prev操作时&#xff0c;不会发生溢出 取模、或者直接判断是否为0/size返回一个值。 数组实现 用函数来…

adb大全指令(持续更新)

连接adb adb connect 192.168.1.133(局域网ip)连接调试命令 adb shell打开日志工具 logcat

minio最新源码编译(处理安全扫描中跨域访问、.js.map等不安全问题) 版本:RELEASE.2024-06-26T01-06-18Z

编译前注意事项 编译基于tag为RELEASE.2024-06-26T01-06-18Z的版本处理安全扫描问题。如&#xff1a;敏感信息泄露、.js.map、跨域访问问题需要准备两个工程&#xff0c;前端工程console和minio工程&#xff0c; 目录结构处理: gowork/ │ └── src/├── github.com├── …

【Xcode】Xcode基本使用指引

文章目录 Xcode安装及iphone模拟器的安装Xcode中Debug和Release的切换Xcode中控件的使用Xcode工程的基本组成Xcode UI基本设计及使用iOS开发项目中的日志系统静态库支持多种架构制作xcframeworklibuv库Xcode路径.pbxproj文件苹果平台的宏Leaks检测内存泄漏OC中的ARC和MRC小结 X…

朴素贝叶斯分类算法

文章目录 贝叶斯定理问题背景朴素贝叶斯朴素贝叶斯分类算法原理朴素贝叶斯分类算法步骤给定示例数据 极大似然估计如何求 P ( 特征 ∣ 类别 ) P ( 类别 ) P(\text{特征} \mid \text{类别}) \times P(\text{类别}) P(特征∣类别)P(类别)&#xff1f;如何求 P ( 类别 ) P(\text{…