Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Thread() for AutoHotkey.dll


  • Please log in to reply
8 replies to this topic
HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
I've created Thread() function to make multithreading using AutoHotkey.dll much easier.

To use Thread() you will need AutoHotkey.dll and CreatePipe() function.
You need also to save AutoHotkey.dll in A_WorkingDir :!:

How to use

Read about Multithreading Consideration and Terminate Thread on MSDN

Download Zip

Many thanks to tinku99 for AutoHotkey.dll and Lexikos for How to: Run Dynamic Script... Through a Pipe!

Example:
#Persistent
Next("Create a new thread",0)
ThreadID:=Thread("MsgBox I am a new thread")

Next("Replace script in thread with new script")
Thread("Msgbox New script in same thread","newFile",ThreadID)

Next("Remove script, add a script and go to its label")
Thread("`n","newFile",ThreadID) ;remove script
Sleep, 100
;Add a Label into the thread without executing it
Thread("Label:`nMsgbox This is a label in thread: %A_ThisLabel%`nReturn","addFile",ThreadID)
Sleep, 500
;Jump to the label in the thread
Thread("Label","ahkLabel",ThreadID)

Next("Add a Goto label from current script and jump to it")
Thread("aLabel:aLabelEnd","addFile",ThreadID)
Sleep, 500
Thread("aLabel","ahkLabel",ThreadID)

Next("Replace script with a combination of script, label and function")
Thread("`n","newFile",ThreadID) ;remove script
;Combination of script, label and function
Thread("Msgbox AutoExecute section`nGoSub, aLabel`nfunc(""a"",""b"")`nfinished:=1`nReturn`naLabel:aLabelEnd`nfunc{}","newFile",ThreadID)
Loop
	If Sleep(100) && Thread("finished","ahkgetvar",ThreadID) ;get a variable from the thread
		break
Next("Run the function in thread from AutoHotkey.exe",0)
Thread("func(x,y)","ahkFunction",ThreadID)

Next("Get some build in parameters")
MsgBox % Thread("A_AhkPath","A_AhkPath",ThreadID)
			. "`n" Thread("A_ScriptFullPath","A_ScriptFullPath",ThreadID)
			. "`n" Thread("A_ScriptName","A_ScriptName",ThreadID)
			. "`n" Thread("A_TickCount","A_TickCount",ThreadID)
Next("ExitApp",0)
ExitApp
sleep(t){
	Sleep %t%
	Return 1
}

Next(text="",wait=3){
	WinWaitActive ahk_class #32770,,%wait%
	WinWaitClose ahk_class #32770,,%wait%
	MsgBox % "Click ok for next example:`n`n" text
	IfWinActive ahk_class #32770
		WinClose
}


aLabel:
MsgBox % "This is a label: " A_ThisLabel
Return
aLabelEnd:
Return

func(p1,p2){
	MsgBox % "func`n" p1 "`n" p2
}

