Что нового

Непредсказуемое поведение RunWait

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
tech-gs сказал(а):
Если программа сама по себе массивная и запускается долго, то будет большая пауза перед выполнением второй команды, связанная с запуском второй копии программы.

Не будет.

tech-gs сказал(а):
чтобы визуально избавиться от cmd-окон, но видеть результаты их работы?

Код:
; http://autoit-script.ru/index.php?topic=3768.0

#NoTrayIcon

#Include <Constants.au3>
#Include <EditConstants.au3>
#Include <GUIConstantsEx.au3>
#Include <GUIEdit.au3>
#Include <WindowsConstants.au3>
#Include <WinAPI.au3>

Opt('WinTitleMatchMode', 3)
Opt('WinWaitDelay', 0)

If ($CmdLine[0] = 3) And (StringInStr($CmdLine[1], '/runwait:') = 1) And (StringInStr($CmdLine[2], '/id:') = 1) And (StringInStr($CmdLine[3], '/handle:') = 1) Then
	$ExitCode = -1
	$hWnd = Ptr(StringTrimLeft($CmdLine[3], 8))
	$ID = Int(StringTrimLeft($CmdLine[2], 4))
	$PID = Run(StringTrimLeft($CmdLine[1], 9), '', @SW_HIDE, $STDERR_MERGED)
	If $PID Then
		Switch @OSVersion
			Case 'WIN_2000', 'WIN_2003', 'WIN_XP', 'WIN_XPe'
				$hProcess = _WinAPI_OpenProcess(0x0400, 0, $PID) ; PROCESS_QUERY_INFORMATION
			Case Else ; Vista/7
				$hProcess = _WinAPI_OpenProcess(0x1000, 0, $PID) ; PROCESS_QUERY_LIMITED_INFORMATION
		EndSwitch
		While 1
			$Data = StdoutRead($PID)
			If @error Then
				ExitLoop
			EndIf
			If @extended Then
				If Not _SendMsg($hWnd, _WinAPI_OemToChar($Data), $ID) Then
					Exit
				EndIf
			EndIf
		WEnd
		If $hProcess Then
			$ExitCode = _WinAPI_GetExitCodeProcess($hProcess)
			If (@error) Or ($ExitCode = 259) Then
				$ExitCode = -1
				If ProcessExists($PID) Then
					ProcessClose($PID)
				EndIf
			EndIf
			_WinAPI_CloseHandle($hProcess)
		EndIf
	EndIf
	_SendMsg($hWnd, $ExitCode, $ID)
	Exit
EndIf

; GUI
$hForm = GUICreate('MyGUI', 573, 642)

; Бонус :-)
InetGet('http://yashied.narod2.ru/ProjectFiles/Miscellaneous/Dark_Archon_Black.gif', @TempDir & '\Dark_Archon_Black.gif')
If Not @error Then
	$hPic = GUICtrlCreatePic(@TempDir & '\Dark_Archon_Black.gif', 222, 497, 128, 128)
	GUICtrlSetCursor(-1, 0)
Else
	$hPic = 0
EndIf

; Run 1
GUICtrlCreateLabel('RunWait(', 20, 20, 48, 14)
$Input1 = GUICtrlCreateInput('cmd.exe /c dir "' & @ScriptDir & '"', 69, 17, 320, 19)
GUICtrlCreateLabel(')=', 393, 20, 11, 14)
$Input2 = GUICtrlCreateInput('', 406, 17, 77, 19)
GUICtrlSetState(-1, $GUI_DISABLE)
$Button1 = GUICtrlCreateButton('Run', 494, 14, 60, 25)
$Edit1 = GUICtrlCreateEdit('', 20, 53, 533, 187, BitOR($ES_AUTOVSCROLL, $ES_READONLY, $ES_WANTRETURN, $WS_VSCROLL))
GUICtrlSetFont(-1, 9, 400, 0, 'Courier New')
GUICtrlSetBkColor(-1, 0)
GUICtrlSetColor(-1, 0xFF0000)
_GUICtrlEdit_SetLimitText(-1, 8 * 1024 * 1024) ; 8 MB (ANSI)
$Dummy1 = GUICtrlCreateDummy()

