#include-once
#include <Memory.au3>

Global $AUTOITTHREAD = False
; *
#OnAutoItStartRegister "__AT_Init"

; #INDEX# =======================================================================================================================
; Title .........: AutThread - Concept!
; AutoIt Version : >=3.3.8.1
; Language ......: Russian
; Description ...: Псевдо-потоки в AutoIt!
; ===============================================================================================================================

; #CURRENT# =====================================================================================================================
;_AutThread_Create
;_AutThread_Kill
;_AutThread_IsAlive
;_AutThread_ParentThreadPID
;_AutThread_SelfIsThread
;_AutThread_ErrorCallbackRegister
; ===============================================================================================================================

; #INTERNAL_USE_ONLY#============================================================================================================
;__AT_Init
;__AT_Main
;__AT_VarType
;__AT_ReinterpretCast
;__AT_TagSize
;__AT_OpenProcess
;__AT_ReadProcessMemory
;__AT_WriteProcessMemory
;__AT_CloseHandle
;__AT_OnExit
; ===============================================================================================================================




; #FUNCTION# =================================================================================================
; Name...........: _AutThread_Create
; Description ...: Запускает функцию параллельно работе основного скрипта
; Syntax.........: _AutThread_Create( $sFunc [, $iRetSize [, $sVarRet [, $aArgs = 0 [, $sCmdLine = "" ]]]] )
; Parameters ....: $sFunc - Имя функции
;    			   $iRetSize - Сколько выделить памяти под ответ (в байтах) [ Optional ]
;    			   $sVarRet - Имя переменной в которую поместится Return (Global only) [ Optional ]
;    			   $aArgs - Массив аргументов которые передадутся функции [ Optional ]
;    			   $sCmdLine - CMDLINE [ Optional ]
; Return values .: ( ThreadIndex ) Or ( False and set @Error )
; Author ........: Firex
; Remarks .......:
; ============================================================================================================
Func _AutThread_Create( $sFunc, $iRetSize = 0, $sVarRet = "", $aArgs = 0, $sCmdLine = "" )
	Local $iAutThread, $__aArgs, $Idx, $Jix, $hMemory, $tagAutThread, $tAutThread, $iTmp, $iFunc, $iErr = 0, $iArgs = 0
	; ---
	$iFunc = StringLen( $sFunc )
	If Not $iFunc Then _
		Return SetError( 1, 0, 0 )

	$tagAutThread = $__tagAutThreadInfo & "char[" & $iFunc & "]; "
	; ---
	If IsArray( $aArgs ) And UBound( $aArgs ) = $aArgs[0] + 1 Then
		Dim $__aArgs[ $aArgs[0] + 1 ][3]
		For $Idx = 1 To $aArgs[0] Step 1
			$tagAutThread &= $__tagAutThreadBody
			$__aArgs[$Idx][0] = __AT_VarType( VarGetType( $aArgs[$Idx] ) )
			If $aArgs[$Idx] == "" Or IsArray( $aArgs[$Idx] ) Then
				$__aArgs[$Idx][2] = Binary( '0x00' )
				$__aArgs[$Idx][1] = 1
			Else
				$__aArgs[$Idx][2] = StringToBinary( String( $aArgs[$Idx] ) )
				$__aArgs[$Idx][1] = BinaryLen( $__aArgs[$Idx][2] )
				If $__aArgs[$Idx][1] > 16581375 Then _
					$__aArgs[$Idx][1] = 16581375 ;~byte iArg[3]; in $__tagAutThreadBody

				$tagAutThread &= "byte[" & $__aArgs[$Idx][1] & "]; "
			EndIf
		Next
		$iArgs = $aArgs[0]
	EndIf
	; ---
	$iTmp = __AT_TagSize( $tagAutThread )
	If $iTmp - $__tagATI_Size < $iRetSize Then _
		$iTmp = $iRetSize

	$hMemory = _MemGlobalAlloc( $iTmp, 0x0040 ) ;$GPTR
	If $hMemory <> 0 Then
		$tAutThread = DllStructCreate( $tagAutThread, $hMemory )
		If Not @Error Then
			DllStructSetData( $tAutThread, "iRet", False )
			DllStructSetData( $tAutThread, "iArgs", $iArgs )
			DllStructSetData( $tAutThread, "iFunc", $iFunc )
			DllStructSetData( $tAutThread, "iBuf", $iTmp )
			DllStructSetData( $tAutThread, 5, $sFunc )
			For $Idx = 1 To $iArgs Step 1
				$iTmp = 6 + ( ( $Idx - 1 ) * 3 )
				For $Jix = 0 To 2 Step 1
					DllStructSetData( $tAutThread, $iTmp + $Jix, $__aArgs[$Idx][$Jix] )
				Next
			Next
		Else
			$iErr = 2
		EndIf
	Else
		$iErr = 1
	EndIf
	; ---
	If Not $iErr Then
		$iAutThread = Run( @ScriptFullPath & ' /AutThreadInfo="' & @AutoItPID & ',' & Int( $hMemory ) & '" ' & $sCmdLine, @ScriptDir )
		If Not $iAutThread Then _
			$iErr = 3
	EndIf
	If Not $iErr Then
		$Idx = $__aAutThreads[0][0] + 1

		ReDim $__aAutThreads[ $Idx + 1 ][4]
		$__aAutThreads[0][0] = $Idx
		$__aAutThreads[$Idx][0] = $hMemory
		$__aAutThreads[$Idx][1] = $tAutThread
		$__aAutThreads[$Idx][2] = $iAutThread
		$__aAutThreads[$Idx][3] = $sVarRet

		$iAutThread = $Idx
	Else
		_MemGlobalFree( $hMemory )
		$iAutThread = 0
	EndIf
	; ---
	Return SetError( $iErr + 1, 0, $iAutThread )
EndFunc

; #FUNCTION# =================================================================================================
; Name...........: _AutThread_Kill
; Description ...: Прекращает выполнение параллельной скрипту функции
; Syntax.........: _AutThread_Kill( $iAutThread )
; Parameters ....: $iAutThread - ThreadIndex
; Return values .: ( True ) Or ( False And set @Error )
; Author ........: Firex
; Remarks .......:
; ============================================================================================================
Func _AutThread_Kill( $iAutThread )
	If $__aAutThreads[0][0] < $iAutThread Then _
		Return SetError( 1, 0, False )
	; ---
	If $__aAutThreads[$iAutThread][0] Then
		ProcessClose( $__aAutThreads[$iAutThread][2] )
		_MemGlobalFree( $__aAutThreads[$iAutThread][0] )
		$__aAutThreads[$iAutThread][0] = 0
		$__aAutThreads[$iAutThread][1] = 0
		$__aAutThreads[$iAutThread][2] = 0
		$__aAutThreads[$iAutThread][3] = 0
	Else
		Return SetError( 2, 0, False )
	EndIf
	; ---
	Return True
EndFunc

; #FUNCTION# =================================================================================================
; Name...........: _AutThread_IsAlive
; Description ...: Проверяет существует ли указанный поток
; Syntax.........: _AutThread_IsAlive( $iAutThread )
; Parameters ....: $iAutThread - ThreadIndex
; Return values .: ( True ) Or ( False Or set @Error )
; Author ........: Firex
; Remarks .......:
; ============================================================================================================
Func _AutThread_IsAlive( $iAutThread )
	If $__aAutThreads[0][0] < $iAutThread Then _
		Return SetError( 1, 0, False )

	Return $__aAutThreads[$iAutThread][0] <> 0
EndFunc

; #FUNCTION# =================================================================================================
; Name...........: _AutThread_ParentThreadPID
; Description ...: Возвращает PID родительского процесса ( если таковой имеется ).
; Syntax.........: _AutThread_ParentThreadPID()
; Parameters ....:
; Return values .: ( ParentPID ) Or ( False And set @Error )
; Author ........: Firex
; Remarks .......:
; ============================================================================================================
Func _AutThread_ParentThreadPID()
	Local $iPid = Eval( "__autThread_ParentScriptPID" )
	If Not $iPid Then _
		$iPid = SetError( 1, 0, 0 )

	Return $iPid
EndFunc

; #FUNCTION# =================================================================================================
; Name...........: _AutThread_SelfIsThread
; Description ...: Проверяет является ли текущее выполнение потоковым
; Syntax.........: _AutThread_SelfIsThread()
; Parameters ....:
; Return values .: ( True ) Or ( False )
; Author ........: Firex
; Remarks .......:
; ============================================================================================================
Func _AutThread_SelfIsThread()
	Return Not IsDeclared( "AUTOITTHREAD" )
