返回

Java 串口通信: 32位迁移至64位解决方案

java

Java 串口通信:从32位迁移到64位

问题背景

旧的 Java 应用程序依赖于名为 libLinuxSerialParallel.so 的动态链接库进行串口通信。该库为 32 位版本,导致必须在 Ubuntu 22 等 64 位系统上使用 32 位兼容层。将应用程序迁移至 64 位架构势在必行,目的是利用 64 位平台的性能优势并简化系统维护。迁移的关键在于寻找合适的替代库,并确保其能正常工作。在进行 64 位编译后,出现 UnsatisfiedLinkError 异常,提示“wrong ELF class: ELFCLASS32”,说明尝试加载32位版本的so库。此外还出现了栈保护(stack guard)警告,提示旧库可能存在安全隐患。

问题分析

直接原因在于原有的 libLinuxSerialParallel.so 为 32 位编译的动态链接库,而 64 位 Java 虚拟机(JVM)无法直接加载它。根本原因则在于 32 位和 64 位应用程序的内存模型差异以及编译时所用指令集的不同,这导致库文件在不同的平台之间不能直接互用。另外,由于加载的so库比较老旧,JVM出现栈保护警告,表示需要更新库,增加栈保护机制。

解决方案一:使用librxtx-java替代

原理

librxtx-java 提供了一种在 Java 中进行串口通信的标准方式,其64位版本可直接与64位JVM配合使用。此库包含了原生代码和Java接口,使得开发者可以直接在Java应用程序中使用标准的串口API。这种替代方案避免了兼容性问题。

步骤

  1. 安装 librxtx-java:

    在终端中使用 apt 安装 librxtx-java:

    sudo apt-get install librxtx-java
    

    该命令会自动下载并安装 librxtx-java_2.2.0+dfsg-2_amd64.deb (或者最新的版本),其中包含必要的64位库文件。

  2. 修改 Java 代码 :

    不再直接调用 libLinuxSerialParallel.so。需要改写你的代码,使用 javax.comm 或者其他的第三方库来进行串口的读写操作。

//  javax.comm 的使用范例
import javax.comm.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;

public class SerialPortExample {
   public static void main(String[] args) {
       // 列出可用的串口
       Enumeration<?> portList = CommPortIdentifier.getPortIdentifiers();
       while (portList.hasMoreElements()) {
           CommPortIdentifier portId = (CommPortIdentifier) portList.nextElement();
           if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) {
             System.out.println("Found serial port: " + portId.getName());
             try {
                // 打开端口
               SerialPort serialPort = (SerialPort) portId.open("SerialExample", 2000);
                // 设置串口参数
                 serialPort.setSerialPortParams(9600,SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
                  // 读写操作
                 InputStream inputStream = serialPort.getInputStream();
                OutputStream outputStream = serialPort.getOutputStream();
                 
                // 读取串口数据
                  byte[] buffer = new byte[1024];
                  int len = inputStream.read(buffer);
                  if(len > 0)
                  {
                     String receiveStr = new String(buffer, 0 , len);
                     System.out.println("Receive data :" + receiveStr );
                  }
              
                 //写入串口数据
                 String sendStr = "Hello SerialPort!";
                 outputStream.write(sendStr.getBytes());
                
                 //关闭串口
                 serialPort.close();

              }
              catch (PortInUseException e){
                  System.err.println("Port in use :" + e.getMessage());

               }
             catch(IOException ex)
              {
                  System.err.println("IOException Error: " + ex.getMessage());
              }

              catch(NoSuchPortException e)
             {
                  System.err.println("NoSuchPort Exception Error:" + e.getMessage());
             }
               catch(UnsupportedCommOperationException e)
              {
                System.err.println("Unsupported Comm Exception Error:" + e.getMessage());

              }
             
          }
     }
 }
}

注意: 要编译以上代码,你需要添加 javax.comm 的jar包作为依赖项,具体的jar包文件位置一般在安装librxtx-java的目录,根据实际安装路径决定。另外,也需要引入相关的java.comm 的原生库。

  1. 测试 :

    编译并运行 Java 应用程序,确认其能正常进行串口通信。确保没有 UnsatisfiedLinkError 错误,栈保护警告也已消失。

额外建议

  • 确认系统用户拥有访问串口的权限,通常是 dialout 组的成员。你可以使用以下命令将用户加入 dialout 组: sudo usermod -a -G dialout $USER,然后重新登录。
  • 如果使用了第三方串口库(如 RXTX ),务必选择支持 64 位的版本。

解决方案二:编译和使用自己构建的 libLinuxSerialParallel.so

原理

若没有 librxtx-java 或其他合适替代方案,也可尝试自行编译 libLinuxSerialParallel.so 库,前提是拥有源码。 需要用64位编译器针对目标架构构建新的库,确保其能够与 64 位 JVM 兼容。

步骤

  1. 获取源码:
    确保你有 libLinuxSerialParallel.so 对应的源代码, 否则,请不要轻易尝试。

  2. 配置编译环境:

    安装64位开发工具链,确保 GCC 等相关编译工具正常工作。

    sudo apt-get update
    sudo apt-get install build-essential gcc g++
    
  3. 配置编译参数:
    Makefile 或者编译脚本中加入正确的编译参数,指定生成 64 位的共享库:

    CFLAGS += -m64 -fPIC
    

    在configure文件中也要指定编译参数,确保编译生成的是64位库。

    或者也可以直接在命令行进行编译,命令需要添加 -fPIC, -m64 选项

    g++  -shared -fPIC -m64 your_source_files.c -o libLinuxSerialParallel.so 
    

    其中your_source_files.c是你原先的库源码文件。注意,可能需要添加一些其他的库作为依赖项,例如 -lstdc++. 根据实际情况来选择合适的库

  4. 替换旧库:

    编译完成后,使用新生成的 64 位 libLinuxSerialParallel.so 替换原先的 32 位版本, 并放在 java 程序的library 加载路径中。

  5. 执行修复堆栈:

    如果出现 execstack 的问题,可执行以下命令修复新生成的库:

  sudo execstack -c <新库文件路径>
  1. 测试 :

    运行应用程序,确认已成功加载64位的 libLinuxSerialParallel.so库,同时程序可以正常运行,没有UnsatisfiedLinkError,以及stack guard 的警告信息。

额外建议

  • 从源代码编译库需要仔细的测试和调试,务必确保库的功能和兼容性没有问题,另外需要确认对应的license是否符合使用要求。
  • 对于安全方面的顾虑,建议优先考虑使用成熟稳定的第三方库。

结论

将 Java 串口通信程序从 32 位迁移到 64 位架构是必要的一步,其可通过使用 64 位版本的串口通信库实现,或者通过自己编译构建动态链接库来实现。根据情况选用适当的方案即可有效解决该问题,提升系统运行效率和安全性。