Что нового

Как перехватить сообщение Windows о завершении работы ?

VladUs

Скриптер
Сообщения
621
Репутация
182
Известно, что при выключении компьютера, Windows отправляет всем оконным
приложениям сообщение WM_QUERYENDSESSION с требованием закрыть окно.
Приложение возвращет назад либо True и тогда окно закрывается и продолжается процедура выключения компьютера, либо возвращает False и тогда данная процедура приостанавливается. Есть ли возможность перехватить рассылку этого сообщения или может быть имеется возможность возвратить False пока код скрипта не будет выполнен до конца.

В качестве примера можно представить программу для бэкапа даннных, которая при выключении компьютера (то бишь получив от Системы сообщение WM_QUERYENDSESSION) запускает код по сохранению данных и только после успешного выполнения процесс выключения компьютера продолжается. Может быть существует и др. решения данной задачи, но меня интересует именно вопрос с обработкой данного сообщения.
Извените за многословность....
 

`p r o x y

«Улыбайтесь, господа!»
Команда форума
Глобальный модератор
Сообщения
596
Репутация
157
Автор
V

VladUs

Скриптер
Сообщения
621
Репутация
182
p r o x y
VladUs
Очень внимательно следует изучть вот эту тему: Автоматический запуск скрипта после нажатия ЗАВЕРШЕНИЯ РАБОТЫ.
Я ознакомлен с данной темой и мне удалось там даже оставить сообщение. Признаться из той темы и возник мой вопрос

Те методы, которые Вы мне дали, заслуживают внимания. Но они не относятся к данной теме, а вопрос данной темы не Автоматический запуск скрипта после нажатия ЗАВЕРШЕНИЯ , а Как перехватить сообщение Windows о завершении работы ?
Если Вас смутил возможный пример, то не обращайте на него внимание, а посмотрите вопрос в теме и первую часть моего первого поста
 

`p r o x y

«Улыбайтесь, господа!»
Команда форума
Глобальный модератор
Сообщения
596
Репутация
157
Я привел решение, как реализовать второй абзац вопроса.
И именно из него вытекает решение первого абзаца вопроса: OnAutoItExitRegister.

Сообщение WM_QUERYENDSESSION можно отловить для любого скрипта AutoIt через функцию, которая будет вызываться при закрытии скрипта. Такую функцию можно назначить с помощью функции OnAutoItExitRegister. Следовательно, как только скрытое окно AutoIt скрипта получит команду на закрытие, будет вызвана пользовательская функция.

Можно попробовать непосредственно отловить и само сообщение, вне закрытия скрипта (хотя смысл усложнять себе работу?) используя функцию GUIRegisterMsg.


В той теме был пример схемы, как выполнять действия при закрытии скрипта. Принцип был такой: скрипт все время работает в фоне, при завершение работы ОС скрипт получает сообщение WM_QUERYENDSESSION, ну а дальше, как писал выше.
 
Автор
V

VladUs

Скриптер
Сообщения
621
Репутация
182
Код:
GUIRegisterMsg()
- именно то что мне надо

Код:
#Include <WinAPIEx.au3>

GUIRegisterMsg ( 0x0011, "_Ret" )

$GUI = GUICreate("Test")
GUISetState(@SW_HIDE )

While 1
    Sleep(100)
WEnd

Func _Ret()
	$i = 0
   Return $i
EndFunc


Благодарю p r o x y
 

`p r o x y

«Улыбайтесь, господа!»
Команда форума
Глобальный модератор
Сообщения
596
Репутация
157
В этом примере подключение WinAPIEx.au3 случайно? Т.к. все нижеследующее можно выполнить без него.
GUISetState(@SW_HIDE) можно убрать, т.к. созданное окно по умолчанию скрыто.
{TopicSolvedInfo}
 
Автор
V

VladUs

Скриптер
Сообщения
621
Репутация
182
В этом примере подключение WinAPIEx.au3 случайно?

Да можно считать случайным, просто ниже функция которая использует данную библиотеку.
В данном примере она лишняя
Еще раз благодарю.
Тема решена.
 

`p r o x y

