多进程共享函数通信 1.4

多线程与单线程的区别:

单线程顺序执行:ABCDEFG,HIJKLMN
单线程异步执行:ABC,KLMN,EFGHI,错开执行避免I/O之类的长时间等待卡住线程【WinWait】
多线程:一个进程内可有多个线程ABCDEFG同时执行互不干扰
多进程:创建多个进程ABCDEFG并互相通信,来达到仿多线程的效果。

 

支持编译后,自己调用自身实现多进程

20220714:修复错误并修改函数名称,与AHK_H 版本进行区分。可以更好的实现多进程+多线程

20220327:修复之前的版本ahkGetvar()无法获取变量内容的BUG,并给出解决方案

/*
═════════════════════════════════════════════════════════════════
                        多进程共享函数、连锁关闭并通信

                    关联进程变量 := New ExecProcess("标签名称") 新建关联进程, 可传参
                    关联进程变量 := ""         清空这个 "关联进程变量" 来关闭对应的进程

     语法参考AHK_H对线程控制的用法,并新增相互通信和进程间互相控制的功能。支持编译运行
  新进程默认隐藏托盘图标和禁用热键来避免冲突,恢复热键和控制的方法类库说明里的 F3 和 F4
═════════════════════════════════════════════════════════════════
*/
; #Include <ExecProcess>

; 受函数封闭性的影响,只能在脚本头部自行判断是否加载新进程,让新进程正常运行
if (A_Args[10]="演示新进程代码载入")
    Goto % A_Args[10]

Process1 := New ExecProcess("演示新进程代码载入", , , ,"value4") ; 带传参的开启新进程

Gosub 演示新进程代码载入 ; 复用共享标签和函数演示
Return

演示新进程代码载入:
Gui -MinimizeBox -MaximizeBox +AlwaysOnTop
Gui Add, Edit, w300 R2 v通信显示 g同步发送
Gui Show, % "x850 w330 y" (A_Args[4]="Value4"?500:400), % (A_Args[4]="Value4"?"F2新":"P主") "进程 - 互相通信同步演示,请输入文字"

Loop {
    Sleep 80
    MouseGetPos, x, y
    ToolTip % (A_Args[4]="Value4"?"F2-新":"P-主") "进程持续运算演示-" A_Index, x+10, y-(A_Args[4]="Value4"?30:70)
}
Return

同步发送:
GuiControlGet, 获取编辑框内容,, 通信显示
if (A_Args[4]="Value4")
    ExecPostFunction( , "Gui同步更新", 获取编辑框内容, "通信显示") ; 第一个参数留空或者填"Parent"是给主进程发送信息
  else
    ExecPostFunction("演示新进程代码载入", "Gui同步更新", 获取编辑框内容, "通信显示")
Return

Gui同步更新(Value, ControlID) {
    GuiControl,, %ControlID%, %Value%
}

GuiClose:
    ; if (A_Args[9]="")
        ExitApp
Return


MyLabel:
    Menu Tray, Icon  ; 让新进程显示图标【新进程默认禁用热键以避免冲突】
    ; MsgBox, , , % "var变量值:" var, 1
Return

F1::
    ; ExecAssign("演示新进程代码载入", "var", "123456") ; 给新进程的变量赋值
    ExecLabel("演示新进程代码载入", "MyLabel") ; 让新进程跳转至指定标签
    if (onoff := !onoff)
        ExecPause("演示新进程代码载入") ; 暂停指定进程
     else
        ExecPause("演示新进程代码载入", "Off")
Return

; F2键做新进程的开、关、一键开关的演示【只有主进程端能够新建进程】
F2::
    ; Process1 := New ExecProcess("演示新进程代码载入") ; 不带传参的新建进程
    Process1 := ""
    ; ProcessOnOff:=(Toggle:=!Toggle) ? New ExecProcess("演示新进程代码载入",,,, "Value4") : ""
Return

