来源:AutoHotkey Community - Communicating between scripts (IPC, inter-process communication)

原作者:Descolada。本文为中文移植和扩展整理版,完整覆盖原帖提到的脚本间通信方法;示例代码按本站习惯改写为 AHK v1 风格,并对代码注释做了中文化处理。原帖基于 AHK v2,若需要对照 v2 原始代码,请查看上方来源链接。

为什么 AHK 脚本之间需要通信

当一个电脑上同时运行多个 AHK 脚本时,很容易遇到资源冲突:两个脚本都想激活窗口、同时发送按键、同时写同一个文件、同时改同一个配置,或者一个脚本需要通知另一个脚本“任务已经开始/结束”。这些都属于 IPC,也就是进程间通信。

脚本间通信没有一种万能方案。文件、注册表、剪贴板写起来简单,但慢且容易被干扰;PostMessage 和 SendMessage 很适合脚本之间发短消息;WM_COPYDATA 可以传字符串或结构体;ObjRegisterActive 可以共享 AHK 对象;Mutex 和 Semaphore 更适合同步资源;共享内存、命名管道和 Socket 则适合更复杂、更高性能或跨程序的场景。

目录

  1. 文件 File
  2. 注册表 Registry
  3. 剪贴板 Clipboard
  4. PostMessage
  5. SendMessage、SendMessageCallback、WM_COPYDATA
  6. ObjRegisterActive
  7. Mutex 互斥量
  8. Semaphore 信号量
  9. Named shared memory 命名共享内存
  10. Named pipe 命名管道
  11. Socket
  12. GWLP_USERDATA 和 atoms

先给选型结论

  • 只想保存状态、方便人工检查:文件或注册表。
  • 只想临时传一段文本:剪贴板可以用,但不够稳定。
  • 想快速通知另一个脚本:PostMessage。
  • 想发送请求并等待返回值:SendMessage。
  • 想传字符串、简单结构体:WM_COPYDATA。
  • 想共享一个 AHK 对象或做脚本服务:ObjRegisterActive。
  • 想防止多个脚本同时操作一个资源:Mutex。
  • 想限制最多几个脚本同时运行某段任务:Semaphore。
  • 想高性能传大量数据:共享内存。
  • 想做稳定的服务端/客户端通信:命名管道或 Socket。

原作者个人更常用 SendMessage,因为它速度快、事件驱动、可以发送数据并拿到返回值。若核心问题是多个脚本抢同一个资源,则应优先考虑 Mutex 或 Semaphore,而不是只想着“怎么传消息”。

1. 文件通信 File

文件通信是最容易想到的办法:脚本 A 写入一个文件,脚本 B 读取这个文件。比如两个脚本都要操作窗口时,可以用一个 lock.txt 表示“当前资源已被占用”。

优点:简单、容易调试,可以直接打开文件查看内容;数据可以保留到重启之后。

缺点:速度慢;高频写入会增加磁盘压力;资源开销较高;安全性一般,用户或其他程序都可以看到或修改文件。

文件通信的关键问题是“锁”。如果脚本 B 正好在脚本 A 打开文件但还没写入内容时读取,就可能误判文件为空。更稳的做法是用 FileOpen 的共享模式让文件在写入期间不可被其他脚本读写。

Script1.ahk:锁定文件,直到按 F1 退出。

#Requires AutoHotkey v1.1

; Script1.ahk
; 打开 lock.txt,并通过共享模式阻止其它脚本同时读写
f := FileOpen("lock.txt", "a-rwd")
f.Write("SCRIPT1")

MsgBox, 64, Script1, 我已经锁定 lock.txt,按 F1 退出后才释放。
return

F1::
ExitApp

Script2.ahk:循环尝试打开文件,直到锁被释放。

; Script2.ahk
MsgBox, 64, Script2, 我会等待 lock.txt 可写。

Loop
{
    ; 如果文件正在被其它脚本占用,FileOpen 会失败
    f := FileOpen("lock.txt", "a-rwd")
    if IsObject(f)
    {
        f.Write("SCRIPT2")
        f.Close()
        break
    }
    Sleep, 200
}