«Улыбайтесь, господа!»
Команда форума
Глобальный модератор
Сообщения
596
Репутация
157
VladUs [?]
Да можно считать случайным, просто ниже функция которая использует данную библиотеку.
В данном примере она лишняя
На всякий случай спросил, мало ли.
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
С помощью OnAutoItExitUnregister() конечно можно отловить завершение сессии или выключение компьютера, но НЕЛЬЗЯ предотвратить сие действие. На все провсе у вас будет 5 сек (по умолчанию). По истечении этого времени программа будет выгружена автоматически.

В случае, если вам недостаточно 5 сек, необходимо будет зарегистрировать сообщение WM_QUERYENDSESSION. В Windows XP достаточно возвратить 0 в ответ на это сообщение, чтобы отменить "shutdown". В Windows Vista/7 все немного сложнее и проще одновременно. Возврат 0 здесь уже ничего не даст (т.е. программно отменить "shutdown" не получиться), но зато появился хороший метод гарантированного сохранения необходимых данных. Для этого перед любыми критическими операциями нужно регистрировать специальное сообщение (причину), а после выполнения таких операций удалять это сообщение. Если вы используете Windows Vista/7, то не раз, наверное, видели полупрозрачный черный экран после нажатия "Shut down" из меню "Start". Вот именно на этом экране и появится это сообщение. Вот небольшой пример:

Код:
#Include <GUIConstantsEx.au3>

Global $Shutdown = False

_WinAPI_SetProcessShutdownParameters(0x03FF)

$hForm = GUICreate('')
GUIRegisterMsg(0x0011, 'WM_QUERYENDSESSION')
GUIRegisterMsg(0x0016, 'WM_ENDSESSION')

_WinAPI_ShutdownBlockReasonCreate($hForm, 'This application is blocking system shutdown because saving critical data is in progress.')
Sleep(60000)
_WinAPI_ShutdownBlockReasonDestroy($hForm)
If $Shutdown Then
	Exit
EndIf

While 1
	Sleep(1000)
WEnd

Func WM_QUERYENDSESSION($hWnd, $Msg, $wParam, $lParam)
	Switch $hWnd
		Case $hForm
			If _WinAPI_ShutdownBlockReasonQuery($hForm) Then
				$Shutdown = True
				Return 0
			EndIf
	EndSwitch
	Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_QUERYENDSESSION

Func WM_ENDSESSION($hWnd, $Msg, $wParam, $lParam)
	Switch $hWnd
		Case $hForm
			If Not $wParam Then
				$Shutdown = False
				Return 0
			EndIf
	EndSwitch
	Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_ENDSESSION

Func _WinAPI_SetProcessShutdownParameters($iLevel, $iFlag = 0)

	Local $Ret = DllCall('kernel32.dll', 'int', 'SetProcessShutdownParameters', 'dword', $iLevel, 'dword', $iFlag)

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

Func _WinAPI_ShutdownBlockReasonCreate($hWnd, $sText)

	Local $Ret = DllCall('user32.dll', 'int', 'ShutdownBlockReasonCreate', 'hwnd', $hWnd, 'wstr', $sText)

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

Func _WinAPI_ShutdownBlockReasonDestroy($hWnd)

	Local $Ret = DllCall('user32.dll', 'int', 'ShutdownBlockReasonDestroy', 'hwnd', $hWnd)

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

Func _WinAPI_ShutdownBlockReasonQuery($hWnd)

	Local $tData = DllStructCreate('wchar[1024]')
	Local $Ret = DllCall('user32.dll', 'int', 'ShutdownBlockReasonQuery', 'hwnd', $hWnd, 'ptr', DllStructGetPtr($tData), 'dword*', 1024)

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


Здесь WM_ENDSESSION нужна для того, чтобы сбросить флаг $Shutdown, если пользователь отменил "shutdown". Sleep(60000) эмулирует сохранение важных данных. По истечении 60 сек, если вы нажали "Start - Shut down", компьютер будет выключен (если конечно вы это не отменили).