Class ExecProcess {  ; By dbgba   Thank FeiYue
    ; 关联进程变量 := New ExecProcess("标签名称") 新建一个关联进程,重复新建该进程变量可重启此进程。最多可传递8组参数
    __New(LabelOrFunc, Arg1:="", Arg2:="", Arg3:="", Arg4:="", Arg5:="", Arg6:="", Arg7:="", Arg8:="") {
        if (A_Args[9]!="")
            Return
        ParentPID := DllCall("GetCurrentProcessId")
        if A_IsCompiled
            Run "%A_ScriptFullPath%" /f "%Arg1%" "%Arg2%" "%Arg3%" "%Arg4%" "%Arg5%" "%Arg6%" "%Arg7%" "%Arg8%" "%ParentPID%" "%LabelOrFunc%",,, pid
         else
            Run "%A_AhkPath%" /f "%A_ScriptFullPath%" "%Arg1%" "%Arg2%" "%Arg3%" "%Arg4%" "%Arg5%" "%Arg6%" "%Arg7%" "%Arg8%" "%ParentPID%" "%LabelOrFunc%",,, pid
        this.pid := pid
    }

    ; 关联进程变量 := "",清空这个“进程变量”来关闭对应的进程
    __Delete() {
        DetectHiddenWindows On  ; Logging Out Script
        SendMessage, 0x111, 65307,,, % A_ScriptFullPath " ahk_pid " this.pid
        Process Close, % this.pid
    }

    ; 与新进程同步退出,使用异步等待主进程结束回调
    _CallBack() {
        ExitApp
    }

    _ScriptStart() {
        Static init:=ExecProcess._ScriptStart()
        #NoTrayIcon
        SetBatchLines % ("-1", Bch:=A_BatchLines)
        OnMessage(0x4a, "_ExecProcessReceive_WM_COPYDATA")
        Gui _ExecProcess_Label%A_ScriptHwnd%: Add, Button, g_ExecProcessGuiHideLabelGoto
        if (A_Args[9]="") {
            DetectHiddenWindows % ("On", DHW:=A_DetectHiddenWindows)
            PostMessage, 0x111, 65307,,, <<ExecProcessParent>> ahk_class AutoHotkeyGUI
            DetectHiddenWindows %DHW%
            Menu Tray, Icon
            Gui _ExecProcess_Label%A_ScriptHwnd%: Show, Hide, <<ExecProcessParent>>
            SetBatchLines %Bch%
            Return
        }
        _ := DllCall("OpenProcess", "Uint", 0x100000, "int", False, "Uint", A_Args[9], "Ptr")
        Gui _ExecProcess_Label%A_ScriptHwnd%: Show, Hide, % "<<ExecProcess" A_Args[10] ">>"
        Suspend On  ; 屏蔽新进程的热键,来避免冲突
        DllCall("RegisterWaitForSingleObject", "Ptr*", 0, "Ptr", _, "Ptr", RegisterCallback("ExecProcess._CallBack", "F"), "Ptr", 0, "Uint", -1, "Uint", 8)
        SetBatchLines %Bch%
    }

    Send(StringToSend, Label:="Parent", wParam:=0) {
        SetBatchLines % ("-1", Bch:=A_BatchLines)
        VarSetCapacity(CopyDataStruct, 3*A_PtrSize, 0)
        , NumPut((StrLen(StringToSend) + 1) * (A_IsUnicode ? 2 : 1), CopyDataStruct, A_PtrSize)
        , NumPut(&StringToSend, CopyDataStruct, 2*A_PtrSize)
        DetectHiddenWindows % ("On", DHW:=A_DetectHiddenWindows)
        WinGet, NewPID, PID, <<ExecProcess%Label%>> ahk_class AutoHotkeyGUI
        SendMessage, 0x4a, wParam, &CopyDataStruct,, ahk_pid %NewPID% ahk_class AutoHotkey
        DetectHiddenWindows %DHW%
        SetBatchLines %Bch%
        Return ErrorLevel
    }

} ; // Class End

; ==================== ↓ 内部调用私有函数与标签 ↓ ====================

