人人都会AI编程

26.4 常见原生模块:串口、硬件调用、系统底层能力

更新时间:2026-07-11

Electron 的 Node.js 运行时可以直接加载经过编译的 C/C++ 原生模块,这使得桌面应用能够实现 Web 端无法企及的硬件交互能力——读写串口、枚举 USB 设备、获取系统级传感器数据,甚至直接调用操作系统原生的动态链接库。本节介绍几个在生产环境中经过验证的常用原生模块及其集成要点。

26.4.1 串口通信:serialport

serialport 是 Node.js 生态中操作串口(RS-232、USB 转串口等)的事实标准库。在 Electron 中集成它能让你的应用直接与 Arduino、PLC、传感器、嵌入式设备等进行串口通信。

安装与配置

npm install serialport

由于包含原生 C++ 代码,安装过程会自动调用 node-gyp 进行编译。确保你的开发环境已安装相应的构建工具(Windows 需要 Visual Studio Build Tools,macOS 需 Xcode Command Line Tools,Linux 需 build-essential)。如果使用 electron-builder 打包,通常需要在 electron-builder 配置中指定 nodeIntegration 或通过 native-ext-loader 处理原生模块。

基本使用示例(主进程)

// main.js
const { SerialPort } = require('serialport');
const { ReadlineParser } = require('@serialport/parser-readline');

async function openSerial() {
  const ports = await SerialPort.list();
  console.log('可用串口:', ports);

  const port = new SerialPort({
    path: '/dev/ttyUSB0',   // Linux/Mac,Windows 上为 'COM3'
    baudRate: 9600,
    autoOpen: false,
  });

  const parser = port.pipe(new ReadlineParser({ delimiter: '\n' }));
  parser.on('data', (line) => {
    console.log('收到数据:', line);
    // 可通过 IPC 发送到渲染进程显示
  });

  port.open((err) => {
    if (err) console.error('打开失败', err);
    else {
      console.log('串口已打开');
      port.write('Hello Device\n'); // 发送数据
    }
  });
}

// 使用 async/await 风格的错误处理同样便捷

关键注意事项

  • 权限问题:Linux 下通常需要将用户加入 dialout 组,macOS 可能需要授权蓝牙/串口相关权限。
  • 热插拔:使用 serialport 提供的 SerialPort.list() 轮询或监听 usb-detection 模块检测设备插拔,及时更新可用串口列表。
  • 安全性:永远不要在主进程直接暴露串口对象给渲染进程。应通过 IPC 封装操作接口(例如 serial:list, serial:open, serial:write),保持最小权限原则。

26.4.2 USB 与 HID 设备

除了串口,许多外设通过 USB 或 HID 协议直接通信,例如打印机、扫描枪、游戏手柄、硬件钱包、自定义 USB 设备等。

node-usb

usb 库提供了对 USB 设备的底层访问,可进行控制传输、批量传输、中断传输等。

npm install usb

基本模式:

const usb = require('usb');

// 监听设备插入/拔出
usb.on('attach', (device) => {
  console.log('设备插入:', device.deviceDescriptor.idVendor, device.deviceDescriptor.idProduct);
});

// 枚举已连接设备
const devices = usb.getDeviceList();
devices.forEach(device => {
  console.log(`设备: ${device.deviceDescriptor.idVendor.toString(16)}:${device.deviceDescriptor.idProduct.toString(16)}`);
});

真正的通信需要 device.open(), 声明接口,然后进行端点的数据传输。由于操作相对繁琐,很多场景下更推荐使用 serialport 的通用串口驱动,或使用设备厂商提供的 Node.js SDK。

node-hid

node-hid 是访问 HID 设备(键盘、鼠标、自定义 HID 设备)的轻量库,适合与无需串口驱动但通过 HID 报告通信的设备交互。

npm install node-hid

获取设备列表并读取数据:

const HID = require('node-hid');
const devices = HID.devices();
console.log(devices);

const device = new HID.HID(vendorId, productId);
device.on('data', (data) => {
  console.log('HID 数据:', data);
});
device.on('error', (err) => {
  console.error('HID 错误:', err);
});

跨平台注意

  • macOS 上某些 HID 设备可能已被系统独占(如键盘/鼠标),需要通过 kIOReturnExclusiveAccess 处理或禁用系统驱动(需谨慎)。
  • 打包时原生模块需要正确对齐 Electron 的 Node.js 版本,可使用 electron-rebuild 工具自动重编译:
  npx electron-rebuild -f -w your-proj
  

26.4.3 系统底层能力

除了直接硬件访问,许多桌面应用需要获取操作系统级别的信息、执行系统命令,或调用本地 DLL / Framework。这里列举几种常见需求及解决方案。

