鸿蒙PC迁移:AutoHotkey v2 脚本引擎鸿蒙PC适配全记录
欢迎加入鸿蒙PC开发者社区,共同打造开发者工具生态:鸿蒙PC开发者社区:https://harmonypc.csdn.net/
项目开源地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_AutoHotkey
欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper
这篇文章记录的是一次把 AutoHotkey 适配到 HarmonyOS PC / 鸿蒙 PC 的完整过程。它和我之前写的那些适配(krita、LANDrop、Tesseract、kiwi…)都不太一样——因为 AutoHotkey 是一个无法被「完整」移植到鸿蒙的软件,而这恰恰是它最值得记录的地方:当一个桌面软件的核心功能在目标系统上从架构层面就不被允许时,适配应该怎么做?
最终的结论和做法是:不假装能搬运它的自动化能力,而是把它身上唯一与操作系统无关、又有独立价值的部分——AutoHotkey v2 的脚本语言本身——用 OHOS NDK 交叉编译成原生 .so,经 N-API 暴露给 ArkTS,做成一个能在鸿蒙 PC 上写脚本、跑脚本的「AutoHotkey 脚本引擎」。 这条路线和 Tesseract、kiwi 那两篇的 NDK+NAPI 模式一脉相承。

一、AutoHotkey 是什么,为什么它和别的项目不一样
AutoHotkey 是 Windows 上非常有名的免费开源自动化 / 宏工具。它有一套自带的脚本语言,最招牌的能力是:
- 定义全局热键 / 热字串(按个组合键触发动作、打几个字母自动展开);
- 模拟键盘鼠标,向其它应用发送按键、点击;
- 操控其它应用的窗口(激活、移动、查找);
- COM 自动化、
DllCall调任意 DLL、读写注册表。
问题就出在这里。我之前适配的 krita、LANDrop、KeePassXC 之所以能搬到鸿蒙,是因为它们的功能本身是「操作系统中立」的——画图、传文件、存密码,换个 GUI 承载层就能跑。
而 AutoHotkey 的功能本身,就是上面那些只有桌面系统、且通常需要特权才能做的事。更关键的是:
全局键鼠钩子、向别的应用注入按键、操控别的应用的窗口——这些行为是鸿蒙(以及任何现代沙箱化操作系统)从安全架构上就禁止第三方应用去做的。这不是「移植难度大」,而是「操作系统设计上不允许」。
我先用源码统计确认了这件事,证据非常硬:
源码 34 个 .cpp 文件,34 个全部 #include <windows.h> (100% 与 Win32 耦合)
入口是 Windows 专属的 _tWinMain
SendInput ×112 SendMessage ×338 SetForegroundWindow ×36
SetWindowsHookEx(全局钩子) RegisterHotKey(全局热键) keybd_event(模拟按键)
连「纯数学」的 source/qmath.h 都是 MSVC x86 内联汇编(__asm fld / fsqrt / fsin),
clang / aarch64 根本无法编译
所以一个忠实的 AutoHotkey 移植在鸿蒙上不可能成立。承认这一点,是这次适配的起点,也是它和别的项目最大的不同。
二、路线选择:只移植「语言核心」,不碰自动化
既然自动化、热键、窗口操控都搬不动,那 AutoHotkey 身上还剩什么是有价值、又能搬的?
答案是:它的脚本语言本身。 AutoHotkey v2 内部其实是一个完整的脚本语言解释器——有词法分析、语法分析、表达式求值、对象系统。这部分逻辑(算术、字符串、变量、控制流、内置函数)和「操作系统」没有关系,只要能脱离 windows.h 编译,就能在鸿蒙上跑。
于是路线就清晰了,和 Tesseract / kiwi 那两篇「C++ → NDK 交叉编译 → N-API → ArkTS」完全是同一条:
鸿蒙 ArkTS 页面(脚本编辑器)
-> AhkRunner 把脚本字符串交给 native
-> libahk_engine.so (N-API)
-> AutoHotkey v2 语言核心:词法 -> 语法 -> 求值
-> 返回 JSON { ok, output, result, errorType, ... }
-> ArkTS 解析结果、用弹窗 / 输出区展示
和别的路线对比一下,差异很清楚:
| Python 库(如 jieba) | Tesseract / kiwi(C++) | AutoHotkey | |
|---|---|---|---|
| 库本体 | 纯 Python | C++ | C++ 应用(深度耦合 Win32) |
| 能否完整移植 | 能 | 能 | 不能(核心功能被沙箱禁止) |
| 移植对象 | 整个库 | 整个引擎 | 只移植语言核心 |
| 鸿蒙后端 | HNP Python | 编进 .so 的 C++ |
编进 .so 的 C++ |
| 运行时依赖 | 依赖设备 Python | 无,自包含 | 无,自包含 |
这条路线诚实又有价值:用户能在鸿蒙 PC 上真的写 AutoHotkey 脚本、做计算、处理字符串,而不是给一个跑不起来的空壳。
三、圈定可移植边界:哪些能搬,哪些搬不动
适配前先把源码过了一遍,明确「能搬 / 搬不动」的边界,这一步省掉了后面大量返工。
搬不动(与 Win32 死耦合,全部排除): 热键、热字串、全局键鼠钩子、SendInput、窗口自动化、COM、DllCall、注册表、GUI 构建器。这些就是 AutoHotkey 不可移植的那一半。
能搬(语言核心,本次移植对象):
- 三种基本类型 Integer / Float / String,以及 AutoHotkey 的数值⇄字符串自动转换、真值规则(
""、0、"0"为假); - 完整运算符集,优先级逐条对标源码
source/defines.h的SYM_*枚举——这是 AutoHotkey 区别于 C 的关键(位运算优先级高于移位、.拼接夹在比较与位运算之间、**右结合且紧于一元负号,-2**2 == -4); - 控制流
if / else、while、Loop N(带A_Index)、return、{}代码块; - 内置函数的 OS 无关子集:
Abs Round Floor Ceil Sqrt Sin Cos Tan Exp Ln Log Mod Min Max、Integer Float Number Chr Ord Type、StrLen SubStr InStr StrUpper StrLower StrReplace Trim Format、MsgBox / OutputDebug。
这里也要诚实交代:上游的词法 / 语法 / 求值器与 script.cpp 等 3 万行代码、和 windows.h 全耦合,无法直接抽取;qmath.h 是 x86 内联汇编不可复用。所以语言核心是按 AutoHotkey v2 的行为重新实现的一份可移植 C++,运算符语义严格对标源码枚举。这一点在工程的 README 里写得很清楚,不夸大成「搬运了上游解释器」。
四、先在开发机上把语言引擎测对
C++ 适配有个很实用的习惯:先把最核心、最容易出错的那层在开发机上验证通过,再去搭 UI。对这次来说,最核心的就是语言引擎本身——词法、语法、求值。
所以我把引擎 ahk_engine.cpp 写成和 N-API 完全解耦:它不引用任何 OpenHarmony / NAPI 头文件,错误一律通过返回值 RunResult 上报、绝不抛到边界外。这样它在开发机上用普通 clang++ 就能编译运行,不需要鸿蒙 SDK 就能先把语言逻辑测对。
配套写了一个 host_test.cpp,覆盖运算符优先级、字符串 / 数学函数、变量与 if / while / Loop 控制流、三元、正则、真值规则,以及除零 / 类型错误 / 未定义变量 / 语法错误这些错误用例。开发机上一条命令编译并运行:
cd ohos_AutoHotkey/harmony_pc/host_test
c++ -std=c++17 -Wall -Wextra -I../entry/src/main/cpp \
host_test.cpp ../entry/src/main/cpp/ahk_engine.cpp -o host_test && ./host_test
全部 52 个用例通过、-Wall -Wextra 零警告——这就是「语言核心确实脱离了 Win32、可移植」的实证。如果这一层就错了,后面 ArkTS 和 Native 写得再漂亮也没用。

五、鸿蒙 PC 工程结构
适配完成后,仓库里新增了 ohos_AutoHotkey/harmony_pc/ 工程,结构和 kiwi / Tesseract 那两篇类似,cpp/ 是重点:
ohos_AutoHotkey/
├── README.md 适配说明(范围 / 证据 / 真实复用 vs 重实现 / 路线图)
└── harmony_pc/ DevEco 工程根
├── build-profile.json5 / oh-package.json5 / hvigorfile.ts
├── AppScope/ 应用级配置 + 图标
└── entry/src/main/
├── module.json5
├── cpp/
│ ├── CMakeLists.txt 编 ahk_engine.cpp + napi_init.cpp -> libahk_engine.so
│ ├── ahk_engine.h / .cpp ★ AutoHotkey v2 语言核心(纯 C++,零 Win32)
│ ├── napi_init.cpp N-API 桥(脚本字符串进 / JSON 出)
│ └── types/libahk_engine/ .so 的 TS 声明
├── ets/
│ ├── native/AhkEngineNative.ets .so 的 ArkTS 封装
│ ├── ahk/AhkRunner.ets 调用 native、解析 JSON
│ └── pages/Index.ets 脚本编辑器 + 示例 + 运行 + 输出
└── resources/
边界很清晰:语言核心集中在 cpp/ahk_engine.*,N-API 只做字符串和 JSON 的进出,ArkTS 分三层(页面 → 业务 Client → native 封装),和 kiwi 的分层完全一致。

六、Native / N-API:脚本进,JSON 结果出
ArkTS 不能直接调 C++ 引擎,中间靠 N-API 桥接。napi_init.cpp 只暴露四个方法,且只做字符串 / JSON 的搬运:
runScript(source: string): Promise<string> // 脚本字符串进,JSON 结果出(异步,工作线程)
runScriptSync(source: string): string // 同步版,结果 JSON 一致
engineVersion(): string // 移植版本
basedOnVersion(): string // 对标的上游 AutoHotkey 版本
真正的执行在引擎里完成,桥只把 RunResult 拼成一段 JSON 返回,页面解析后展示:
{
"ok": true,
"output": "1..100 的和 = 5050\n",
"result": "1..100 的和 = 5050",
"errorType": "",
"errorMessage": "",
"errorLine": 0
}
CMake 这边也只做一件事:把引擎和桥两个 .cpp 一起编成 libahk_engine.so,链接鸿蒙的 N-API 运行时。因为语言核心是自包含的纯 C++,不需要 Tesseract 那种预编译依赖链脚本,一次就能编出来,是最易构建的一档:
add_library(ahk_engine SHARED napi_init.cpp ahk_engine.cpp)
target_compile_options(ahk_engine PRIVATE -fexceptions -O2 -fvisibility=hidden)
target_link_libraries(ahk_engine PUBLIC libace_napi.z.so)
七、ArkTS 页面:脚本编辑器 + 示例 + 运行 + 弹窗
页面 Index.ets 做成了一个能直接上手的脚本运行器:顶部是环境检查(显示移植版本、对标的上游版本),中间是六个一键载入的示例脚本和一个脚本编辑器,下面是运行按钮和输出区。
这里有一个很关键的体验细节,也是适配里专门修过的一个坑。桌面版 AutoHotkey 的 MsgBox "xxx" 会弹一个对话框;而最初我把 MsgBox 的内容写进了页面底部的「输出区」。结果用户按 AutoHotkey 的习惯点「运行」、等弹窗,没看到弹窗就以为「点了没反应」——其实结果早就显示在下方输出区了。
所以最终版改成:点运行后立刻冒一个 运行中… 的 Toast 给即时反馈,MsgBox 的结果用真正的弹窗对话框显示(贴近 AutoHotkey 体验、不会被忽略),同时改用同步执行排除异步在真机上不回调的可能:
private runScript(): void {
promptAction.showToast({ message: '运行中…', duration: 800 }); // 即时反馈
const r = this.runner.runSync(this.script);
if (r.ok) {
const popup = r.output.length > 0 ? r.output : ('结果: ' + r.result);
this.showDialog('运行结果', popup); // MsgBox -> 弹窗,不会被忽略
} else {
this.showDialog('运行出错', r.errorType + '\n' + r.errorMessage);
}
}
页面底部还专门放了一块「关于适配范围」的说明,老老实实告诉用户:本应用移植的是 AutoHotkey 的语言核心,热键 / 键鼠注入 / 窗口自动化 / COM / DllCall 依赖系统级权限、在鸿蒙沙箱中无法实现,不在范围内。不藏着掖着。
八、构建:把 .so 和 ArkTS 一起编出来
工程在 DevEco Studio 里可以直接构建。entry/build-profile.json5 的 externalNativeOptions 会驱动 DevEco 调 cpp/CMakeLists.txt,用 OHOS NDK 的 clang 把引擎交叉编译成 libahk_engine.so(arm64-v8a 真机 / x86_64 模拟器),自动打进 HAP 的 libs/。
和 kiwi 那篇一样,这里也要注意 compatibleSdkVersion 要和本机 SDK 匹配(本机是 API 21 就写 6.0.1(21),否则会报 Unable to find the compatibleSdkVersion )。构建日志里能看到 Native 和 ArkTS 两条线都编了,最后签名打包:
Finished :entry:default@BuildNativeWithCmake
Finished :entry:default@BuildNativeWithNinja
Finished :entry:default@CompileArkTS
Finished :entry:default@PackageHap
Finished :entry:default@SignHap
BUILD SUCCESSFUL
构建产物里能看到 libahk_engine.so 同时编出了 arm64-v8a 和 x86_64 两个架构,已打进 HAP。

九、装到真机并实测
构建出签名 HAP 后,用 hdc 装到鸿蒙 PC 上并启动:
HDC=/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc
$HDC install -r entry/build/default/outputs/default/entry-default-signed.hap
$HDC shell aa start -a EntryAbility -b com.autohotkey.ohos.engine
装好后在设备上实测,编辑器里就是默认脚本——用 Loop 把 1 到 100 累加:
total := 0
Loop 100
{
total += A_Index
}
MsgBox "1..100 的和 = " . total
点「运行脚本」,立刻冒出 运行中… 提示,紧接着弹出对话框:1..100 的和 = 5050。这说明从脚本字符串 → N-API → C++ 引擎求值 → JSON 回传 → ArkTS 弹窗的整条链路在真机上跑通了。

再点几个示例验证不是写死的结果:
- 字符串处理:
MsgBox Format("你好, {1}! 长度={2}", name, StrLen(name))—— 弹出你好, 鸿蒙 PC! 长度=5; - 数学函数:
Round(Sqrt(2), 6)、Max / Min / Mod—— 数值正确; - if / else 判断、三元 + 正则匹配 —— 逻辑正确。
每个示例都是引擎真在求值,换输入结果就跟着变。

十、踩坑与总结
这次适配踩到、并值得记下来的几个点:
- 预览器不加载原生
.so。 DevEco 的 Previewer 不会加载 Native 模块,import 'libahk_engine.so'在预览器里必然失败——原生模块的功能必须用模拟器或真机验证。 - 「点了没反应」往往是反馈不可见,不是真没跑。 桌面习惯期待
MsgBox弹窗,而结果在输出区。改成 Toast + 弹窗对话框后体验立刻对了。 compatibleSdkVersion要和本机 SDK 对齐,否则命令行 / DevEco 构建直接报错。- 诚实划定范围比硬撑更重要。 AutoHotkey 注定无法完整移植,与其做个跑不起来的空壳,不如把能搬的语言核心做扎实、并明确告诉用户哪些做不了。
完成后,这个应用是一个自包含、安装即用的鸿蒙 PC「AutoHotkey 脚本引擎」:
- 不依赖设备上任何运行时,语言核心编进
.so,安装即用; - 运算符 / 优先级 / 类型语义严格对标 AutoHotkey v2 源码;
- 引擎与 N-API 解耦,开发机上普通 C++ 编译器就能先测对(52/52 通过);
- DevEco 构建通过,真机实测算术、字符串、数学、控制流、正则全部正常。
从这次适配能总结出一条和「整库移植」不一样的思路——当一个软件的核心功能在目标系统上从架构层面就不被允许时,不要假装能整体搬运,而是剥离出它「与操作系统无关」的那部分内核去移植:
先确认哪些功能是目标系统禁止的(沙箱权限边界)
-> 圈定「与 OS 无关、又有独立价值」的内核(这里是语言解释器)
-> 把内核与 N-API 解耦,在开发机上测对
-> NDK 交叉编译进 .so,N-API 只做字符串 / JSON 进出
-> ArkTS 做成能上手的页面,并诚实标注不可用的能力
-> DevEco / 真机验证闭环
后续如果要继续往「FULL 档」推进,可以接入 AutoHotkey 自带的 lib_pcre(纯 C、可移植)替换正则占位,拿到与上游完全一致的 RegEx 语法;再为上游解释器写一层 TCHAR→char 的可移植 shim,分阶段把对象系统、用户自定义函数搬过来。但就「让鸿蒙 PC 用户能真的写、跑 AutoHotkey 脚本」这个目标而言,当前这一档已经闭环。
更多推荐




所有评论(0)