MsgBox, 64, Script2, Script2 已经获得 lock.txt 的访问权。

2. 注册表通信 Registry

注册表通信和文件通信类似:一个脚本用 RegWrite 写状态,另一个脚本用 RegRead 读状态。它适合做低频状态广播或保存配置,但不适合多个脚本同时写同一个键。

优点:使用简单;可以用注册表编辑器调试;数据可以跨重启保留。

缺点:速度慢;安全性一般;数据类型有限,多数情况下主要是字符串和数字;没有天然锁机制,无法很好地避免多个脚本同时写入。

建议优先写入 HKEY_CURRENT_USER,避免管理员权限和不同用户配置的问题。

Script1.ahk:写入状态。

; Script1.ahk
RegWrite, REG_SZ, HKEY_CURRENT_USER\SOFTWARE\AHKScripts, Script1, 我正在执行任务
MsgBox, 已写入 Script1 状态。

Script2.ahk:读取状态。

; Script2.ahk
RegRead, state, HKEY_CURRENT_USER\SOFTWARE\AHKScripts, Script1
if ErrorLevel
    state := "没有读取到状态"

MsgBox, Script1 当前状态:%state%

3. 剪贴板通信 Clipboard

剪贴板是一个全局共享资源,所以也可以用来通信。发送方写入特定前缀的文本,接收方监听剪贴板变化并识别这个前缀。

优点:容易实现,传文本方便。

缺点:很容易被用户复制粘贴、其它脚本、输入法或软件干扰;如果没有备份恢复,会破坏用户原本剪贴板。

如果用剪贴板通信,至少要做到:消息加唯一前缀、发送前备份剪贴板、发送成功或超时后恢复剪贴板。

ClipRead.ahk:监听剪贴板文本。

; ClipRead.ahk
OnClipboardChange("ClipChange")
return

ClipChange(type)
{
    ; type=1 通常表示剪贴板里是文本
    if (type != 1)
        return

    data := Clipboard
    prefix := "AHKClipboardMessage:"

    if (SubStr(data, 1, StrLen(prefix)) != prefix)
        return

    ; 清空剪贴板作为“已收到”的信号
    OnClipboardChange("ClipChange", 0)
    Clipboard :=

    msg := SubStr(data, StrLen(prefix) + 1)
    MsgBox, 64, ClipRead, 收到数据:%msg%
    ExitApp
}

ClipWrite.ahk:写入消息,并在超时后恢复剪贴板。

; ClipWrite.ahk
ClipSave := ClipboardAll
Clipboard := "AHKClipboardMessage:这段文本通过剪贴板发送"

OnClipboardChange("ClipChange")
SetTimer, RestoreClipboard, -500
return

ClipChange(type)
{
    global ClipSave

    ; 接收方清空剪贴板后,type 通常会变成 0
    if (type != 0)
        return

    SetTimer, RestoreClipboard, Off
    Gosub, RestoreClipboard
    MsgBox, 64, ClipWrite, 对方已经收到剪贴板消息。
}

RestoreClipboard:
OnClipboardChange("ClipChange", 0)
Clipboard := ClipSave
return

4. PostMessage

PostMessage 适合发送短消息。它可以发给指定窗口,也可以广播给所有窗口。它是事件驱动的,接收方不需要一直轮询。

优点:比较容易使用;速度快;事件驱动;可以通过 HWND_BROADCAST 广播给所有窗口。

缺点:数据量有限,适合传数字、状态码、短参数,不适合直接传复杂对象或大文本。

建议使用 RegisterWindowMessage 注册一个自定义消息名,再用 OnMessage() 监听,避免和系统消息冲突。

Receiver.ahk:等待其它脚本广播消息。

; Receiver.ahk
MsgNum := DllCall("RegisterWindowMessage", "Str", "NewAHKScript")
OnMessage(MsgNum, "NewScriptCreated")
return