Если необходимо что-то выполнить именно во время "shutdown", то можно вызвать _WinAPI_ShutdownBlockReasonCreate() непосредственно из обработчика WM_QUERYENDSESSION(), просигнализировать об этом в основной цикл с помощью Dummy или специального флага, а затем вернуть 0. После сохранения всех важных данных, нужно будет вызвать _WinAPI_ShutdownBlockReasonDestroy() и выйти из программы с помощью Exit() для продолжения процесса "shutdown" (опять же, если пользователь не отменил этот процесс, см. флаг $Shutdown).
 

`p r o x y

«Улыбайтесь, господа!»
Команда форума
Глобальный модератор
Сообщения
596
Репутация
157
Yashied [?]
С помощью OnAutoItExitUnregister() конечно можно отловить завершение сессии или выключение компьютера, но НЕЛЬЗЯ предотвратить сие действие. На все провсе у вас будет 5 сек (по умолчанию). По истечении этого времени программа будет выгружена автоматически.
Так, а если: shutdown /a
Выполнить свои действия и вызвать выключение вручную
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
`p r o x y сказал(а):
Так, а если: shutdown /a

Это совсем другое. "shutdown /a" отменяет выключение, которое было инициировано "shutdown /t".
 
Автор
V

VladUs

Скриптер
Сообщения
621
Репутация
182
Yashied
Вы очень хорошо меня поняли по сути поднятого вопроса.
Тогда в продолжении темы: А как поступать с консольными приложениями у которых отсутствуют окна ?
(Побольшей части меня интересует Windows Xp)
Обнаружив консольное приложение, Csrss вызывает обработчик консоли, посылая событие CTRL_LOGOFF_EVENT
(при завершении работы системы только процессы сервисов получают событие CTRL_SHUTDOWN_EVENT ).
Если обработчик возвращает FALSE, Csrss уничтожает процесс. Если обработчик возвращает TRUE или не отвечает в течении определенного времени ... Csrss выводит диологовое окно принудительного завершения программы.

Внутренне устройство Windows XP
М. Руссинович, Д. Соломон
И возможно ли перехватить системные сообщения для других не Autoit программ.
 

Yashied

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

:smile:
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
Вот еще один (более простой) пример на тему "Shutdown в Windows Vista/7". Все необходимые функции возьмите в предыдущем ответе.

Код:
#Include <ButtonConstants.au3>
#Include <GUIConstantsEx.au3>

$hForm = GUICreate('MyGUI', 200, 200)
$Button = GUICtrlCreateButton('', 73, 62, 54, 54, $BS_ICON)
GUICtrlSetImage(-1, @SystemDir & '\shell32.dll', 45)
GUICtrlSetTip(-1, 'Log off ' & @UserName)
$Check = GUICtrlCreateCheckBox('Block Windows shutdown', 10, 173, 144, 21)
GUIRegisterMsg(0x0011, 'WM_QUERYENDSESSION')
GUISetState()

; Set the highest shutdown priority for the current process to prevent closure the other processes.
_WinAPI_SetProcessShutdownParameters(0x03FF)

While 1
    $Msg = GUIGetMsg()
    Switch $Msg
        Case $GUI_EVENT_CLOSE
            ExitLoop
		Case $Button
			Shutdown(0)
        Case $Check
			If GUICtrlRead($Check) = $GUI_CHECKED Then
				_WinAPI_ShutdownBlockReasonCreate($hForm, 'This application is blocking system shutdown because the saving critical data is in progress.')
			Else
				_WinAPI_ShutdownBlockReasonDestroy($hForm)
			EndIf
    EndSwitch
WEnd

Func WM_QUERYENDSESSION($hWnd, $Msg, $wParam, $lParam)
	Switch $hWnd
		Case $hForm
			If _WinAPI_ShutdownBlockReasonQuery($hForm) Then
				Return 0
			EndIf
	EndSwitch
	Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_QUERYENDSESSION


VladUs сказал(а):
И возможно ли перехватить системные сообщения для других не Autoit программ.

Если вы и сможете перехватить сообщения для других окон с помощью хука, то вряд ли сможете что-нибудь с этим сделать. Но вы можете установить наивысший приоритет для вашего процесса на получение уведомлений о завершении работы:

Код:
_WinAPI_SetProcessShutdownParameters(0x03FF)


Здесь 0x03FF есть максимальный приоритет для приложений. Таким образом, ваша программа получит первая сообщение о завершении работы Windows, и, если вы его отмените, то до других приложений дело просто не дойдет, и они не будут выгружены.
 