EndFunc

; #FUNCTION# =================================================================================================
; Name...........: _AutThread_ErrorCallbackRegister
; Description ...: Регистрирует функцию ( будет вызвана при падении потока ).
; Syntax.........: _AutThread_ErrorCallbackRegister( [ $sFunc ] )
; Parameters ....: $sFunc - Имя функции
; Return values .:
; Author ........: Firex
; Remarks .......: Функция должна содержать один аргумент, он получит ThreadIndex.
; ============================================================================================================
Func _AutThread_ErrorCallbackRegister( $sFunc = "" )
	$__sAutThread_OnErrCallback = $sFunc
EndFunc


; # INTERNAL USE ONLY
; =======================================================
Func __AT_Init()
	Local $Idx, $hProcess, $_aAutThreadInfo, $tBufThread, $pBufThread, $tAutThread
	Local $iArgs, $iLen, $sType, $sFunc, $aCallArg, $tBuf, $pBuf, $iBuf
	Local $vCallResult, $vCallReport[3]

	Global $__aAutThreads[1][4] = [ [ 0 ] ], $__sAutThread_OnErrCallback = ""
	Global Const $__tagAutThreadBody = "byte vType; byte iArg[3]; ", $__tagATB_Size = __AT_TagSize( $__tagAutThreadBody )
	Global Const $__tagAutThreadInfo = "byte iRet; byte iArgs; ushort iFunc; dword iBuf; ", $__tagATI_Size = __AT_TagSize( $__tagAutThreadInfo )
	OnAutoItExitRegister( "__AT_OnExit" )
	AdlibRegister( "__AT_Main", 450 )
	; ---
	$_aAutThreadInfo = StringRegExp( $CmdLineRaw, '^.*/(?i)AutThreadInfo="(\d+),(\d+)".*$', 3 )
	If UBound( $_aAutThreadInfo ) <> 2 Then _
		Return

	$_aAutThreadInfo[0] = Int( $_aAutThreadInfo[0] )
	$_aAutThreadInfo[1] = Ptr( $_aAutThreadInfo[1] )
	; ---
	Global Const $__autThread_ParentScriptPID = $_aAutThreadInfo[0]
	; ---
	$hProcess = __AT_OpenProcess( $_aAutThreadInfo[0] )
	If $hProcess Then
		$tAutThread = DllStructCreate( $__tagAutThreadInfo )
		__AT_ReadProcessMemory( $hProcess, $_aAutThreadInfo[1], $tAutThread )
		If Not @Error Then
			$iArgs = DllStructGetData( $tAutThread, "iArgs" )
			$iLen = DllStructGetData( $tAutThread, "iFunc" )
			$iBuf = DllStructGetData( $tAutThread, "iBuf" )
			If $iBuf - $iLen > 0 And $iArgs Then _
				Dim $aCallArg[ $iArgs + 1 ] = [ "CallArgArray" ]

			$tBufThread = DllStructCreate( "char[" & $iLen & "]; byte[" & $iBuf - $iLen & "]; " )
			$pBufThread = $_aAutThreadInfo[1] + DllStructGetSize( $tAutThread )
			__AT_ReadProcessMemory( $hProcess, $pBufThread, $tBufThread )
			If Not @Error Then
				$sFunc = DllStructGetData( $tBufThread, 1 )
				$pBuf = DllStructGetPtr( $tBufThread ) + $iLen
				; ~~~
				For $Idx = 1 To $iArgs Step 1
					$tBuf = DllStructCreate( $__tagAutThreadBody, $pBuf )
					$sType = DllStructGetData( $tBuf, "vType" )
					$iLen = Int( DllStructGetData( $tBuf, "iArg" ) )
					; *
					$pBuf += $__tagATB_Size
					$tBuf = DllStructCreate( "byte[" & $iLen & "]", $pBuf )
					$aCallArg[$Idx] = BinaryToString( DllStructGetData( $tBuf, 1 ) )
					__AT_ReinterpretCast( $sType, $aCallArg[$Idx] )
					$pBuf += $iLen
				Next
				Dim $tBufThread = "", $tBuf = "" ;Free memory
				; ---
				$vCallResult = Call( $sFunc, $aCallArg )
				$vCallReport[0] = @Error
				$vCallReport[1] = @Extended
				$vCallReport[2] = __AT_VarType( VarGetType( $vCallResult ), 1 )
				DllStructSetData( $tAutThread, 1, $vCallReport[2] )
				DllStructSetData( $tAutThread, 2, $vCallReport[0] )
				DllStructSetData( $tAutThread, 3, $vCallReport[1] )
				; ---
				$vCallResult = StringToBinary( $vCallResult )
				$iLen = BinaryLen( $vCallResult )
				If $iLen > $iBuf Then _
					$iLen = $iBuf
				DllStructSetData( $tAutThread, 4, $iLen )

				$tBuf = DllStructCreate( "byte[" & $iLen & "]; " )
				DllStructSetData( $tBuf, 1, $vCallResult )

				__AT_WriteProcessMemory( $hProcess, $pBufThread, $tBuf )
				__AT_WriteProcessMemory( $hProcess, $_aAutThreadInfo[1], $tAutThread )
				__AT_CloseHandle( $hProcess )
				; *
				Exit 148 ;Thread success exit!
			EndIf
		EndIf
	EndIf
	; ---
	Exit ( 148 - @Error ) ;Error