NewScriptCreated(wParam, lParam, msg, hwnd)
{
    MsgBox, 64, Receiver, 新脚本窗口句柄:%hwnd%`n`nwParam:%wParam%`nlParam:%lParam%
}

Sender.ahk:广播自己已经启动,并携带两个数字参数。

; Sender.ahk
MsgNum := DllCall("RegisterWindowMessage", "Str", "NewAHKScript")

; 0xFFFF 是 HWND_BROADCAST,表示广播给所有顶层窗口
PostMessage, %MsgNum%, 123, -456,, ahk_id 0xFFFF

5. SendMessage、SendMessageCallback、WM_COPYDATA

SendMessage 可以把数据发给指定脚本,并等待对方返回结果。普通 SendMessage 和 PostMessage 类似,主要传两个数字;如果需要传字符串或简单结构体,就可以使用 WM_COPYDATA。

优点:比较容易使用;速度快;事件驱动;能拿到返回值。

缺点:需要目标窗口;普通消息数据量有限;接收方处理太慢会拖慢双方,除非改用 SendMessageCallback 之类的异步方式。

注意:WM_COPYDATA 适合传简单线性数据,例如字符串或不嵌套的结构体。不能直接传 AHK 对象、数组、嵌套结构或指针,因为另一个进程里的内存地址并不相同。

Receiver.ahk:接收 SendMessage 并返回一个数字。

; Receiver.ahk
MsgNum := DllCall("RegisterWindowMessage", "Str", "AHK_RequestReply")
OnMessage(MsgNum, "HandleRequest")
return

HandleRequest(wParam, lParam, msg, hwnd)
{
    ; SendMessage 可以通过 return 把数字返回给发送方
    MsgBox, 64, Receiver, 收到请求:`nwParam=%wParam%`nlParam=%lParam%
    return 2026
}

Sender.ahk:发送请求并等待返回值。

; Sender.ahk
DetectHiddenWindows, On

receiverHwnd := WinExist("Receiver.ahk ahk_class AutoHotkey")
if (!receiverHwnd)
{
    MsgBox, 请先运行 Receiver.ahk
    ExitApp
}

MsgNum := DllCall("RegisterWindowMessage", "Str", "AHK_RequestReply")
SendMessage, %MsgNum%, 123, -456,, ahk_id %receiverHwnd%

MsgBox, 64, Sender, 收到返回值:%ErrorLevel%

WM_COPYDATA 接收方示例:接收字符串。

; WM_COPYDATA_Receiver.ahk
OnMessage(0x4A, "ReceiveCopyData") ; 0x4A = WM_COPYDATA
return

ReceiveCopyData(wParam, lParam)
{
    cbData := NumGet(lParam + A_PtrSize, 0, "UInt")
    pData  := NumGet(lParam + A_PtrSize * 2, 0, "Ptr")
    text   := StrGet(pData, cbData // 2, "UTF-16")

    MsgBox, 64, WM_COPYDATA Receiver, 收到文本:%text%
    return true
}

WM_COPYDATA 发送方示例:发送字符串。

; WM_COPYDATA_Sender.ahk
DetectHiddenWindows, On

target := WinExist("WM_COPYDATA_Receiver.ahk ahk_class AutoHotkey")
if (!target)
{
    MsgBox, 请先运行 WM_COPYDATA_Receiver.ahk
    ExitApp
}

text := "这是一段通过 WM_COPYDATA 发送的文本"
bytes := (StrLen(text) + 1) * 2

VarSetCapacity(buf, bytes, 0)
StrPut(text, &buf, "UTF-16")

VarSetCapacity(cds, A_PtrSize * 3, 0)
NumPut(1, cds, 0, "Ptr")                 ; dwData,可自定义
NumPut(bytes, cds, A_PtrSize, "UInt")    ; cbData,字节数
NumPut(&buf, cds, A_PtrSize * 2, "Ptr")  ; lpData,数据指针

SendMessage, 0x4A, 0, &cds,, ahk_id %target%
MsgBox, 发送完成,返回值:%ErrorLevel%

6. ObjRegisterActive

ObjRegisterActive 可以把一个 AHK 对象注册为活动对象,让其它脚本通过 COM 的方式获取并调用它。因为对象保存在内存里,读写速度很快,也很适合做“脚本服务”。

优点:可以共享对象和方法,封装能力强,适合做后台服务。

缺点:不容易监听对象变化;多个脚本同时读写同一个对象时仍然会有并发问题,必要时需要配合 Mutex。

Register.ahk:注册共享对象。

; Register.ahk
sharedObj := { key: "test", count: 0 }
ObjRegisterActive(sharedObj, "{EB5BAF88-E58D-48F9-AE79-56392D4C7AF6}")
MsgBox, 已注册共享对象,关闭脚本后对象失效。
return

ObjRegisterActive(Object, CLSID, Flags := 0)
{
    static cookieJar := {}

    if (CLSID = "")
    {
        if cookieJar.HasKey(Object)
            DllCall("oleaut32\RevokeActiveObject", "UInt", cookieJar.Delete(Object), "Ptr", 0)
        return
    }

    VarSetCapacity(_clsid, 16, 0)
    if DllCall("ole32\CLSIDFromString", "WStr", CLSID, "Ptr", &_clsid) < 0
        throw Exception("CLSID 无效:" CLSID)

    punk := ComObjValue(Object)
    hr := DllCall("oleaut32\RegisterActiveObject", "Ptr", punk, "Ptr", &_clsid, "UInt", Flags, "UInt*", cookie)
    if (hr < 0)
        throw Exception("RegisterActiveObject 失败:" hr)

    cookieJar[CLSID] := cookie
}

Read.ahk:读取共享对象。

; Read.ahk
obj := ComObjActive("{EB5BAF88-E58D-48F9-AE79-56392D4C7AF6}")
MsgBox, % "key=" obj.key "`ncount=" obj.count

Write.ahk:修改共享对象。

; Write.ahk
obj := ComObjActive("{EB5BAF88-E58D-48F9-AE79-56392D4C7AF6}")
obj.count += 1
MsgBox, % "count 已修改为:" obj.count

7. Mutex 互斥量

Mutex 是 mutual exclusion 的缩写,意思是互斥。它不是用来传递大量数据的,而是用来保证同一时间只有一个脚本能访问某个共享资源,比如文件、窗口、共享对象、发送按键等。

如果你的问题是“多个脚本会互相打架”,Mutex 往往比通信方案更关键。

Script1.ahk:占用互斥量,直到消息框关闭。

; Script1.ahk
mtx := new Mutex("Local\MyMutex")

if mtx.Wait(0)
{
    MsgBox, 64, Script1, 我已经占用 Mutex,关闭此窗口后释放。
    mtx.Release()
}
else
{
    MsgBox, 48, Script1, Mutex 已被其它脚本占用。
}

class Mutex
{
    __New(name)
    {
        this.h := DllCall("CreateMutex", "Ptr", 0, "Int", 0, "Str", name, "Ptr")
        if (!this.h)
            throw Exception("CreateMutex 失败")
    }

    Wait(timeout := 0xFFFFFFFF)
    {
        r := DllCall("WaitForSingleObject", "Ptr", this.h, "UInt", timeout, "UInt")
        return (r = 0)
    }

    Release()
    {
        return DllCall("ReleaseMutex", "Ptr", this.h)
    }

    __Delete()
    {
        if (this.h)
            DllCall("CloseHandle", "Ptr", this.h)
    }
}

Script2.ahk:尝试进入同一个互斥量。

; Script2.ahk
mtx := new Mutex("Local\MyMutex")

if mtx.Wait(1000)
{
    MsgBox, 64, Script2, 我获得了 Mutex,可以安全操作共享资源。
    mtx.Release()
}
else
{
    MsgBox, 48, Script2, 等待超时,共享资源仍被占用。
}

class Mutex
{
    __New(name)
    {
        this.h := DllCall("CreateMutex", "Ptr", 0, "Int", 0, "Str", name, "Ptr")
    }
    Wait(timeout := 0xFFFFFFFF)
    {
        return DllCall("WaitForSingleObject", "Ptr", this.h, "UInt", timeout, "UInt") = 0
    }
    Release()
    {
        return DllCall("ReleaseMutex", "Ptr", this.h)
    }
    __Delete()
    {
        if (this.h)
            DllCall("CloseHandle", "Ptr", this.h)
    }
}

8. Semaphore 信号量

Semaphore 和 Mutex 都是同步工具。区别是 Mutex 通常只允许一个脚本进入,而 Semaphore 可以设置最多允许 N 个脚本同时进入。

例如你有很多个脚本实例在执行任务,但最多只允许两个实例同时运行,就可以用信号量控制并发数量。

; Semaphore.ahk
; 最多允许 2 个实例同时进入
sem := new Semaphore(2, 2, "Local\AHKSemaphore")

if sem.Wait(0)
{
    MsgBox, 64, Semaphore, 我进入了任务区,关闭窗口后释放名额。
    sem.Release()
}
else
{
    MsgBox, 48, Semaphore, 当前已有 2 个实例在运行,请稍后再试。
}

class Semaphore
{
    __New(initialCount, maximumCount, name)
    {
        this.h := DllCall("CreateSemaphore", "Ptr", 0, "Int", initialCount, "Int", maximumCount, "Str", name, "Ptr")
        if (!this.h)
            throw Exception("CreateSemaphore 失败")
    }

    Wait(timeout := 0xFFFFFFFF)
    {
        return DllCall("WaitForSingleObject", "Ptr", this.h, "UInt", timeout, "UInt") = 0
    }

    Release(count := 1)
    {
        return DllCall("ReleaseSemaphore", "Ptr", this.h, "Int", count, "Int*", oldCount := 0)
    }

    __Delete()
    {
        if (this.h)
            DllCall("CloseHandle", "Ptr", this.h)
    }
}

9. Named shared memory 命名共享内存

命名共享内存可以让多个进程打开同一块内存区域,适合传递大量数据或高频读写。它比文件、注册表、剪贴板快得多,但实现难度也更高。

共享内存只解决“数据放在哪里”,不解决“什么时候能安全读写”。如果多个脚本同时读写同一块内存,仍然需要 Mutex 或其它同步机制。

Writer.ahk:创建共享内存并写入字符串。

; SharedMemory_Writer.ahk
name := "Local\AHKSharedMemoryDemo"
size := 4096

PAGE_READWRITE := 0x04
FILE_MAP_ALL_ACCESS := 0xF001F

hMap := DllCall("CreateFileMapping", "Ptr", -1, "Ptr", 0, "UInt", PAGE_READWRITE
    , "UInt", 0, "UInt", size, "Str", name, "Ptr")

if (!hMap)
{
    MsgBox, CreateFileMapping 失败。
    ExitApp
}

pView := DllCall("MapViewOfFile", "Ptr", hMap, "UInt", FILE_MAP_ALL_ACCESS
    , "UInt", 0, "UInt", 0, "UPtr", size, "Ptr")

text := "这段文本来自共享内存 Writer"
StrPut(text, pView, size // 2, "UTF-16")

MsgBox, 64, Writer, 已写入共享内存,关闭后释放。

DllCall("UnmapViewOfFile", "Ptr", pView)
DllCall("CloseHandle", "Ptr", hMap)

Reader.ahk:打开共享内存并读取字符串。

; SharedMemory_Reader.ahk
name := "Local\AHKSharedMemoryDemo"
size := 4096
FILE_MAP_READ := 0x0004

hMap := DllCall("OpenFileMapping", "UInt", FILE_MAP_READ, "Int", 0, "Str", name, "Ptr")
if (!hMap)
{
    MsgBox, 请先运行 SharedMemory_Writer.ahk
    ExitApp
}

pView := DllCall("MapViewOfFile", "Ptr", hMap, "UInt", FILE_MAP_READ
    , "UInt", 0, "UInt", 0, "UPtr", size, "Ptr")

text := StrGet(pView, "UTF-16")
MsgBox, 64, Reader, 读取到:%text%

DllCall("UnmapViewOfFile", "Ptr", pView)
DllCall("CloseHandle", "Ptr", hMap)

10. Named pipe 命名管道

命名管道适合本机或网络上的进程通信。通常由服务端创建管道,客户端连接,然后双方像读写文件一样读写管道。

优点:速度快;支持双向通信;可以阻塞也可以非阻塞;可以支持多个客户端。

缺点:在 AHK 中扩展多个客户端会比较复杂;非阻塞实现也不轻松。

PipeServer.ahk:创建管道并等待客户端写入。

; PipeServer.ahk
pipeName := "\\.\pipe\AHKPipeDemo"

PIPE_ACCESS_DUPLEX := 0x00000003
PIPE_TYPE_MESSAGE := 0x00000004
PIPE_READMODE_MESSAGE := 0x00000002
PIPE_WAIT := 0x00000000

hPipe := DllCall("CreateNamedPipe", "Str", pipeName
    , "UInt", PIPE_ACCESS_DUPLEX
    , "UInt", PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT
    , "UInt", 1, "UInt", 4096, "UInt", 4096, "UInt", 0, "Ptr", 0, "Ptr")

if (!hPipe)
{
    MsgBox, CreateNamedPipe 失败。
    ExitApp
}

MsgBox, 64, PipeServer, 管道已创建,等待客户端连接。
DllCall("ConnectNamedPipe", "Ptr", hPipe, "Ptr", 0)

VarSetCapacity(buf, 4096, 0)
DllCall("ReadFile", "Ptr", hPipe, "Ptr", &buf, "UInt", 4096, "UInt*", bytesRead := 0, "Ptr", 0)

text := StrGet(&buf, bytesRead // 2, "UTF-16")
MsgBox, 64, PipeServer, 收到:%text%

DllCall("CloseHandle", "Ptr", hPipe)

PipeClient.ahk:连接管道并写入文本。

; PipeClient.ahk
pipeName := "\\.\pipe\AHKPipeDemo"

DllCall("WaitNamedPipe", "Str", pipeName, "UInt", 5000)
hFile := DllCall("CreateFile", "Str", pipeName, "UInt", 0x40000000
    , "UInt", 0, "Ptr", 0, "UInt", 3, "UInt", 0, "Ptr", 0, "Ptr")

if (hFile = -1)
{
    MsgBox, 连接命名管道失败。
    ExitApp
}

text := "来自 PipeClient 的消息"
bytes := (StrLen(text) + 1) * 2
VarSetCapacity(buf, bytes, 0)
StrPut(text, &buf, "UTF-16")

DllCall("WriteFile", "Ptr", hFile, "Ptr", &buf, "UInt", bytes, "UInt*", written := 0, "Ptr", 0)
DllCall("CloseHandle", "Ptr", hFile)

MsgBox, 64, PipeClient, 已发送。

11. Socket

Socket 更适合网络通信,而不只是本机脚本之间通信。它通过 IP 地址和端口定位通信对象,常见协议是 TCP 和 UDP。

如果你的 AHK 脚本要和 Python、Node.js、其它程序、局域网设备或另一台电脑通信,Socket 是很自然的方案。缺点是需要处理端口、连接、协议、超时、异常等问题。

AHK v1 中常用做法是使用现成 Socket 库,不建议新手直接从 Winsock API 手搓完整 TCP 服务。下面是伪代码式结构,表达通信流程:

; Socket 通信思路示例(建议配合成熟 Socket 库使用)
; Server:
; 1. 监听 127.0.0.1:9000
; 2. 等待客户端连接
; 3. 收到文本后解析命令
; 4. 返回执行结果

; Client:
; 1. 连接 127.0.0.1:9000
; 2. 发送命令,例如 "ping"
; 3. 读取服务端返回值
; 4. 关闭连接

站内如果已经有 Socket 通信示例,可以优先参考:Socket通信_TCP与UDP示例集

12. GWLP_USERDATA 和 atoms

每个 AHK 脚本都有一个隐藏主窗口,而 Windows 窗口有一个 GWLP_USERDATA 字段,可以保存一个指针大小的数据。其它脚本只要知道这个窗口句柄,就能读取这个数字。

优点:实现简单,速度快。

缺点:基本是单向通信;直接共享大数据比较麻烦;如果保存的是指针,还需要考虑跨进程内存读取。

写入一个数字:

; WriteUserData.ahk
GWLP_USERDATA := -21
DllCall("SetWindowLongPtr", "Ptr", A_ScriptHwnd, "Int", GWLP_USERDATA, "Ptr", 1234)

MsgBox, 已把数字 1234 写入当前脚本主窗口的 GWLP_USERDATA。
return

从另一个脚本读取这个数字:

; ReadUserData.ahk
DetectHiddenWindows, On

hWnd := WinExist("WriteUserData.ahk ahk_class AutoHotkey")
if (!hWnd)
{
    MsgBox, 请先运行 WriteUserData.ahk
    ExitApp
}

GWLP_USERDATA := -21
value := DllCall("GetWindowLongPtr", "Ptr", hWnd, "Int", GWLP_USERDATA, "Ptr")
MsgBox, 读取到:%value%

Atoms 可以把字符串注册到系统原子表中,得到一个数字 ID。这样可以把“字符串名称”间接变成一个数字,再通过 PostMessage 或 GWLP_USERDATA 传给其它脚本。它适合传递共享内存名称、Mutex 名称等短字符串标识。

写入 atom 编号:

; AtomWriter.ahk
name := "Local\MySharedMappingName"
atom := DllCall("GlobalAddAtom", "Str", name, "UShort")

GWLP_USERDATA := -21
DllCall("SetWindowLongPtr", "Ptr", A_ScriptHwnd, "Int", GWLP_USERDATA, "Ptr", atom)

MsgBox, 已把 atom 编号写入 GWLP_USERDATA:%atom%
return

读取 atom 并还原字符串:

; AtomReader.ahk
DetectHiddenWindows, On

hWnd := WinExist("AtomWriter.ahk ahk_class AutoHotkey")
GWLP_USERDATA := -21
atom := DllCall("GetWindowLongPtr", "Ptr", hWnd, "Int", GWLP_USERDATA, "Ptr")

VarSetCapacity(buf, 260 * 2, 0)
DllCall("GlobalGetAtomName", "UShort", atom, "Str", buf, "Int", 260)

MsgBox, atom=%atom%`n名称=%buf%

整体对比

方法 适合场景 优点 主要问题
文件 低频状态、简单锁、人工可检查数据 简单、可持久化、易调试 慢,容易被外部修改
注册表 配置、低频状态广播 可持久化,RegEdit 可查看 不适合多写入方和高频通信
剪贴板 临时文本消息 实现简单 容易干扰用户剪贴板
PostMessage 短消息、事件通知、广播 快,事件驱动 数据量有限
SendMessage 请求/响应、需要返回值 快,可返回结果 接收方慢会阻塞发送方
WM_COPYDATA 字符串、简单结构体 适合传文本 不能直接传对象和指针结构
ObjRegisterActive 共享对象、脚本服务 封装能力强,速度快 并发控制要另做
Mutex 资源互斥 防止脚本互相抢资源 不负责传大数据
Semaphore 限制并发数量 适合任务队列 比 Mutex 稍难理解
共享内存 大量数据、高性能读写 很快 实现复杂,需要同步
命名管道 服务端/客户端通信 双向、稳定、较快 AHK 中多客户端扩展较复杂
Socket 跨程序、跨机器、网络通信 通用性强 要处理协议和网络异常

本站补充:AHK v1 用户的学习顺序

如果你主要写 AHK v1,不建议一上来就研究共享内存和命名管道。更实用的路线是:

  1. 先理解文件、注册表、剪贴板为什么简单但不稳定。
  2. 重点掌握 PostMessage 和 SendMessage。
  3. 需要传文本时学习 WM_COPYDATA。
  4. 多个脚本抢同一资源时学习 Mutex。
  5. 脚本规模变大后,再研究 ObjRegisterActive、命名管道、Socket 和共享内存。

很多脚本间通信问题,表面上是“我怎么把数据发过去”,本质上其实是“我怎么避免两个脚本同时操作同一个资源”。所以设计方案前,先判断你要解决的是传数据、发通知、等待返回、共享状态,还是同步资源。这个判断做对了,后面代码会简单很多。

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