系统信息与监控 – systeminformation

systeminformation 是一个纯 JavaScript 库(部分使用系统命令),可无痛获取 CPU、内存、磁盘、网络、电池、温度、进程列表等信息,几乎不需要原生编译。

npm install systeminformation

用法示例(主进程):

const si = require('systeminformation');

si.cpu()
  .then(data => console.log(data))
  .catch(error => console.error(error));

// 获取实时 CPU 负载、内存使用率等
si.currentLoad().then(data => console.log(`CPU 负载: ${data.currentLoad}%`));
si.mem().then(data => console.log(`可用内存: ${(data.available / 1024 / 1024 / 1024).toFixed(2)} GB`));

非常适合制作系统仪表盘、资源监控类应用。

执行系统命令 – child_process

Node.js 内置的 child_process 模块在 Electron 主进程中可以直接使用,且权限完整。可以用来调用任何命令行工具(如 ping, ffmpeg, ssh 等)。

const { exec, spawn } = require('child_process');

exec('dir', { cwd: 'C:\\' }, (error, stdout, stderr) => {
  if (error) throw error;
  console.log(stdout);
});

// 流式处理长时间任务
const ffmpeg = spawn('ffmpeg', ['-i', 'input.mp4', 'output.avi']);
ffmpeg.stderr.on('data', (data) => {
  console.log(`stderr: ${data}`);
});
ffmpeg.on('close', (code) => {
  console.log(`进程退出,代码 ${code}`);
});

安全提醒:若需从渲染进程触发系统命令,务必通过 IPC 让主进程执行,并严格校验参数(避免命令注入)。

调用原生动态库 – ffi-napi / koffi

有时你需要调用 Windows DLL、macOS .dylib 或 Linux .so 中的函数,例如使用某些只提供 C API 的硬件 SDK。ffi-napikoffi 都是用于 Node.js 的 FFI (Foreign Function Interface) 库。

koffi 相对更轻量且不需要带编译的原生依赖,对 Electron 集成友好:

npm install koffi

调用 Windows user32.dll 的 MessageBox 示例:

const koffi = require('koffi');

const user32 = koffi.load('user32.dll');
const MessageBox = user32.func('int MessageBoxA(int hWnd, const char *lpText, const char *lpCaption, int uType)');

const MB_OK = 0;
MessageBox(0, 'Hello from Electron!', 'Native Dialog', MB_OK);

对于硬件 SDK,类似地加载厂商的 DLL,按照头文件定义函数签名,即可在 JavaScript 中直接操作设备。使用 FFI 时要格外小心类型对齐、内存管理和线程安全。

注册表操作(Windows)

在 Windows 上读写注册表可以使用 regeditwinreg 模块(纯 JS 实现,无需原生编译)。可用于开机自启、文件关联等。

const Registry = require('winreg');
const regKey = new Registry({
  hive: Registry.HKCU,
  key:  '\\Software\\MyApp'
});

regKey.set('Setting', Registry.REG_SZ, 'value', (err) => {
  if (!err) console.log('写入成功');
});

26.4.4 打包与分发考虑

原生模块的编译产物必须与 Electron 的 Node.js ABI 匹配。项目通常需要配置 electron-rebuildpostinstall 或构建前自动重编译:

// package.json 脚本示例
"scripts": {
  "postinstall": "electron-rebuild -f -w your-module-name",
  "build": "electron-builder"
}

electron-builder 配置中,还需要确保原生模块被正确包含到 asar 包中或解压到 app.asar.unpacked 目录(部分原生 .node 文件无法在 asar 内直接加载)。常见做法是设置:

# electron-builder.yml
asar: true
asarUnpack:
  - "node_modules/serialport/**"
  - "node_modules/usb/**"

此外,不同平台可能有签名要求(如 Windows 驱动签名、macOS 的 Hardened Runtime 与 entitlements),需要根据目标硬件 API 启用相应能力,例如在 macOS 的 entitlements.mac.plist 中添加:

<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.device.usb</key>
<true/>

26.4.5 总结

Electron 通过原生模块突破了 Web 技术的硬件边界,使其能够胜任工控上位机、硬件调试工具、系统监控面板等专业场景。使用时的核心原则是:

  • 优先寻找成熟的 Node.js 原生库,在其之上用 IPC 封装出安全 API。
  • 关注跨平台差异和权限申请,测试务必覆盖目标操作系统。
  • 借助 electron-rebuild 保持 ABI 兼容,合理配置打包策略。

掌握了这些常见原生模块的运用,你的 Electron 应用就不再仅仅是一个浏览器壳子,而成为真正融入操作系统的强大桌面软件。