; 接收字符串后保存在变量名为"CopyOfData",以供调用
_ExecProcessReceive_WM_COPYDATA(wParam, lParam) {
    CopyOfData := StrGet(NumGet(lParam + 2*A_PtrSize))
    Switch wParam
    {    ; wParam 是ExecProcess库内部通讯的编号
      Case 1 : _ExecProcessPostFunction(CopyOfData)
      Case 2 : _ExecProcessPostFunction(CopyOfData,1)
      Case 3 :
        LabelName := StrReplace(SubStr(CopyOfData, 1, 50), " ")
        if !IsLabel(LabelName)
            Return False
        Gosub %LabelName%
      Case 4 :
        LabelName := StrReplace(SubStr(CopyOfData, 1, 50), " ")
        if !IsLabel(LabelName)
            Return False
        Global _ExecProcessVarNameRtn := "_ExecProcessVarGetvarValue"
        Gosub _ExecProcessVarGetvarRtn
        _ExecProcessVarGetvarValue := LabelName
        DetectHiddenWindows On
        Control, Check, , Button1, % "<<ExecProcess" (A_Args[10]="" ? "Parent" : A_Args[10]) ">> ahk_class AutoHotkeyGUI"
      Case 5 : ExecProcessvarName := StrReplace(SubStr(CopyOfData, 1, 50), " "), %ExecProcessvarName% := SubStr(CopyOfData, 51)
      Case 6 : Global _ExecProcessVarGetvarRtnVar := CopyOfData
      Case 7 :
        Global _ExecProcessVarNameRtn := StrReplace(SubStr(CopyOfData, 1, 50), " ")
        Gosub _ExecProcessVarGetvarRtn
        ExecProcess.Send(_ExecProcessVarGetvarValue, , 6)
      Case 8 : ExecProcess.Send(A_IsPaused, , 6)
      Case 9 :
        SetBatchLines % ("-1", Bch:=A_BatchLines)
        Critical
        Suspend Off
        s:="||Home|End|Ins|Del|PgUp|PgDn|Left|Right|Up|Down|NumpadEnter|"
        Loop 254
            k:=GetKeyName(Format("VK{:X}",A_Index)), s.=InStr(s,"|" k "|") ? "" : k "|"
        For k,v in { Escape:"Esc", Control:"Ctrl", Backspace:"BS" }
            s:=StrReplace(s, k, v)
        s:=Trim(RegExReplace(s,"\|+","|"), "|")
        Loop, Parse, s, |
        {  ; 只能够禁用大多数热键和组合
            Hotkey %A_LoopField%, Off, UseErrorLevel
            Hotkey ~%A_LoopField%, Off, UseErrorLevel
            Hotkey ^%A_LoopField%, Off, UseErrorLevel
            Hotkey #%A_LoopField%, Off, UseErrorLevel
            Hotkey !%A_LoopField%, Off, UseErrorLevel
            Hotkey +%A_LoopField%, Off, UseErrorLevel
            Hotkey ^!%A_LoopField%, Off, UseErrorLevel
            Hotkey ^+%A_LoopField%, Off, UseErrorLevel
            Hotkey ^#%A_LoopField%, Off, UseErrorLevel
        }
        For _,v in StrSplit(CopyOfData, "|")
            Hotkey %v%, On
        Critical Off
        SetBatchLines %Bch%
    }
    Return True
}

_ExecProcessPostFunction(CopyOfData, Synchronous:=0) {
    Global _ExecProcessFunctionName := StrReplace(SubStr(CopyOfData, 1, 50), " "), _ExecProcessFunctionArgs := []
    Loop 10
        _ExecProcessFunctionArgs[A_Index] := RegExReplace(CopyOfData, "(^.+ExecProcessFuncNameLabelArg" A_Index+10 ")(.*)(ExecProcessFuncNameLabelArg" A_Index+30 ".+)", "$2")
    if !IsFunc(_ExecProcessFunctionName)
        Return
    if Synchronous
        SetTimer _ProcessPostFunctionSetTimer, -1
      else
        Gosub _ProcessPostFunctionSetTimer
    Return

    _ProcessPostFunctionSetTimer:
    %_ExecProcessFunctionName%(_ExecProcessFunctionArgs*)
    Return
}

Goto _ExecProcessLabelSkip

_ExecProcessGuiHideLabelGoto:
    Goto %_ExecProcessVarGetvarValue%
Return

_ExecProcessVarGetvarRtn:
    Global _ExecProcessVarGetvarValue := %_ExecProcessVarNameRtn%
    Gui _debug_%A_ScriptHwnd%: Add, Text,, % _ExecProcessVarGetvarValue %_ExecProcessVarNameRtn%
Return

_ExecProcessLabelSkip:
SetBatchLines %A_BatchLines%
; ==================== ↑ 内部调用私有函数与标签 ↑ ====================


; 让进程调用函数【等待函数执行完毕才返回】
; ExecFunction("演示新进程代码载入", "MyFunc", "Hello World!")
ExecFunction(ProcessLabel:="Parent", FuncName:="", Arg1:="", Arg2:="", Arg3:="", Arg4:="", Arg5:="", Arg6:="", Arg7:="", Arg8:="", Arg9:="", Arg10:="") {
    L := "ExecProcessFuncNameLabelArg", FuncNameArgs := Format("{:-50}", FuncName) . L "11" Arg1 L "31" L "12" Arg2 L "32" L "13" Arg3 L "33" L "14" Arg4 L "34" L "15" Arg5 L "35" L "16" Arg6 L "36" L "17" Arg7 L "37" L "18" Arg8 L "38" L "19" Arg9 L "39" L "20" Arg10 L "40End"
    Return ExecProcess.Send(FuncNameArgs, ProcessLabel, 1)
}

; 让进程调用函数【不等待函数执行完毕返回】
; ExecPostFunction("演示新进程代码载入", "MyFunc", "Hello World!")
ExecPostFunction(ProcessLabel:="Parent", FuncName:="", Arg1:="", Arg2:="", Arg3:="", Arg4:="", Arg5:="", Arg6:="", Arg7:="", Arg8:="", Arg9:="", Arg10:="") {
    L := "ExecProcessFuncNameLabelArg", FuncNameArgs := Format("{:-50}", FuncName) . L "11" Arg1 L "31" L "12" Arg2 L "32" L "13" Arg3 L "33" L "14" Arg4 L "34" L "15" Arg5 L "35" L "16" Arg6 L "36" L "17" Arg7 L "37" L "18" Arg8 L "38" L "19" Arg9 L "39" L "20" Arg10 L "40End"
    Return ExecProcess.Send(FuncNameArgs, ProcessLabel, 2)
}

; 只有异步执行标签能不受变量作用域影响,所以默认使用异步执行
; 让进程跳转至指定标签 ExecLabel("演示新进程代码载入", "MyLabel")
ExecLabel(ProcessLabel:="Parent", LabelName:="", DoNotWait:=0) {
    if DoNotWait
        Rtn := ExecProcess.Send(Format("{:-50}", LabelName), ProcessLabel, 3)  ; Synchronisation
      else
        Rtn := ExecProcess.Send(Format("{:-50}", LabelName), ProcessLabel, 4)  ; Asynchronous
    Return Rtn
}

; 给进程的变量赋值:ExecAssign("演示新进程代码载入", "var", "123456")
ExecAssign(ProcessLabel:="Parent", VarName:="", Value:="") {
    Return ExecProcess.Send(Format("{:-50}", VarName) . Value, ProcessLabel, 5)
}

; 返回进程中变量的内容:MsgBox % ExecGetvar("演示新进程代码载入","var")
ExecGetvar(ProcessLabel:="Parent", VarName:="") {
    Global _ExecProcessVarGetvarRtnVar
    ExecProcess.Send(Format("{:-50}", VarName), ProcessLabel, 7)
    Return _ExecProcessVarGetvarRtnVar
}

; 查看新进程运行状态:MsgBox % ExecReady("演示新进程代码载入")
ExecReady(ProcessLabel) {
    DetectHiddenWindows On
    Return WinExist("<<ExecProcess" ProcessLabel ">> ahk_class AutoHotkeyGUI") ? 1 : 0
}

; 暂停指定进程:ExecPause("演示新进程代码载入", "Off")
ExecPause(ProcessLabel, ahkPauseOnOff:="On") {
    Global _ExecProcessVarGetvarRtnVar
    ExecProcess.Send("", ProcessLabel, 8) ; Return _ExecProcessVarGetvarRtnVar
    DetectHiddenWindows On
    if (_ExecProcessVarGetvarRtnVar=1) && (ahkPauseOnOff="Off")
        PostMessage, 0x111, 65306,,, <<ExecProcess%ProcessLabel%>> ahk_class AutoHotkeyGUI
      else if (_ExecProcessVarGetvarRtnVar=0) && (ahkPauseOnOff="On")
        PostMessage, 0x111, 65306,,, <<ExecProcess%ProcessLabel%>> ahk_class AutoHotkeyGUI
}

/*
; 【由于复用进程机制的缘故,新进程将无法启用热键。若想在新进程启用热键可参考以下方法】
; ==== F3和F4为弥补措施, 按F3后F2结束新进程将无法启用, 需要按F4恢复给主进程才行 ====

; 开启关联进程的热键,启用后会屏蔽主进程对应热键。屏蔽后,需要F4为主进程恢复热键
F3::ExecHotkey("演示新进程代码载入","Esc|F2|F3") ; 用 | 号分隔来添加多个热键

; ExecHotkey只能屏蔽日常大部分热键和组合键,会有组合键疏漏。若不了解, 则不推荐对新进程开启热键
F4::ExecRecoveryHotkey("演示新进程代码载入") ; 恢复主进程热键
*/