; Run 2
GUICtrlCreateLabel('RunWait(', 20, 260, 48, 14)
$Input3 = GUICtrlCreateInput('ping.exe ' & TCPNameToIP('autoit-script.ru'), 69, 257, 320, 19)
GUICtrlCreateLabel(')=', 393, 260, 11, 14)
$Input4 = GUICtrlCreateInput('', 406, 257, 77, 19)
GUICtrlSetState(-1, $GUI_DISABLE)
$Button2 = GUICtrlCreateButton('Run', 494, 254, 60, 25)
$Edit2 = GUICtrlCreateEdit('', 20, 293, 533, 187, BitOR($ES_AUTOVSCROLL, $ES_READONLY, $ES_WANTRETURN, $WS_VSCROLL))
GUICtrlSetFont(-1, 9, 400, 0, 'Courier New')
GUICtrlSetBkColor(-1, 0)
GUICtrlSetColor(-1, 0x00D000)
_GUICtrlEdit_SetLimitText(-1, 8 * 1024 * 1024) ; 8 MB (ANSI)
$Dummy2 = GUICtrlCreateDummy()

GUIRegisterMsg($WM_COPYDATA, 'WM_COPYDATA')
GUISetState()

$Count = 0

While 1
	Switch GUIGetMsg()
		Case 0
			ContinueLoop
		Case $GUI_EVENT_CLOSE
			ExitLoop
		Case $hPic
			ShellExecute('http://autoit-script.ru/index.php?topic=3768.0')
		Case $Button1
			If @compiled Then
				Run(@ScriptFullPath & '" /runwait:' & StringReplace(GUICtrlRead($Input1), '"', '""') & '" ' & '/id:1')
			Else
				Run(@AutoItExe & ' "' & @ScriptFullPath & '" ' & '"/runwait:' & StringReplace(GUICtrlRead($Input1), '"', '""') & '" ' & '/id:1 /handle:' & Number($hForm))
			EndIf
			$Count += 1
			GUISetCursor(1, 1)
			GUICtrlSetState($Button1, $GUI_DISABLE)
			GUICtrlSetState($Input1, $GUI_DISABLE)
			GUICtrlSetData($Input2, 'WAIT...')
			GUICtrlSetData($Edit1, '')
		Case $Button2
			If @compiled Then
				Run(@ScriptFullPath & '" /runwait:' & StringReplace(GUICtrlRead($Input3), '"', '""') & '" ' & '/id:2')
			Else
				Run(@AutoItExe & ' "' & @ScriptFullPath & '" ' & '"/runwait:' & StringReplace(GUICtrlRead($Input3), '"', '""') & '" ' & '/id:2 /handle:' & Number($hForm))
			EndIf
			$Count += 1
			GUISetCursor(1, 1)
			GUICtrlSetState($Button2, $GUI_DISABLE)
			GUICtrlSetState($Input3, $GUI_DISABLE)
			GUICtrlSetData($Input4, 'WAIT...')
			GUICtrlSetData($Edit2, '')
		Case $Dummy1 ; Приходит от WM_COPYDATA при завершении программы с ID = 1 ("/id:1")
			$ExitCode = GUICtrlRead($Dummy1)
			If $ExitCode = -1 Then
				$ExitCode = 'ERROR'
			EndIf
			GUICtrlSetData($Input2, $ExitCode)
			GUICtrlSetState($Button1, $GUI_ENABLE)
			GUICtrlSetState($Input1, $GUI_ENABLE)
			$Count -= 1
			If Not $Count Then
				GUISetCursor(2, 0)
			EndIf
		Case $Dummy2 ; Приходит от WM_COPYDATA при завершении программы с ID = 2 ("/id:2")
			$ExitCode = GUICtrlRead($Dummy2)
			If $ExitCode = -1 Then
				$ExitCode = 'ERROR'
			EndIf
			GUICtrlSetData($Input4, $ExitCode)
			GUICtrlSetState($Button2, $GUI_ENABLE)
			GUICtrlSetState($Input3, $GUI_ENABLE)
			$Count -= 1
			If Not $Count Then
				GUISetCursor(2, 0)
			EndIf
	EndSwitch
WEnd

FileDelete(@TempDir & '\Dark_Archon_Black.gif')

Func _SendMsg($hWnd, $iData, $iID)

	Local $tCOPYDATA = DllStructCreate('ulong_ptr;dword;ptr')
	Local $Val, $Ret

	If IsString($iData) Then
		$tData = DllStructCreate('char[' & (StringLen($iData) + 1) & '];dword')
		$Val = 1
	Else
		$tData = DllStructCreate('int;dword')
		$Val = 2
	EndIf
	DllStructSetData($tData, 1, $iData)
	DllStructSetData($tData, 2, $iID)
	DllStructSetData($tCOPYDATA, 1, $Val)
	DllStructSetData($tCOPYDATA, 2, DllStructGetSize($tData))
	DllStructSetData($tCOPYDATA, 3, DllStructGetPtr($tData))
	$Ret = DllCall('user32.dll', 'lresult', 'SendMessage', 'hwnd', $hWnd, 'uint', $WM_COPYDATA, 'ptr', 0, 'ptr', DllStructGetPtr($tCOPYDATA))
	If (@error) Or (Not $Ret[0]) Then
		Return 0
	EndIf
	Return 1
