Electron for 鸿蒙PC:用 Node-API 打通原生系统调用,告别"Web 孤岛"
前言
把 Electron 应用移植到鸿蒙 PC 平台,最大的拦路虎不是 UI,而是原生系统能力。
在 Windows 上,你可以通过 node-ffi、winapi 或 Shell 命令随意调用系统功能。但鸿蒙 PC 的系统 API 是 C/C++ 接口,Electron 的 Node.js 运行时无法直接触达。解法只有一个:Node-API(原 N-API) ------用 C++ 编写原生插件,再通过 require() 暴露给 Electron 主进程。
本文将从零开始,带你完成以下实战目标:
- 搭建 Electron for HarmonyOS 的 Node-API 开发环境
- 编写一个获取系统硬件信息的原生插件
- 实现文件系统监控(
inotify风格 Watch) - 主进程与渲染进程通过 IPC 消费原生数据
- 打包并在鸿蒙 PC 上运行
一、技术架构总览
┌─────────────────────────────────────────────────┐
│ Electron 渲染进程 (Chromium) │
│ Vue/React SPA ──IPC(contextBridge)──▶ 主进程 │
└──────────────────────────┬──────────────────────┘
│ require('./addon.node')
┌──────────────────────────▼──────────────────────┐
│ Node.js 主进程 (Node-API 层) │
│ addon.node ──C++ FFI──▶ 鸿蒙系统 C API │
└─────────────────────────────────────────────────┘
核心链路:渲染进程 → IPC → Node.js 主进程 → Node-API 插件(.node)→ 鸿蒙原生 C API
二、环境搭建
2.1 前置要求
| 工具 | 版本 | 用途 |
|---|---|---|
| DevEco Studio | 5.0.5+ | 鸿蒙 SDK 和模拟器 |
| Electron for HarmonyOS | 34.x | 定制版 Electron |
| node-gyp | 10.x | 编译 .node 插件 |
| cmake-js | 7.x | CMake 构建插件(可选) |
| Python | 3.10+ | node-gyp 依赖 |
2.2 获取 Electron for HarmonyOS
bash
# 克隆官方适配仓库
git clone https://gitee.com/openharmony-tpc/electron.git
cd electron
git checkout ohos-main # 切到鸿蒙分支
# 安装专用 npm 包(内含鸿蒙 SDK 路径)
npm install @ohos/electron-napi-headers --save-dev
2.3 配置 node-gyp 指向鸿蒙 SDK
bash
# 设置环境变量(以 DevEco Studio 默认路径为例)
export OHOS_SDK_HOME=$HOME/DevEco-Studio/sdk/HarmonyOS-NEXT-DB6
export GYP_DEFINES="ohos_sdk=$OHOS_SDK_HOME"
# 验证
node-gyp list
三、编写第一个 Node-API 插件:获取系统硬件信息
3.1 目录结构
electron-hmos-demo/
├── native/
│ ├── binding.gyp # node-gyp 构建配置
│ ├── sysinfo.cpp # 原生插件主体
│ └── include/
│ └── ohos_sysinfo.h # 鸿蒙系统信息头文件
├── main.js # Electron 主进程
├── preload.js # 预加载脚本
├── renderer/
│ └── index.html # 渲染页面
└── package.json
3.2 编写原生插件 sysinfo.cpp
cpp
// native/sysinfo.cpp
#include <napi.h>
#include <string>
// 鸿蒙 PC 系统信息 API(ohos.systemCapability.deviceInfo)
#ifdef __OHOS__
#include "device_info.h" // HarmonyOS SDK 头文件
#else
// Windows/Mac 开发环境 mock
#include <sys/utsname.h>
#endif
// 获取设备型号
Napi::String GetDeviceModel(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
#ifdef __OHOS__
// 鸿蒙原生 API:OH_DeviceInfo_GetDeviceModel
char model[256] = {0};
OH_DeviceInfo_GetDeviceModel(model, sizeof(model));
return Napi::String::New(env, std::string(model));
#else
// 非鸿蒙环境返回 mock 数据
return Napi::String::New(env, "HarmonyOS-PC-Mock");
#endif
}
// 获取系统版本
Napi::String GetOsVersion(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
#ifdef __OHOS__
char version[128] = {0};
OH_DeviceInfo_GetSystemVersion(version, sizeof(version));
return Napi::String::New(env, std::string(version));
#else
struct utsname buf;
uname(&buf);
return Napi::String::New(env, std::string(buf.release));
#endif
}
// 获取可用内存(MB)
Napi::Number GetFreeMemory(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
#ifdef __OHOS__
// 鸿蒙 OH_Memory API
OH_MemoryInfo_t memInfo;
OH_Memory_GetInfo(&memInfo);
double freeMB = static_cast<double>(memInfo.freeBytes) / 1024.0 / 1024.0;
return Napi::Number::New(env, freeMB);
#else
// 开发环境用 Node.js 内置 API mock
return Napi::Number::New(env, 4096.0);
#endif
}
// 异步获取 CPU 使用率(演示异步 Node-API 用法)
class CpuUsageWorker : public Napi::AsyncWorker {
public:
CpuUsageWorker(Napi::Function& callback)
: Napi::AsyncWorker(callback), cpuUsage_(0.0) {}
void Execute() override {
#ifdef __OHOS__
OH_CpuInfo_t cpuInfo;
OH_Cpu_GetUsage(&cpuInfo);
cpuUsage_ = cpuInfo.totalUsage * 100.0;
#else
cpuUsage_ = 23.5; // mock
#endif
}
void OnOK() override {
Napi::HandleScope scope(Env());
Callback().Call({Env().Null(), Napi::Number::New(Env(), cpuUsage_)});
}
private:
double cpuUsage_;
};
Napi::Value GetCpuUsageAsync(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (info.Length() < 1 || !info[0].IsFunction()) {
Napi::TypeError::New(env, "Callback function required").ThrowAsJavaScriptException();
return env.Null();
}
Napi::Function callback = info[0].As<Napi::Function>();
auto* worker = new CpuUsageWorker(callback);
worker->Queue();
return env.Undefined();
}
// 模块初始化 & 导出
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set("getDeviceModel", Napi::Function::New(env, GetDeviceModel));
exports.Set("getOsVersion", Napi::Function::New(env, GetOsVersion));
exports.Set("getFreeMemory", Napi::Function::New(env, GetFreeMemory));
exports.Set("getCpuUsageAsync", Napi::Function::New(env, GetCpuUsageAsync));
return exports;
}
NODE_API_MODULE(sysinfo, Init)
3.3 编写 binding.gyp
json
{
"targets": [{
"target_name": "sysinfo",
"sources": ["sysinfo.cpp"],
"include_dirs": [
"<!@(node -p \"require('node-addon-api').include\")",
"<(OHOS_SDK_HOME)/native/sysapi/include"
],
"libraries": [
"-L<(OHOS_SDK_HOME)/native/sysapi/lib",
"-ldevice_info",
"-lmemory_info",
"-lcpu_info"
],
"defines": ["NAPI_DISABLE_CPP_EXCEPTIONS"],
"cflags_cc": ["-std=c++17"],
"conditions": [
["OS=='ohos'", {
"defines": ["__OHOS__=1"]
}]
]
}]
}
3.4 编译插件
bash
cd native
# 首次编译
node-gyp configure --arch=x64 --target_platform=ohos
node-gyp build
# 输出产物:native/build/Release/sysinfo.node
四、在 Electron 主进程中调用插件
4.1 主进程 main.js
javascript
// main.js
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
// 加载原生插件(自动适配 Debug/Release)
let sysinfo;
try {
sysinfo = require('./native/build/Release/sysinfo.node');
console.log('[Node-API] sysinfo plugin loaded successfully');
} catch (err) {
console.error('[Node-API] Failed to load sysinfo plugin:', err.message);
// Fallback: 使用 JS 层 mock(开发阶段)
sysinfo = {
getDeviceModel: () => 'HarmonyPC-DEV',
getOsVersion: () => '5.0.0-OHOS',
getFreeMemory: () => 8192,
getCpuUsageAsync: (cb) => setTimeout(() => cb(null, 15.8), 100)
};
}
function createWindow() {
const win = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true, // 必须开启!
nodeIntegration: false, // 必须关闭!
}
});
win.loadFile('renderer/index.html');
}
app.whenReady().then(createWindow);
// ===== IPC 处理:同步系统信息查询 =====
ipcMain.handle('get-sys-info', async () => {
return {
deviceModel: sysinfo.getDeviceModel(),
osVersion: sysinfo.getOsVersion(),
freeMemoryMB: sysinfo.getFreeMemory(),
};
});
// ===== IPC 处理:异步 CPU 使用率 =====
ipcMain.handle('get-cpu-usage', () => {
return new Promise((resolve, reject) => {
sysinfo.getCpuUsageAsync((err, usage) => {
if (err) reject(err);
else resolve(usage);
});
});
});
4.2 预加载脚本 preload.js
javascript
// preload.js - 安全地将 IPC 能力暴露给渲染进程
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
// 获取系统信息(一次性)
getSysInfo: () => ipcRenderer.invoke('get-sys-info'),
// 获取 CPU 使用率
getCpuUsage: () => ipcRenderer.invoke('get-cpu-usage'),
// 订阅实时 CPU 更新(推模式)
onCpuUpdate: (callback) => {
ipcRenderer.on('cpu-update', (_event, value) => callback(value));
// 返回取消订阅函数
return () => ipcRenderer.removeAllListeners('cpu-update');
}
});
五、渲染进程展示数据
html
<!-- renderer/index.html -->
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>鸿蒙 PC 系统监控</title>
<style>
body { font-family: 'HarmonyOS Sans', sans-serif; background: #0d1117; color: #e6edf3; padding: 2rem; }
.card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 1.5rem; margin-bottom: 1rem; }
.metric { font-size: 2rem; font-weight: bold; color: #58a6ff; }
.label { font-size: 0.85rem; color: #8b949e; margin-top: 0.25rem; }
#cpu-bar { height: 8px; background: #21262d; border-radius: 4px; margin-top: 0.5rem; }
#cpu-fill { height: 100%; background: linear-gradient(90deg, #238636, #3fb950); border-radius: 4px; transition: width 0.5s; }
</style>
</head>
<body>
<h1>🖥️ HarmonyOS PC 系统监控</h1>
<div class="card">
<div class="label">设备型号</div>
<div class="metric" id="device-model">加载中...</div>
</div>
<div class="card">
<div class="label">系统版本</div>
<div class="metric" id="os-version">加载中...</div>
</div>
<div class="card">
<div class="label">可用内存</div>
<div class="metric" id="free-mem">加载中...</div>
</div>
<div class="card">
<div class="label">CPU 使用率</div>
<div class="metric" id="cpu-usage">0%</div>
<div id="cpu-bar"><div id="cpu-fill" style="width:0%"></div></div>
</div>
<script>
(async () => {
// 一次性获取系统信息
const info = await window.electronAPI.getSysInfo();
document.getElementById('device-model').textContent = info.deviceModel;
document.getElementById('os-version').textContent = info.osVersion;
document.getElementById('free-mem').textContent =
`${info.freeMemoryMB.toFixed(0)} MB`;
// 定时刷新 CPU 使用率
const refreshCpu = async () => {
const usage = await window.electronAPI.getCpuUsage();
document.getElementById('cpu-usage').textContent = `${usage.toFixed(1)}%`;
document.getElementById('cpu-fill').style.width = `${usage}%`;
};
await refreshCpu();
setInterval(refreshCpu, 2000); // 每 2 秒刷新
})();
</script>
</body>
</html>
六、打包部署到鸿蒙 PC
6.1 package.json 关键配置
json
{
"name": "electron-hmos-sysinfo",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"build:native": "cd native && node-gyp rebuild --target_platform=ohos",
"pack:hmos": "electron-builder --platform=ohos --arch=x64",
"dev": "electron ."
},
"build": {
"appId": "com.example.electron.sysinfo",
"productName": "SysMonitor",
"ohos": {
"target": "default",
"hap": {
"type": "entry",
"bundleName": "com.example.electron.sysinfo"
}
},
"files": [
"main.js",
"preload.js",
"renderer/**",
"native/build/Release/*.node"
]
},
"dependencies": {
"node-addon-api": "^8.1.0"
},
"devDependencies": {
"electron": "34.0.0-ohos",
"electron-builder": "^25.0.0"
}
}
6.2 打包命令
bash
# 1. 编译原生插件(针对鸿蒙架构)
npm run build:native
# 2. 打包为鸿蒙 HAP 包
npm run pack:hmos
# 3. 通过 DevEco Studio 安装到鸿蒙 PC
hdc shell install dist/com.example.electron.sysinfo-1.0.0.hap
七、踩坑指南
坑1:.node 文件 RPATH 问题
鸿蒙 PC 的动态库搜索路径与 Linux 不同,需在 binding.gyp 中显式设置:
json
"ldflags": ["-Wl,-rpath,@loader_path/../lib"]
坑2:异步 Worker 线程崩溃
Node-API 的 AsyncWorker::Execute() 运行在 libuv 线程池中,不能在此直接调用任何 Napi 对象 。如需传递数据,必须用成员变量暂存,在 OnOK() 中再转换为 JS 值。
坑3:contextIsolation 导致 require 不可用
渲染进程绝对不能 用 require('electron'),必须通过 preload.js + contextBridge 传递能力。违反此规则会导致安全漏洞,且在新版 Electron 中直接报错。
坑4:鸿蒙 SDK 版本不匹配
OH_DeviceInfo_GetDeviceModel 在 HarmonyOS NEXT DB6 之前的 API Level 不存在,确保 sdk-version 对齐:
json
// module.json5
"metadata": [
{ "name": "ohos.SDK.version", "value": "12" }
]
八、总结与扩展方向
通过 Node-API,Electron 应用获得了直接调用鸿蒙 PC 原生 C API 的能力,从根本上解决了"Web 孤岛"问题。
| 原生能力 | Node-API 可达性 | 典型场景 |
|---|---|---|
| 硬件信息 | ✅ 完全支持 | 设备授权、运维监控 |
| 文件系统 | ✅ 完全支持 | 文件管理器、IDE |
| 系统通知 | ✅ 完全支持 | 消息提醒应用 |
| 蓝牙/NFC | ⚠️ 部分支持 | 需额外权限申请 |
| 相机/麦克风 | ⚠️ 受沙箱限制 | HAP 需声明权限 |
| 分布式能力 | 🔄 开发中 | 多设备协同 |
下一步推荐阅读:
- 官方仓库:
https://gitcode.com/openharmony-sig/electron - Node-API 文档:
nodejs.org/api/n-api.html - 应用如何接入Electron框架:
https://developer.huawei.com/consumer/cn/blog/topic/03208728403662067
💡 如果本文对你有帮助,欢迎点赞收藏!如有疑问欢迎评论区留言。