Автор
V

VladUs

Скриптер
Сообщения
621
Репутация
182
Yashied Благодарю Вас за исчерпывающие разъяснения по теме. :smile:
 

endpoints

Новичок
Сообщения
26
Репутация
0
Подскажите,пожалуйста, как будет выглядеть код скрипта который может:
1) приостановить завершение работы windows 7 (вызванное софтиной упса при переходе на питание от батареи)
2) выполнение копирования файлов
3) и после окончания копирования продолжить завершение работы системы?
 

Prog

Продвинутый
Сообщения
593
Репутация
73
Для начала можно попробовать запретить завершение работы ОС получив сообщения WM_QUERYENDSESSION и WM_ENDSESSION. Возможно сработает. А если нет, то понадобится перехват функций и повезет если WinAPI, а не функций драйвера. В первом случае понадобится разработка DLL, а во втором, разработка драйвера.
 

endpoints

Новичок
Сообщения
26
Репутация
0
в предыдущих ответах мне показалось что это решили при помощи аренды времени под завершение критических операций в windows 7, только я не понял что нужно оставить и заменить в коде скрипта для моего сценария ...


Добавлено:
Сообщение автоматически объединено:

к примеру в моем случае замена sleep(60000) на
DirCreate("C:\new")
FileCopy("C:\old\*.*", "C:\new\")

достаточно?
 
A

Alofa

Гость
endpoints сказал(а):
Подскажите,пожалуйста, как будет выглядеть код скрипта...

К примеру так:
Код:
#RequireAdmin
#include <WinAPISys.au3>

Opt('WinTitleMatchMode', 3)

; Приоритет отключения процесса по отношению к другим процессам в системе
Global Const $iAPPLICATION_FIRST_SHUTDOWN_RANGE = 0x03FF ; ... (Приложения) Первая очередь отключения 
Global Const $iAPPLICATION_LAST_SHUTDOWN_RANGE = 0x01FF ; .... (Приложения) Последняя очередь отключения
Global Const $iSYSTEM_FIRST_SHUTDOWN_RANGE = 0x04FF ; ........ (Система) Первая очередь отключения
Global Const $iSYSTEM_LAST_SHUTDOWN_RANGE = 0x00FF ; ......... (Система) Последняя очередь отключения

Global $hWnd, $sWinTitle = 'Моя программа' ; ..... Заголовок окна вашей программы
Global $sText = 'Подождите, не так скоро...' ; ... Строка, отображаемая при блокировке завершения работы системы.

If WinExists($sWinTitle) Then Exit ; Предотвращение повторного запуска программы
AutoItWinSetTitle($sWinTitle)
$hWnd = WinGetHandle($sWinTitle)

OnAutoItExitRegister('_ActionsOnExit')

_WinAPI_SetProcessShutdownParameters($iSYSTEM_FIRST_SHUTDOWN_RANGE)
_WinAPI_ShutdownBlockReasonCreate($hWnd, $sText)

While Sleep(600000)
WEnd

Func _ActionsOnExit()
	_WinAPI_ShutdownBlockReasonQuery($hWnd)

	;   ... Ваши действия ...
	FileCopy('C:\_Old\*.*', 'C:\_New\', 8) ; Копирование (смотрите, что означает третий параметр в описании FileCopy())
	Sleep(10000)
	;   .....................

	_WinAPI_ShutdownBlockReasonDestroy($hWnd)
EndFunc   ;==>_ActionsOnExit


Добавлено: 10.09.16
Добавил еще 2 переменных в скрипт.
 

endpoints

Новичок
Сообщения
26
Репутация
0
Спасибо! У меня вместо filecopy прописан путь к батнику в котором бэкап происходит спомощью xcopy. Вот когда делаю shutdown появляется ошибка cmd которую видно на скрине, вот если это окно с ошибкой закрыть то только после этого происходит запуск батника... Подскажите,пожалуйста, как это лечится?
 

Вложения

  • 2016-09-09_01-40-47.png
    2016-09-09_01-40-47.png
    37.4 КБ · Просмотры: 18
Верх