EndFunc   ;==>_SendMsg

Func _WinAPI_GetExitCodeProcess($hProcess)

	Local $Ret = DllCall('kernel32.dll', 'int', 'GetExitCodeProcess', 'ptr', $hProcess, 'dword*', 0)

	If (@error) Or (Not $Ret[0]) Then
		Return SetError(1, 0, 0)
	EndIf
	Return $Ret[2]
EndFunc   ;==>_WinAPI_GetExitCodeProcess

Func _WinAPI_OemToChar($sStr)

	Local $tData = DllStructCreate('char[' & StringLen($sStr) + 1 & ']')
	Local $Ret = DllCall('user32.dll', 'int', 'OemToChar', 'str', $sStr, 'ptr', DllStructGetPtr($tData))

	If (@error) Or (Not $Ret[0]) Then
		Return SetError(1, 0, '')
	EndIf
	Return DllStructGetData($tData, 1)
EndFunc   ;==>_WinAPI_OemToChar

Func WM_COPYDATA($hWnd, $iMsg, $wParam, $lParam)

	Local $tCOPYDATA = DllStructCreate('ulong_ptr;dword;ptr', $lParam)

	Switch $hWnd
		Case $hForm
			Switch DllStructGetData($tCOPYDATA, 1)
				Case 1 ; Text
					$tData = DllStructCreate('char[' & (DllStructGetData($tCOPYDATA, 2) - 4) & '];dword', DllStructGetData($tCOPYDATA, 3))
					Switch DllStructGetData($tData, 2)
						Case 1 ; "/id:1"
							_GUICtrlEdit_InsertText($Edit1, DllStructGetData($tData, 1))
						Case 2 ; "/id:2"
							_GUICtrlEdit_InsertText($Edit2, DllStructGetData($tData, 1))
					EndSwitch
				Case 2 ; ExitCode
					$tData = DllStructCreate('int;dword', DllStructGetData($tCOPYDATA, 3))
					Switch DllStructGetData($tData, 2)
						Case 1 ; "/id:1"
							GUICtrlSendToDummy($Dummy1, DllStructGetData($tData, 1))
						Case 2 ; "/id:2"
							GUICtrlSendToDummy($Dummy2, DllStructGetData($tData, 1))
					EndSwitch
			EndSwitch
			Return 1
	EndSwitch
	Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_COPYDATA
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
Изменил код в предыдущем ответе.

:smile:
 

madmasles

Модератор
Глобальный модератор
Сообщения
7,790
Репутация
2,322
Yashied,
Я давно пытался получать вывод из консоли построчно, но получал только блоками, что меня не устраивало и я забросил это дело. Теперь, благодаря Вам, я понял, как это можно сделать! :ok:
Большое Огромное СПАСИБО!!!
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
Данные из консоли всегда считываются блоками. При подключении консоли создается буфер, и программа посылает в этот буфер свои данные по мере их поступления. StdoutRead() считывает из буфера все данные, которые там есть на данный момент, и затем очищает его. Если считывать данные достаточно быстро, то можно получать информацию такими же блоками, что и помещает ее в буфер ведомая программа. А так, как получиться.
 
Автор
T

tech-gs

Знающий
Сообщения
54
Репутация
5
Yashied
Все работает отлично. То что было нужно.
Сейчас протестирую на командах, которые долго и нудно работают и выводят много информации.
 
Автор
T

tech-gs

Знающий
Сообщения
54
Репутация
5
Попробовал с помощью этого кода посредством утилиты Updater.exe скачать полный набор обновлений к касперскому. Все отработало нормально. Общий объем выведенной в текстовое поле информации составил чуть менее 2 миллиона символов. Вывод конечно шел блоками, когда примерно по 4000 символов, когда на много меньше (построчно похоже все же никак). В общем, при таком объеме информации - терпимо. Код возврата CMD определяется исправно. Ну и что самое важное - параллельные сеансы CMD отрабатывают не влияя друг на друга, в отличае от запуска через RunWait(...)).

Вопрос к Yashied: может все таки стоит написать рекламацию про досрочное завершение RunWait( 1 ) после завершения параллельного RunWait( 2 ) ?
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
tech-gs сказал(а):
...может все таки стоит написать рекламацию про досрочное завершение RunWait( 1 ) после завершения параллельного RunWait( 2 )?

tech-gs, что по твоему представляет собой RunWait()? Грубо говоря, это Run() + While/WEnd. Т.е. именно то, что я здесь и реализовал. А т.к. AutoIt изначально не разрабатывался, как многопоточный ЯП, то работа всего скрипта будет приостановлена на время выполнения цикла RunWait(). А если теоретически нельзя вызвать RunWait() более одного раза в одно и тоже время в одном скрипте, то разработчики и не заморачивались над этим, и это правильно, нефиг бессмысленно усложнять ЯП. Счастье и то, что при вызове второго RunWait(), первый завершает свою работу и выдает ошибку (недокументированую), а не подвешивает программу.

tech-gs сказал(а):
Вывод конечно шел блоками, когда примерно по 4000 символов, когда на много меньше (построчно похоже все же никак).

Ты можешь при получении очередной порции данных в основном скрипте в WM_COPYDATA разбивать его функцией

Код:
StringSplit(DllStructGetData($tData, 1), @CR)


И потом построчно выводить его в Edit той же _GUICtrlEdit_InsertText(), и будет выглядеть как бы построчно. Но это ты уже сам...

:smile:
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
У меня вопос к знатокам CMD (CreatoR?). Как можно в одном вызове CMD, например "cmd /c dir", поставить небольшую задержку перед выполнением "dir" (100 мс вполне достаточно)? .bat?
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
Yashied [?]
Как можно в одном вызове CMD, например "cmd /c dir", поставить небольшую задержку перед выполнением "dir"
Код:
Ping -n 1 Localhost > Nul & Dir
правда это на одну секунду.
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
Спасибо. А можно меньше? На одну секунду я нашел команду "timeout":

"timeout /t 1 > nul & dir"

Но это будет многовато, хотя...
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
tech-gs сказал(а):
Вывод конечно шел блоками, когда примерно по 4000 символов, когда на много меньше (построчно похоже все же никак).

Замени в предыдущем примере следующий кусок, и текст будет выводиться построчно, но скорость сильно упадет.

Код:
If ($CmdLine[0] = 3) And (StringInStr($CmdLine[1], '/runwait:') = 1) And (StringInStr($CmdLine[2], '/id:') = 1) And (StringInStr($CmdLine[3], '/handle:') = 1) Then
	$ExitCode = -1
	$hWnd = Ptr(StringTrimLeft($CmdLine[3], 8))
	$ID = Int(StringTrimLeft($CmdLine[2], 4))
	$PID = Run(StringTrimLeft($CmdLine[1], 9), '', @SW_HIDE, $STDERR_MERGED)
	If $PID Then
		Switch @OSVersion
			Case 'WIN_2000', 'WIN_2003', 'WIN_XP', 'WIN_XPe'
				$hProcess = _WinAPI_OpenProcess(0x0400, 0, $PID) ; PROCESS_QUERY_INFORMATION
			Case Else ; Vista/7
				$hProcess = _WinAPI_OpenProcess(0x1000, 0, $PID) ; PROCESS_QUERY_LIMITED_INFORMATION
		EndSwitch
		While 1
			$Data = StdoutRead($PID)
			If @error Then
				ExitLoop
			EndIf
			If @extended Then
				$List = StringSplit(StringRegExpReplace(StringStripCR(_WinAPI_OemToChar($Data)), '\n\z', ''), @LF)
				If IsArray($List) Then
					For $i = 1 To $List[0]
						If ($i < $List[0]) Or (StringRight($Data, 2) = @CRLF) Then
							$List[$i] &= @CRLF
						EndIf
						If Not _SendMsg($hWnd, $List[$i], $ID) Then
							Exit
						EndIf
					Next
				EndIf
			EndIf
		WEnd
		If $hProcess Then
			$ExitCode = _WinAPI_GetExitCodeProcess($hProcess)
			If (@error) Or ($ExitCode = 259) Then
				$ExitCode = -1
				If ProcessExists($PID) Then
					ProcessClose($PID)
				EndIf
			EndIf
			_WinAPI_CloseHandle($hProcess)
		EndIf
	EndIf
	_SendMsg($hWnd, $ExitCode, $ID)
	Exit
EndIf
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
Вот придумал. Думаю этого будет достаточно (у меня ~100 мс).

Код:
$Timer = TimerInit()
RunWait('cmd.exe /c dir', '', @SW_HIDE)
ConsoleWrite(TimerDiff($Timer) & @CR)

$Timer = TimerInit()
RunWait('cmd.exe /c (for /l %a in (1,1,200) do @echo ? > nul) & dir', '', @SW_HIDE)
ConsoleWrite(TimerDiff($Timer) & @CR)


:smile:
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
Yashied [?]
Есть ещё метод, с использованием vbs:

Код:
$Timer = TimerInit()
RunWait('cmd.exe /c (Echo WScript.Sleep 50>%temp%\slp.vbs & Start /Wait wscript.exe %temp%\slp.vbs //E:VBScript & del %temp%\slp.vbs) & dir', '', @SW_HIDE)
ConsoleWrite(TimerDiff($Timer) & @CR)


Правда паузу немного больше чем та что указывается в Sleep.

P.S
Вот тут есть неплохие методы.
 
Автор
T

tech-gs

Знающий
Сообщения
54
Репутация
5
Yashied сказал(а):
Замени в предыдущем примере следующий кусок, и текст будет выводиться построчно, но скорость сильно упадет.

Протестировал этот кусок. Скорость практически не падает (или просто не заметил). Способ вывода несколько изменился: вывод идет так же блочно, только блок выводится не сразу целиком, а печатается построчно, примерно за 0.2-0.3 сек все строки блока. Т.е, уже присутствуют признаки скроллинга текста! В любом случае спасибо за вашу работу, очень интересные решения!

P.s.: Заметил, что при реализации перенаправления, блочность происходит не только при передаче/приеме сообщений из CMD в приложение, но и запускаемые в теле командной строки утилиты начинают писать логи своей работы в файлы так же блоками.
 
Автор
T

tech-gs

Знающий
Сообщения
54
Репутация
5
Yashied
Попробовал перенести Case $Dummy1 и Case $Dummy2 из цикла GUIGetMsg() в WM_COMMAND($hWnd, $Msg, $wParam, $lParam) - перестает работать, т.е. командная строка работает, ее текст передается в программу, но программа ее завершение не отлавливает, висит в ожидании чуда. Все остальные события в WM_COMMAND работают нормально. Раньше в других программах пробовал обрабатывать события элементов Dummy через WM_COMMAND - вроде бы все работало нормально. Дай, пожалуйста, какое-нибудь этому объяснение.
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
tech-gs сказал(а):
Попробовал перенести Case $Dummy1 и Case $Dummy2 из цикла GUIGetMsg() в WM_COMMAND($hWnd, $Msg, $wParam, $lParam)

Вопрос - зачем??? GUICtrlSendToDummy() работает на внутреннем уровне и не регистрируется WM_COMMAND. Вообще, Dummy, это виртуальный элемент. Он создается только в рамках AutoIt. Например, GUICtrlGetHandle($Dummy) вернет 0. Если очень хочется избавиться от Dummy, то помести его код в WM_COPYDATA.

Код:
Func WM_COPYDATA($hWnd, $iMsg, $wParam, $lParam)

	Local $tCOPYDATA = DllStructCreate('ulong_ptr;dword;ptr', $lParam)
	Local $tData, $iData

	Switch $hWnd
		Case $hForm
			Switch DllStructGetData($tCOPYDATA, 1)
				Case 1 ; Text
					$tData = DllStructCreate('char[' & (DllStructGetData($tCOPYDATA, 2) - 4) & '];dword', DllStructGetData($tCOPYDATA, 3))
					Switch DllStructGetData($tData, 2)
						Case 1 ; "/id:1"
							_GUICtrlEdit_InsertText($Edit1, DllStructGetData($tData, 1))
						Case 2 ; "/id:2"
							_GUICtrlEdit_InsertText($Edit2, DllStructGetData($tData, 1))
					EndSwitch
				Case 2 ; ExitCode
					$tData = DllStructCreate('int;dword', DllStructGetData($tCOPYDATA, 3))
					$iData = DllStructGetData($tData, 1)
					If $iData = -1 Then
						$iData = 'ERROR'
					EndIf
					Switch DllStructGetData($tData, 2)
						Case 1 ; "/id:1"
							GUICtrlSetData($Input2, $iData)
							GUICtrlSetState($Button1, $GUI_ENABLE)
							GUICtrlSetState($Input1, $GUI_ENABLE)
						Case 2 ; "/id:2"
							GUICtrlSetData($Input4, $iData)
							GUICtrlSetState($Button2, $GUI_ENABLE)
							GUICtrlSetState($Input3, $GUI_ENABLE)
					EndSwitch
					$Count -= 1
					If Not $Count Then
						GUISetCursor(2, 0)
					EndIf
			EndSwitch
			Return 1
	EndSwitch
	Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_COPYDATA
 
Верх