EndFunc

Func __AT_Main()
	Local $Idx, $Jix, $iRetType, $iPid
	; ---
	For $Idx = 1 To $__aAutThreads[0][0] Step 1
		If $__aAutThreads[$Idx][0] Then
			$iRetType = DllStructGetData( $__aAutThreads[$Idx][1], 1 )
			If $iRetType Then
				Local $tRet, $iErr, $iExt, $iBuf, $vRet
				; ~
				$iErr = DllStructGetData( $__aAutThreads[$Idx][1], 2 )
				$iExt = DllStructGetData( $__aAutThreads[$Idx][1], 3 )
				$iBuf = DllStructGetData( $__aAutThreads[$Idx][1], 4 )
				If $iBuf Then
					$tRet = DllStructCreate( "byte[" & $iBuf & "]; ", $__aAutThreads[$Idx][0] + $__tagATI_Size )
					$vRet = BinaryToString( DllStructGetData( $tRet, 1 ) )
					__AT_ReinterpretCast( $iRetType, $vRet )
				Else
					$vRet = ""
				EndIf
				; ---
				If $__aAutThreads[$Idx][3] Then
					Assign( $__aAutThreads[$Idx][3], $vRet, 2 )
					Assign( $__aAutThreads[$Idx][3] & "_err", $iErr, 2 )
					Assign( $__aAutThreads[$Idx][3] & "_ext", $iExt, 2 )
				EndIf
			ElseIf Not ProcessExists( $__aAutThreads[$Idx][2] ) Then
				Call( $__sAutThread_OnErrCallback, $Idx )
			Else
				ContinueLoop
			EndIf
			; ---
			_AutThread_Kill( $Idx )
		EndIf
	Next
	; ---
	$iPid = _AutThread_ParentThreadPID()
	If $iPid And Not ProcessExists( $iPid ) Then _
		Exit 149
EndFunc

Func __AT_VarType( $vType, $iRet = 1 )
	Local $aTypes[10][2] = [ [ 9, 1 ], [ 10, "Array" ], [ 11, "Int32" ], [ 12, "Int64" ], [ 13, "Binary" ], _
		[ 14, "Bool" ], [ 15, "Ptr" ], [ 16, "Double" ], [ 17, "String" ], [ 18, "Keyword" ] ], $Idx, $Jix, $vRet = 0
	; ---
	For $Idx = 1 To $aTypes[0][0] Step 1
		For $Jix = 0 To $aTypes[0][1] Step 1
			If $vType = $aTypes[$Idx][$Jix] Then
				$vRet = $aTypes[$Idx][($Jix-1)*-1]
				If $Jix = $iRet - 1 Then _
					$vRet = $vType

				ExitLoop 2
			EndIf
		Next
	Next
	; ---
	Return $vRet
EndFunc