Function Thread()
Thread(FileOrTextOrLabel,Params="",ThreadId=""){
	static
	local _thread,lib,h,#__PIPE_NAME_,#__PIPE_,#__PIPE_GA_,thread_,result,isfile,FileExtract_ToMem:="FileExtract_ToMem"
			,dllcall:="|A_AhkPath|A_AhkVersion|A_AppData|A_AutoTrim|A_BatchLines|A_Caret|A_ComSpec|A_ControlDelay|A_Cursor|A_DateTime"
	. "|A_DefaultMouseSpeed|A_Desktop|A_DetectHiddenText|A_DetectHiddenWindows|A_EndChar|A_EventInfo|A_ExitReason|A_FormatFloat"
	. "|A_FormatInteger|A_Gui|A_GuiControl|A_GuiEvent|A_IconFile|A_IconHidden|A_IconNumber|A_IconTip|A_IPAddress|A_IsAdmin"
	. "|A_IsCritical|A_IsPaused|A_IsSuspended|A_KeyDelay|A_Language|A_LastError|A_LineFile|A_LineNumber|A_LoopField|A_LoopFileAttrib"
	. "|A_LoopFileDir|A_LoopFileExt|A_LoopFileFullPath|A_LoopFileLongPath|A_LoopFileName|A_LoopFileShortName|A_LoopFileShortPath"
	. "|A_LoopFileSize|A_LoopFileTime|A_LoopIndex|A_LoopReadLine|A_LoopRegKey|A_LoopRegName|A_LoopRegSubKey|A_LoopRegTimeModified"
	. "|A_LoopRegType|A_MMM|A_MouseDelay|A_MyDocuments|A_Now|A_OSType|A_OSVersion|A_PriorHotkey|A_ProgramFiles|A_Programs"
	. "|A_ScreenWidth|A_ScriptDir|A_ScriptFullPath|A_ScriptName|A_Space|A_StartMenu|A_Startup|A_StringCaseSense|A_Temp|A_ThisFunc"
	. "|A_ThisHotkey|A_ThisLabel|A_ThisMenu|A_ThisMenuItem|A_ThisMenuItemPos|A_TickCount|A_TimeIdle|A_TimeIdlePhysical"
	. "|A_TimeSincePriorHotkey|A_TimeSinceThisHotkey|A_TitleMatchMode|A_TitleMatchModeSpeed|A_True|A_UserName|A_WinDelay|A_WinDir"
	. "|A_WorkingDir|newFile|addFile|ahkLabel|ahkFindFunc|ahkgetvar|ahkContinue|ahkTerminate|ahkFunction|ahkassign|Terminate|Suspend|Resume|Priority|ExitCode|"
	If (!_DllPath){
		CheckDir := A_WorkingDir . "\AutoHotkey*.dll`n" . A_ScriptDir . "\AutoHotkey*.dll`n" . SubStr(A_AhkPath,1,InStr(A_AhkPath,"\",1,0)) . "AutoHotkey*.dll`n" . SubStr(A_AhkPath,1,InStr(A_AhkPath,"\",1,0)) . "Lib\AutoHotkey*.dll`n" . A_MyDocuments . "\Lib\AutoHotkey*.dll"
		While (!FileExist(_DllPath ? _DllPath : (_DllPath:=A_WorkingDir . "\AutoHotkey.dll"))){
			Sleep, 50
			Loop,Parse,CheckDir,`n
			{
				Loop % A_LoopField
					FileMove,%A_LoopFileFullPath%,% (_DllPath:=RegExReplace(A_LoopField,"\*"))
				If (FileExist(_DllPath) and breaked:=1)
					break
			}
			If A_Index<21
				Continue
			MsgBox AutoHotkey.dll was not found
			ExitApp
		}
		StringReplace,_DllPath,_DllPath,\\,\,A
		DllCall("LoadLibrary","Str",_DllPath)
		Sleep,10
	}
	iscritical:=A_IsCritical
	Critical, On
	If (ThreadId<>""){
		If (ThreadId=0 || ThreadId="Main")
      _thread:=_DllPath
    else if ThreadId is not digit
    {
      _thread:=ThreadId
      DllCall("LoadLibrary","Str",_thread)
    }	else If ThreadId is digit
			_thread:=thread%ThreadId%
		else {
			_thread := "AutoHotkey_" A_TickCount . ".dll"
			WorkingDir:=A_WorkingDir
			SetWorkingDir % SubStr(_DllPath,1,InStr(_DllPath,"\",1,0)-1)
			FileMove, %_DllPath%, %_thread%
			lib := DllCall("LoadLibrary", "str", _thread)
			FileMove, %_thread%,%_DllPath%
			SetWorkingDir % WorkingDir
			Sleep, 10
		}
	} else {
		_thread := "AutoHotkey_" A_TickCount . ".dll"
		WorkingDir:=A_WorkingDir
		SetWorkingDir % SubStr(_DllPath,1,InStr(_DllPath,"\",1,0)-1)
		FileMove, %_DllPath%, %_thread%
		lib := DllCall("LoadLibrary", "str", _thread)
		FileMove, %_thread%,%_DllPath%
		SetWorkingDir % WorkingDir
		Sleep, 10
	}
	Critical, %iscritical%
	If !FileExist(FileOrTextOrLabel){
		StringReplace,FileOrTextOrLabel,FileOrTextOrLabel,`n,`r`n
		StringReplace,FileOrTextOrLabel,FileOrTextOrLabel,`r`r,`r
		If RegExMatch(FileOrTextOrLabel,"m)^[^:]+:[^:]+|[^\{}]+\{}$"){
			If !(fullscript){
				If (A_IsCompiled and !fullScript and IsFunc(FileExtract_ToMem)){
					pData:=0,DataSize:=0
					If (%FileExtract_ToMem%(">AUTOHOTKEY SCRIPT<", pData, DataSize) 
						|| %FileExtract_ToMem%(">AHF WITH ICON<", pData, DataSize)){
						VarSetCapacity(fullScript,DataSize)
						DllCall("lstrcpyn", "str", fullScript, "uint", pData, "int", DataSize+1)
						VarSetCapacity(fullScript,-1)
						StringReplace,fullScript,fullScript,`n,`r`n
						StringReplace,fullScript,fullScript,`r`r,`r
						fullScript .="`r`n"
					}
				} else if (!fullScript){
					FileRead,fullScript,%A_ScriptFullPath%
					StringReplace,fullScript,fullScript,`n,`r`n
					StringReplace,fullScript,fullScript,`r`r,`r
					fullScript .= "`r`n"
				}
			}
			Loop,Parse,FileOrTextOrLabel,`n,`r
			{
				If A_LoopField=
					Continue
				If A_Index=1
					FileOrTextOrLabel=
				If (RegExMatch(A_LoopField,"^[^:\s]+:[^:\s=]+$")){
					StringSplit,label,A_LoopField,:
					If (label0=2 and IsLabel(label1) and IsLabel(label2)){
						FileOrTextOrLabel .=SubStr(fullScript
							, ErrorLevel:=InStr(fullScript,"`r`n" label1 ":`r`n")
							, InStr(fullScript,"`r`n" label2 ":`r`n")-ErrorLevel) . "`r`n"
					}
				} else if RegExMatch(A_LoopField,"^[^\{}\s]+\{}$"){
					StringTrimRight,label,A_LoopField,2
					FileOrTextOrLabel .= SubStr(fullScript
						, h:=RegExMatch(fullScript,"i)\R" label "\([^)\R]*\)\R?\{")
						, RegExMatch(fullScript,"\R\s*}\s*\K\R",1,h)-h) . "`r`n"
				} else
					FileOrTextOrLabel .= A_LoopField "`r`n"
			}
		}
	} else isfile:=1
	If InStr(dllcall,"|" . Params . "|")
	{
    If Params=Terminate
			Return DllCall("TerminateThread","UInt",ThreadID,"Int",99999)
		else if Params=Suspend
			Return DllCall("SuspendThread","UInt",ThreadID)
		else if Params=Resume
			Return DllCall("ResumeThread","UInt",ThreadID)
		else if Params=GetExitCode
			Return DllCall("GetExitCodeThread","UInt",ThreadID,"UIntP",ErrorLevel)
		else if Params=Priority
			Return DllCall("SetThreadPriority","UInt",ThreadID,"Int",FileOrTextOrLabel)
		Loop,Parse,dllcall,|
			If (Params=A_LoopField)
				Params:=A_LoopField
		If (ThreadId<>"")
		{
			If (Params = "newFile"){
				If !(isfile){
					FileOrTextOrLabel:="#Persistent`n" . FileOrTextOrLabel
					result := DllCall(_thread . "\addFile", "str", CreatePipe(FileOrTextOrLabel,"AhkDll newFile @ " A_Hour ":" A_Min ":" A_Sec ":" A_MSec,0), "uchar", 1,"uchar" , 2, "Cdecl UInt")
					Return result
				} else
					Return DllCall(_thread . "\addFile", "str", FileOrTextOrLabel, "uchar", 1,"uchar" , 2, "Cdecl UInt")
			} else if (Params="addFile"){
				If !(isfile){
					FileOrTextOrLabel:="#Persistent`n" . FileOrTextOrLabel
					result := DllCall(_thread . "\addFile", "str", CreatePipe(FileOrTextOrLabel,"AhkDll addFile @ " A_Hour ":" A_Min ":" A_Sec ":" A_MSec,0), "uchar", 1,"uchar" , 1, "Cdecl UInt")
					Return result
				} else if FileExist(FileOrTextOrLabel)
					Return DllCall(_thread . "\addFile", "str", FileOrTextOrLabel, "uchar", 1,"uchar" , 1, "Cdecl UInt")
			} else if Params in A_AhkPath,A_AhkVersion,A_AppData,A_AutoTrim,A_BatchLines,A_Caret,A_ComSpec,A_ControlDelay
									,A_Cursor,A_DateTime,A_DefaultMouseSpeed,A_Desktop,A_DetectHiddenText,A_DetectHiddenWindows
									,A_EndChar,A_EventInfo,A_ExitReason,A_FormatFloat,A_FormatInteger,A_Gui,A_GuiControl,A_GuiEvent
									,A_IconFile,A_IconHidden,A_IconNumber,A_IconTip,A_IPAddress,A_IsAdmin,A_IsCritical,A_IsPaused
									,A_IsSuspended,A_KeyDelay,A_Language,A_LastError,A_LineFile,A_LineNumber,A_LoopField,A_LoopFileAttrib
									,A_LoopFileDir,A_LoopFileExt,A_LoopFileFullPath,A_LoopFileLongPath,A_LoopFileName,A_LoopFileShortName
									,A_LoopFileShortPath,A_LoopFileSize,A_LoopFileTime,A_LoopIndex,A_LoopReadLine,A_LoopRegKey
									,A_LoopRegName,A_LoopRegSubKey,A_LoopRegTimeModified,A_LoopRegType,A_MMM,A_MouseDelay,A_MyDocuments
									,A_Now,A_OSType,A_OSVersion,A_PriorHotkey,A_ProgramFiles,A_Programs,A_ScreenWidth,A_ScriptDir
									,A_ScriptFullPath,A_ScriptName,A_Space,A_StartMenu,A_Startup,A_StringCaseSense,A_Temp,A_ThisFunc
									,A_ThisHotkey,A_ThisLabel,A_ThisMenu,A_ThisMenuItem,A_ThisMenuItemPos,A_TickCount,A_TimeIdle
									,A_TimeIdlePhysical,A_TimeSincePriorHotkey,A_TimeSinceThisHotkey,A_TitleMatchMode
									,A_TitleMatchModeSpeed,A_True,A_UserName,A_WinDelay,A_WinDir,A_WorkingDir
			{
				Varsetcapacity(result,100000000)
				DllCall(_thread . "\EBIV" . SubStr(Params,2), "str", result,"str",Params, "Cdecl UInt")
				Return result
			} else if Params in ahkgetvar
			{
				Varsetcapacity(result,100000000)
				DllCall(_thread . "\ahkgetvar", "str", FileOrTextOrLabel, "str", result, "Cdecl uint")
				Return result
			} else if Params in ahkLabel,ahkFindFunc,ahkContinue,ahkTerminate
				Return DllCall(_thread . "\" . Params, "str", FileOrTextOrLabel, "Cdecl UInt")
			else if Params in ahkassign
      {
        If InStr(FileOrTextOrLabel,"="){
          label:=SubStr(FileOrTextOrLabel,InStr(FileOrTextOrLabel,"=")+1)
          FileOrTextOrLabel:=SubStr(FileOrTextOrLabel,1,InStr(FileOrTextOrLabel,"=")-1)
        }
				Return DllCall(_thread . "\" . Params, "str", FileOrTextOrLabel, "Str", label, "Cdecl UInt")
			} else if Params = ahkFunction
			{
				IfInString,FileOrTextOrLabel,(
				{
					label:=SubStr(FileOrTextOrLabel,1,InStr(FileOrTextOrLabel,"(")-1)
					StringReplace,FileOrTextOrLabel,FileOrTextOrLabel,(
					StringReplace,FileOrTextOrLabel,FileOrTextOrLabel,)
					StringReplace,FileOrTextOrLabel,FileOrTextOrLabel,% label
					IfInString,FileOrTextOrLabel,`,
						StringSplit,label,FileOrTextOrLabel,`,
					else
						label1:=FileOrTextOrLabel,label2:=""
					result:=DllCall(_thread . "\ahkFunction", "str", label,"str",label1,"str",label2, "Cdecl UInt")
          DllCall("Sleep","Int",50)
          Return result
				} else {
          result:=DllCall(_thread . "\ahkFunction", "str", FileOrTextOrLabel,"Cdecl UInt")
          DllCall("Sleep","Int",50)
					Return result
        }
			}
		}
	} else {
			If !(isfile){
				FileOrTextOrLabel:="#Persistent`n" . FileOrTextOrLabel
				thread_ := DllCall(_thread . "\ahkdll", "str", CreatePipe(FileOrTextOrLabel,"AhkDll @ " A_Hour ":" A_Min ":" A_Sec ":" A_MSec,1), "str", _thread, "str", Params, "Cdecl Int")
			} else {
				thread_ := DllCall(_thread . "\ahkdll", "str", FileOrTextOrLabel, "str", _thread, "str", Params, "Cdecl Int")
      }
		DllCall("Sleep","Int",50)
	}
	thread%thread_%:=_thread
	lib%thread_% := lib
	Return thread_
}


tinku99
  • Members
  • 560 posts
  • Last active: Feb 08 2015 12:54 AM
  • Joined: 03 Aug 2007
Hey,
I tried the example, Nice !
I will test new releases of the dll against it and give you a heads up if there is a change to the API.

I am surprised you were able to get TerminateThread to work without crashing. Have you tried stressing this feature? Create and terminate a bunch of threads that do something boring.
Is there no system resource / memory leak?

HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008

Hey,
I tried the example, Nice !
I will test new releases of the dll against it and give you a heads up if there is a change to the API.

I am surprised you were able to get TerminateThread to work without crashing. Have you tried stressing this feature? Create and terminate a bunch of threads that do something boring.
Is there no system resource / memory leak?


Thanks tinku99.

Terminating the thread using TerminateThread is no problem, but it is not possible to free the memory, or at least you cannot start a new thread after that (unless you reload all freed dlls again).

Stressing looks fine up to ~300 threads, but the memory grows quite much.
Loop 100
{
	ToolTip % A_Index
	id:=Thread("Gui:GuiEnd")
	Thread(id,"K")
}
ExitApp

Gui:
Gui,Add,Button,,Test
Gui, Show,,test
Return
GuiEnd:
Return

Escape::ExitApp
DllCall("SetProcessWorkingSetSize",...) can help to keep memory low, but it crashes already after loading around ~100 threads.
Loop 100
{
	ToolTip % A_Index
	id:=Thread("Gui:GuiEnd")
	Thread(id,"K")
	EmptyMem()
}
ExitApp

Gui:
Gui,Add,Button,,Test
Gui, Show,,test
Return
GuiEnd:
Return

Escape::ExitApp

EmptyMem(PID="AHK Rocks"){
  pid:=(pid="AHK Rocks") ? DllCall("GetCurrentProcessId") : pid
  h:=DllCall("OpenProcess", "UInt", 0x001F0FFF, "Int", 0, "Int", pid)
  DllCall("SetProcessWorkingSetSize", "UInt", h, "Int", -1, "Int", -1)
  DllCall("CloseHandle", "Int", h)
}


telppa
  • Guests
  • Last active:
  • Joined: --
it's cool
i love it

HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
I like it too, finally we have multithreading in AutoHotkey :D

- All AutoHotkey.dll functions are not case sensitive anymore :)
- AutoHotkey.dll can be now saved in A_WorkingDir, A_ScriptDir, A_AhkPath, Lib or UserLib (My_Documents\Lib)



HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008

You can now use Thread() to control any dll or AutoHotkey.exe in same process.
- ThreadId can be the id of the thread returned when Thread() is launched or the name of the file you want to control
- - to control/access AutoHotkey.exe you can use A_AhkPath
- - to control/access AutoHotkey.dll itself pass A_ScriptOptions
- to control/access the a dll from another dll use this:

Thread("thread=test","ahkassign",A_AhkPath) ;same as thread=test but using exported function ahkassign
MsgBox % thread ;so you see the change
Loop 2 ;Start 2 threads
	thread%A_Index%:=Thread("DLL" A_Index ":DLL#" A_Index "`nThread:ThreadEnd")
Sleep, 500
Loop 2
	Thread("DllLabel" A_Index,"ahklabel",thread%A_Index%)
MsgBox press ok to exit
Return


DLL1:
Thread("dll1=" . A_ScriptOptions,"ahkassign",A_AhkPath) ;will be launced by 1st thread
Return
DllLabel1:
dll2:=Thread("dll2","ahkgetvar",A_AhkPath)
MsgBox Launch label in second thread
Thread("Launch2","ahklabel",dll2)
Return
Launch1:
MsgBox First Dll
Return
DLL#1:
Return


DLL2:
Thread("dll2=" . A_ScriptOptions,"ahkassign",A_AhkPath) ;will be launced by 2nd thread
Return
DllLabel2:
dll1:=Thread("dll1","ahkgetvar",A_AhkPath)
MsgBox Launch label in first thread
Thread("Launch1","ahklabel",dll1)
Return
Launch2:
MsgBox Second Dll
Return
DLL#2:
Return

Thread:
If 0
 Return
#include Thread.dll
ThreadEnd:
Return
- You can also use CriticalSection() to access a variable from multiple threads without crash :)
From my testing, the only way to have definitely no crash is to use a separate thread (dll) for your variables and objects (not yet implemented in AutoHotkey.dll).
Then create alias in any other thread and main exe.
#include Thread.dll

SetBatchLines,-1 ;Max performance
;Create CriticalSection and return pointer to use with Lock() and UnLock()
_C:=CriticalSection()

;Get pointer to Variables to use in Alias()
pVar:=getVar(var)
pString:=getVar(String)
Loop 5
{
	ThreadScript=
	(
	  ;create Alias variables, so you can use same variables in exe and dlls
	  SetBatchLines,-1
	  Alias(var,%pVar% + 0)
	  Alias(string,%pString% + 0)
	  Loop
	  {
		 Lock(%_C%)
		 var:=A_ScriptOptions
		 string.="newstring"
		 UnLock(%_C%)
	  }
	)
	Thread(ThreadScript "`nCriSec:CriSecEnd")
}
Loop
  {
    Lock(_C)
    ToolTip % "length of string: " StrLen(string) "`nCurrent Thread: " var
    UnLock(_C)
  }
Esc::
CriticalSection("") ;Delete all CriticalSections
ExitApp

CriSec:
Return
#Include CriticalSection.ahk
CriSecEnd:
Return


Enjoy AutoHotkey ;)

iweindesmedt
  • Members
  • 25 posts
  • Last active: Mar 08 2011 09:06 PM
  • Joined: 23 Nov 2010
Nice app. Thumbs up.

There are already tons of rip applications like this so I feel it's a bit of 'another of those', but anyways, you must have learned alot. Again, nice work.

Btw I LOLd @ AskJolene :lol:
Don't teach a pig to sing, it wastes your time and it annoys the pig.

HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
Thanks iweindesmedt.

It is true, using AHK I have learned a lot and want to learn more :D

AhkDllThread and AhkDllObject are easier to use and more faster, though they need AHK_L++ ;)

kenn
  • Members
  • 407 posts
  • Last active: Jan 14 2015 08:16 PM
  • Joined: 11 Oct 2010

You can now use Thread() to control #include Thread.dll

sorry for my noob question but where is thread.dll :oops: