| | |
| | | package chuankou; |
| | | |
| | | import java.io.File; |
| | | import java.io.FileOutputStream; |
| | | import java.io.InputStream; |
| | | import java.net.URISyntaxException; |
| | | import java.nio.file.Files; |
| | | import java.nio.file.Path; |
| | | import java.nio.file.Paths; |
| | | import java.nio.file.StandardCopyOption; |
| | | |
| | | /** |
| | | * Ensures the correct jSerialComm native library is available on Windows/x86_64 systems. |
| | | * 修复 jSerialComm 库加载问题 |
| | | */ |
| | | public final class SerialPortNativeLoader { |
| | | private static final String LIB_PROPERTY = "com.fazecast.jSerialComm.library.path"; |
| | | private static final String EXPECTED_DLL = "jSerialComm.dll"; |
| | | private static volatile boolean libraryConfigured = false; |
| | | |
| | | // 静态块:确保在任何 SerialPort 类加载之前运行 |
| | | static { |
| | | ensureLibraryPresentEarly(); |
| | | } |
| | | |
| | | private SerialPortNativeLoader() { |
| | | // utility class |
| | | } |
| | | |
| | | public static void ensureLibraryPresent() { |
| | | if (System.getProperty(LIB_PROPERTY) != null) { |
| | | /** |
| | | * 早期初始化 - 在静态块中运行 |
| | | */ |
| | | private static synchronized void ensureLibraryPresentEarly() { |
| | | if (libraryConfigured) { |
| | | return; |
| | | } |
| | | |
| | | String osName = System.getProperty("os.name", "").toLowerCase(); |
| | | if (!osName.contains("win")) { |
| | | return; |
| | | } |
| | | |
| | | String arch = System.getProperty("os.arch", "").toLowerCase(); |
| | | if (!arch.contains("64")) { |
| | | return; |
| | | } |
| | | |
| | | Path candidateDir = Paths.get("lib", "native", "windows", "x86_64").toAbsolutePath(); |
| | | File dllFile = candidateDir.resolve(EXPECTED_DLL).toFile(); |
| | | if (!dllFile.isFile()) { |
| | | candidateDir = resolveFromCodeSource(); |
| | | if (candidateDir != null) { |
| | | dllFile = candidateDir.resolve(EXPECTED_DLL).toFile(); |
| | | } |
| | | } |
| | | |
| | | if (dllFile.isFile()) { |
| | | System.setProperty(LIB_PROPERTY, candidateDir.toString()); |
| | | } else { |
| | | System.err.println("Expected jSerialComm native library not found. Checked " + dllFile); |
| | | } |
| | | } |
| | | |
| | | private static Path resolveFromCodeSource() { |
| | | |
| | | System.out.println("开始初始化 jSerialComm 本地库..."); |
| | | |
| | | // 第一步:设置系统属性,阻止自动下载 |
| | | setCriticalSystemProperties(); |
| | | |
| | | // 第二步:尝试从项目资源中提取 DLL |
| | | try { |
| | | java.security.CodeSource codeSource = SerialPortNativeLoader.class.getProtectionDomain().getCodeSource(); |
| | | if (codeSource == null) { |
| | | return null; |
| | | extractNativeLibraryFromResources(); |
| | | libraryConfigured = true; |
| | | System.out.println("jSerialComm 本地库初始化成功"); |
| | | } catch (Exception e) { |
| | | System.err.println("从资源提取 DLL 失败: " + e.getMessage()); |
| | | // 尝试备用方案 |
| | | try { |
| | | setupLibraryFromFileSystem(); |
| | | libraryConfigured = true; |
| | | System.out.println("从文件系统加载 jSerialComm 成功"); |
| | | } catch (Exception ex) { |
| | | System.err.println("所有加载方式都失败: " + ex.getMessage()); |
| | | // 最后尝试:允许 jSerialComm 使用默认方式 |
| | | allowDefaultBehavior(); |
| | | } |
| | | |
| | | Path location = Paths.get(codeSource.getLocation().toURI()).toAbsolutePath(); |
| | | Path baseDir = location.getParent(); |
| | | if (baseDir == null) { |
| | | return null; |
| | | } |
| | | |
| | | Path siblingLibDir = baseDir.resolveSibling("lib").resolve("native").resolve("windows").resolve("x86_64"); |
| | | if (siblingLibDir.toFile().isDirectory()) { |
| | | return siblingLibDir; |
| | | } |
| | | |
| | | Path relativeLibDir = baseDir.resolve("lib").resolve("native").resolve("windows").resolve("x86_64"); |
| | | if (relativeLibDir.toFile().isDirectory()) { |
| | | return relativeLibDir; |
| | | } |
| | | } catch (URISyntaxException | SecurityException ignored) { |
| | | // ignore |
| | | } |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 设置关键系统属性(在类加载前必须设置) |
| | | */ |
| | | private static void setCriticalSystemProperties() { |
| | | // 阻止自动下载和端口代理 |
| | | System.setProperty("com.fazecast.jSerialComm.port.agent", "disabled"); |
| | | System.setProperty("com.fazecast.jSerialComm.autoload", "false"); |
| | | System.setProperty("com.fazecast.jSerialComm.autoDispose", "false"); |
| | | |
| | | // 禁用日志和调试 |
| | | System.setProperty("com.fazecast.jSerialComm.debug", "false"); |
| | | System.setProperty("com.fazecast.jSerialComm.log", "false"); |
| | | |
| | | System.out.println("已设置 jSerialComm 系统属性"); |
| | | } |
| | | |
| | | /** |
| | | * 从 JAR 资源中提取 DLL |
| | | */ |
| | | private static void extractNativeLibraryFromResources() throws Exception { |
| | | // 确定目标路径 |
| | | String tempDir = System.getProperty("java.io.tmpdir"); |
| | | Path targetDir = Paths.get(tempDir, "jSerialComm", "2.10.4"); |
| | | |
| | | // 如果目录不存在,创建它 |
| | | if (!Files.exists(targetDir)) { |
| | | Files.createDirectories(targetDir); |
| | | } |
| | | |
| | | Path dllPath = targetDir.resolve(EXPECTED_DLL); |
| | | |
| | | // 检查 DLL 是否已存在且可用 |
| | | if (Files.exists(dllPath)) { |
| | | System.out.println("DLL 已存在于: " + dllPath); |
| | | // 设置库路径 |
| | | System.setProperty(LIB_PROPERTY, targetDir.toString()); |
| | | return; |
| | | } |
| | | |
| | | // 从类路径资源加载 DLL |
| | | String resourcePath = "/native/windows/x86_64/" + EXPECTED_DLL; |
| | | InputStream dllStream = SerialPortNativeLoader.class.getResourceAsStream(resourcePath); |
| | | |
| | | if (dllStream == null) { |
| | | // 尝试其他可能的路径 |
| | | resourcePath = "/lib/native/windows/x86_64/" + EXPECTED_DLL; |
| | | dllStream = SerialPortNativeLoader.class.getResourceAsStream(resourcePath); |
| | | } |
| | | |
| | | if (dllStream == null) { |
| | | // 尝试从文件系统加载 |
| | | resourcePath = "lib/native/windows/x86_64/" + EXPECTED_DLL; |
| | | File file = new File(resourcePath); |
| | | if (file.exists()) { |
| | | dllStream = Files.newInputStream(file.toPath()); |
| | | } |
| | | } |
| | | |
| | | if (dllStream == null) { |
| | | throw new RuntimeException("无法在资源中找到 " + EXPECTED_DLL); |
| | | } |
| | | |
| | | // 复制 DLL 到临时目录 |
| | | try (InputStream is = dllStream; |
| | | FileOutputStream fos = new FileOutputStream(dllPath.toFile())) { |
| | | |
| | | byte[] buffer = new byte[8192]; |
| | | int bytesRead; |
| | | while ((bytesRead = is.read(buffer)) != -1) { |
| | | fos.write(buffer, 0, bytesRead); |
| | | } |
| | | } |
| | | |
| | | System.out.println("DLL 已提取到: " + dllPath); |
| | | |
| | | // 设置库路径 |
| | | System.setProperty(LIB_PROPERTY, targetDir.toString()); |
| | | System.setProperty("java.library.path", |
| | | System.getProperty("java.library.path") + File.pathSeparator + targetDir.toString()); |
| | | } |
| | | |
| | | /** |
| | | * 从文件系统设置库 |
| | | */ |
| | | private static void setupLibraryFromFileSystem() throws Exception { |
| | | // 检查多个可能的路径 |
| | | String[] possiblePaths = { |
| | | "lib/native/windows/x86_64/" + EXPECTED_DLL, |
| | | "./lib/native/windows/x86_64/" + EXPECTED_DLL, |
| | | System.getProperty("user.dir") + "/lib/native/windows/x86_64/" + EXPECTED_DLL, |
| | | "../lib/native/windows/x86_64/" + EXPECTED_DLL |
| | | }; |
| | | |
| | | for (String path : possiblePaths) { |
| | | File dllFile = new File(path); |
| | | if (dllFile.exists()) { |
| | | System.out.println("找到 DLL: " + dllFile.getAbsolutePath()); |
| | | // 使用 DLL 所在目录 |
| | | File parentDir = dllFile.getParentFile(); |
| | | System.setProperty(LIB_PROPERTY, parentDir.getAbsolutePath()); |
| | | System.setProperty("java.library.path", |
| | | System.getProperty("java.library.path") + File.pathSeparator + parentDir.getAbsolutePath()); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | throw new RuntimeException("在文件系统中找不到 " + EXPECTED_DLL); |
| | | } |
| | | |
| | | /** |
| | | * 允许默认行为(最后的手段) |
| | | */ |
| | | private static void allowDefaultBehavior() { |
| | | System.err.println("警告:使用 jSerialComm 的默认加载行为"); |
| | | // 清空阻止属性,让 jSerialComm 自己处理 |
| | | System.clearProperty("com.fazecast.jSerialComm.autoload"); |
| | | System.clearProperty("com.fazecast.jSerialComm.port.agent"); |
| | | } |
| | | |
| | | /** |
| | | * 公共方法 - 供其他代码调用 |
| | | */ |
| | | public static synchronized void ensureLibraryPresent() { |
| | | // 静态块已经执行,这里只是确保 |
| | | if (!libraryConfigured) { |
| | | ensureLibraryPresentEarly(); |
| | | } |
| | | } |
| | | } |