Func __AT_ReinterpretCast( $sType, ByRef $vVar )
	Switch __AT_VarType( $sType, 2 )
		Case "Array"
			$vVar = ""
		Case "Int32", "Int64"
			$vVar = Int( $vVar )
		Case "Binary"
			If StringLeft( $vVar, 2 ) <> "0x" Then _
				$vVar = "0x" & String( $vVar )
			$vVar = Binary( $vVar )
		Case "Bool"
			Switch $vVar
				Case "True"
					$vVar = True
				Case "False"
					$vVar = False
				Case Else
					$vVar = ( Number( $vVar ) >= 1 )
			EndSwitch
		Case "Ptr"
			$vVar = Ptr( Int( $vVar ) )
		Case "Double"
			$vVar = Round( $vVar, 16 )
		Case "String"
			$vVar = String( $vVar )
		Case "Keyword"
			$vVar = Default
		Case Else
			Return SetError( 1 )
	EndSwitch
EndFunc

Func __AT_TagSize( $sTagStruct )
	;Local $tStruct = DllStructCreate( $sTagStruct )
	; ---
	;If @Error Then _
	;	Return SetError( @Error, 0, 0 )
	; ---
	;Return DllStructGetSize( $tStruct )
	Local $aStruct, $iSize, $iTypeSize, $Idx
	; ---
	$aStruct = StringRegExp( $sTagStruct, '\040*([\w\*]+)[\s\w]*\[?((?<=\[)\d*|)', 3 )
	For $Idx = 0 To UBound( $aStruct ) - 1 Step 2
		Switch StringLower( $aStruct[$Idx] )
			Case "byte", "char", "boolean"
				$iTypeSize = 1
			Case "wchar", "short", "ushort", "word"
				$iTypeSize = 2
			Case "int", "long", "bool", "uint", "ulong", "dword", "hwnd", "handle", "float"
				$iTypeSize = 4
			Case "int64", "uint64", "double"
				$iTypeSize = 8
			Case "ptr", "int*", "long*", "uint*", "ulong*", "dword*"
				If @AutoItX64 Then
					$iTypeSize = 8
				Else
					$iTypeSize = 4
				EndIf
			Case Else
				$iTypeSize = 0
		EndSwitch
		If $aStruct[$Idx+1] = "" Then _
			$aStruct[$Idx+1] = 1

		$iSize += $iTypeSize * $aStruct[$Idx+1]
	Next
	; ---
	Return $iSize
EndFunc

Func __AT_OpenProcess( $iProcessID )
	Local $aResult = DllCall( "kernel32.dll", "handle", "OpenProcess", "dword", 0x1F0FFF, "bool", 0, "dword", $iProcessID )
	If @error Or Not $aResult[0] Then Return SetError(@error+1, @extended, 0)

	Return $aResult[0]
EndFunc   ;==>_WinAPI_OpenProcess

Func __AT_ReadProcessMemory( $hProcess, $pBaseAddress, $tBuffer )
	Local $aResult = DllCall("kernel32.dll", "bool", "ReadProcessMemory", "handle", $hProcess, _
		"ptr", $pBaseAddress, "ptr", DllStructGetPtr( $tBuffer ), "ulong_ptr", DllStructGetSize( $tBuffer ), "ulong_ptr*", 0)
	If @error Then Return SetError(@error, @extended, False)
	Return $aResult[0]
EndFunc   ;==>_WinAPI_ReadProcessMemory

Func __AT_WriteProcessMemory( $hProcess, $pBaseAddress, $tBuffer )
	Local $aResult = DllCall("kernel32.dll", "bool", "WriteProcessMemory", "handle", $hProcess, "ptr", $pBaseAddress, "ptr", _
			DllStructGetPtr( $tBuffer ), "ulong_ptr", DllStructGetSize( $tBuffer ), "ulong_ptr*", 0 )
	If @error Then Return SetError(@error, @extended, False)
	Return $aResult[0]
EndFunc   ;==>_WinAPI_WriteProcessMemory

Func __AT_CloseHandle( $hObject )
	Local $aResult = DllCall("kernel32.dll", "bool", "CloseHandle", "handle", $hObject)
	If @error Then Return SetError(@error, @extended, False)
	Return $aResult[0]
EndFunc   ;==>_WinAPI_CloseHandle

Func __AT_OnExit()
	For $Idx = 1 To $__aAutThreads[0][0] Step 1
		_AutThread_Kill( $Idx )
	Next
EndFunc