H 版里经常看到几个名字:AutoHotkey.dllahkdllahktextdllahkExec。新手容易把它们混在一起。其实先抓住一句话就够了:AutoHotkey.dll 是 DLL 形态的 AHK 解释器,后面这些名字大多是围绕它提供的导出函数和调用方式。

如果你只是在 H 版脚本内部创建线程,优先看 AhkThread()。如果你想让外部语言调用 AHK,或者自己用 DllCall 控制 AHK 解释器,就要理解 AutoHotkey.dll 这一层。

先加载 DLL

最小理解是:先把 DLL 加载进当前进程,再调用它导出的函数。

#Requires AutoHotkey v1.1
#NoEnv
SetBatchLines, -1

dllPath := A_ScriptDir "\AutoHotkey.dll"
hModule := DllCall("LoadLibrary", "Str", dllPath, "Ptr")

if (!hModule)
{
    MsgBox, AutoHotkey.dll 加载失败
    ExitApp
}

MsgBox, AutoHotkey.dll 已加载

这段代码只是证明 DLL 能加载。真正使用时,还要调用 DLL 里的导出函数,比如从文件启动脚本、从字符串启动脚本、执行代码、调用函数等。

ahkdll:从文件启动

ahkdll 适合脚本已经保存成文件的情况。你可以把工作脚本放在单独文件里,然后通过 DLL 启动它。

dllPath := A_ScriptDir "\AutoHotkey.dll"
scriptFile := A_ScriptDir "\Worker.ahk"

FileDelete, %scriptFile%
FileAppend, #Persistent`nMsgBox`, 来自 Worker.ahk, %scriptFile%, UTF-8

hThread := DllCall(dllPath "\ahkdll"
    , "Str", scriptFile
    , "Str", ""
    , "Str", "FileThread"
    , "CDecl Ptr")

文件方式的好处是代码可维护。外部语言调用时也一样,能把 AHK 逻辑放成独立脚本,就不要把大段脚本拼成字符串。

ahktextdll:从字符串启动

ahktextdll 适合临时脚本、动态生成脚本、测试片段。它不要求先写入文件。

dllPath := A_ScriptDir "\AutoHotkey.dll"

scriptText =
(
#Persistent
MsgBox, 来自字符串脚本
)

hThread := DllCall(dllPath "\ahktextdll"
    , "Str", scriptText
    , "Str", ""
    , "Str", "TextThread"
    , "CDecl Ptr")

字符串方式方便,但一长就难维护。尤其是外部语言拼接 AHK 脚本时,引号、换行、编码都容易出问题。

ahkExec:追加执行代码

ahkExec 更像是向已经存在的 AHK 环境里执行一段代码。它适合小段命令,不适合承载完整项目结构。

DllCall(dllPath "\ahkExec"
    , "Str", "ToolTip, ahkExec 执行了一段代码"
    , "CDecl Int")

如果你需要长期运行的逻辑,优先用文件脚本或明确的函数调用。不要把所有事情都塞给 ahkExec

外部语言为什么要用 DLL

外部语言调用 AHK 有两种常见思路:一种是每次启动一个 exe,让脚本从命令行参数接收任务;另一种是加载 AutoHotkey.dll,让 AHK 解释器常驻在当前进程里。

前者简单,但每次启动进程都有成本,参数也多半只能按字符串处理。后者复杂一些,但可以长期保持解释器环境,更适合频繁调用、回调、复杂状态共享。

比如 Python、C#、易语言、VBS 这类语言,都可以围绕 DLL 做封装。真正要注意的是这些细节:

  • 调用约定是否匹配,常见是 CDecl
  • 32 位程序要配 32 位 DLL,64 位程序要配 64 位 DLL。
  • 字符串编码要统一,中文路径和中文脚本尤其要小心。
  • 长期运行的线程要设计退出方式。
  • 调用函数时要明确参数和返回值类型。

MemoryLoadLibrary 是另一种加载方式

普通 LoadLibrary 是从文件路径加载 DLL。H 版还提供过 MemoryLoadLibrary() 这类能力,可以从内存模块加载 DLL,并配合 MemoryGetProcAddress() 获取导出函数地址。

它适合更高级的发布和嵌入场景,比如资源内置、内存模块、多份独立模块状态。新手不用急着碰,先把普通 DLL 加载和导出函数调用理解清楚。

下面是一个最小结构:用 MemoryLoadLibrary() 加载 AutoHotkey.dll,再用 MemoryGetProcAddress() 找到 ahktextdllahkReady,最后通过函数指针调用。

dllPath := A_ScriptDir "\AutoHotkey.dll"

mod := MemoryLoadLibrary(dllPath)
if (!mod)
{
    MsgBox, MemoryLoadLibrary 加载失败
    ExitApp
}

pAhkTextDll := MemoryGetProcAddress(mod, "ahktextdll")
pAhkReady := MemoryGetProcAddress(mod, "ahkReady")

if (!pAhkTextDll || !pAhkReady)
{
    MemoryFreeLibrary(mod)
    MsgBox, 找不到需要的导出函数
    ExitApp
}

code := "#Persistent`nMsgBox, 来自 MemoryLoadLibrary 加载的 AutoHotkey.dll"
DllCall(pAhkTextDll, "Str", code, "Str", "", "Str", "MemThread", "CDecl Ptr")

While DllCall(pAhkReady)
    Sleep, 100

MemoryFreeLibrary(mod)

这个例子的重点不是推荐所有场景都改成内存加载,而是看清楚调用链路:先拿模块句柄,再取导出函数地址,用 DllCall 调用,最后释放模块。普通 DLL 调用都跑通以后,再考虑 Memory 系列会稳得多。

我建议的分工

  • H 版脚本内部多线程:优先用 AhkThread()
  • 脚本文件已经存在:考虑 ahkdll
  • 临时代码片段:考虑 ahktextdllahkExec
  • 外部语言频繁调用 AHK:围绕 AutoHotkey.dll 封装。
  • 正式发布和嵌入:再研究内存加载、资源释放、打包策略。

AutoHotkey.dll 最有价值的地方,不是让 AHK 代码写得更花,而是让 AHK 能进入别的程序结构里。理解这一点,H 版的 DLL 方向就不会乱。

站内相关链接

声明:站内资源为整理优化好的代码上传分享与学习研究,如果是开源代码基本都会标明出处,方便大家扩展学习路径。请不要恶意搬运,破坏站长辛苦整理维护的劳动成果。本站为爱好者分享站点,所有内容不作为商业行为。如若本站上传内容侵犯了原著者的合法权益,请联系我们进行删除下架。