; 启用指定进程的热键:ExecHotkey("演示新进程代码载入","Esc|F2|^1")
ExecHotkey(ProcessLabel, ProcessHotkey) {
    if (ProcessLabel!="")
        For _,v in StrSplit(ProcessHotkey, "|")
            Hotkey, %v%, Off
    ExecRecoveryHotkey(ProcessLabel, ProcessHotkey)
    , ExecProcess.Send(ProcessHotkey, ProcessLabel, 9)
}

; 记录与恢复主进程热键:ExecRecoveryHotkey("演示新进程代码载入")
ExecRecoveryHotkey(ProcessLabel, ProcessHotkey:="") {
    Static _Boolean
    if (A_Args[9]!="")
        Return
    if !_Boolean
        _Boolean:=[]
    if (ProcessHotkey="") {
        For _,v in StrSplit(_Boolean[ProcessLabel], "|")
            Hotkey %v%, On
        Return _Boolean[ProcessLabel]
    }
    Return _Boolean[ProcessLabel] := ProcessHotkey
}

; 临时新建进程【依赖AHK解释器,编译后无效】ProcessExec("Loop{`nSleep 80`nToolTip test-%A_Index%`n}")
; 结束临时进程:ProcessExec("")    ;【第二个参数带编号可不重复新建临时进程】
ProcessExec(NewCode:="", flag:="Default") {
    if A_AhkPath {
        SetBatchLines % ("-1", Bch:=A_BatchLines)
        Critical
        DetectHiddenWindows On
        WinGet, NewPID, PID, <<ExecNew%flag%>> ahk_class AutoHotkeyGUI
        Process Close, %NewPID%
        add=`nflag=<<ExecNew%flag%>>`n
        (%
        #NoTrayIcon
        Gui Gui_ExecFlag_Gui%A_ScriptHwnd%: Show, Hide, %flag%
        DllCall("RegisterShellHookWindow", "Ptr", A_ScriptHwnd)
        , OnMessage(DllCall("RegisterWindowMessage", "Str", "ShellHook"), "_ShellEvent")
        _ShellEvent() {
            DetectHiddenWindows On
            IfWinNotExist <<ExecProcessParent>> ahk_class AutoHotkeyGUI, , ExitApp
         }
        )
        NewCode:=add "`n" NewCode "`nExitApp"
        , exec := ComObjCreate("WScript.Shell").Exec(A_AhkPath " /ErrorStdOut /f *")
        , exec.StdIn.Write(NewCode)
        , exec.StdIn.Close()
        Critical Off
        SetBatchLines %Bch%
        WinWait, <<ExecNew%flag%>> ahk_class AutoHotkeyGUI, , 3
        WinGet, RtnID, ID, <<ExecNew%flag%>> ahk_class AutoHotkeyGUI
        if RtnID
            Return True
    }
    Return False
}

; 读取ahk脚本来新建临时新进程【依赖AHK解释器,编译后无效】
; ProcessExecFile("NewScript.ahk")  ;【第二个参数带编号可不重复新建临时进程】
ProcessExecFile(FilePath:="", flag:="Default") {
    SplitPath, FilePath,,,,, drive
    if drive=
        FilePath=%A_ScriptDir%\%FilePath%
    FileRead, FileReadVar, %FilePath%
    if (FileReadVar!="")
        Rtn := ProcessExec(FileReadVar, flag)
    Return (Rtn="" ? False : Rtn)
}

给TA捐赠
共{{data.count}}人
人已捐赠
其他

学习AHK第7天

2021-12-9 17:14:50

其他

AHKv1文件或目录监控〔搬运〕

2021-12-10 12:55:28

7 条回复 A文章作者 M管理员
  1. AHK中文社区
    1河许人给您打赏了¥2
  2. 蜜獾哥
    蜜獾哥给您打赏了¥5
  3. 山重水复疑无路

    我是不是可以理解成,Global声明的全局变量,只在单个进程里有用?

    • dbgba

      对的,大致就是简易的操控其它进程。操控其它进程的方法是通过进程之间的通信在操作

  4. random
    random给您打赏了¥10
  5. AHK中文社区
    1河许人给您打赏了¥200
个人中心
购物车
优惠劵
有新私信 私信列表
搜索