2

У меня есть безголовый компьютер, на котором запущена специальная служба, которую я хочу включить / отключить с помощью кнопки питания, а не удаленного подключения каждый раз. Компьютер выполняет и другие функции, поэтому отключать его нельзя.

Можно ли подключить кнопку питания системы под Windows XP и выше, чтобы моя программа получала событие до того, как Windows инициирует событие отключения / отключения (до PBT_APMQUERYSUSPEND )?

Я понял это, см. Мой собственный ответ ниже!

2 ответа2

1

Вы можете опросить WMI для события завершения работы, но нет способа остановить его. Таким образом, нет гарантии, что ваша программа получит событие до того, как какие-либо услуги будут прекращены.

1

Это действительно возможно, но немного хакерски и требует двух совершенно разных реализаций в зависимости от версии Windows. Для обоих способов вам нужно установить кнопку питания, чтобы перевести компьютер в режим сна.

Windows XP и ниже:

Вам нужно переписать функцию WndProc главного окна вашей программы. На IDE, которые не поддерживают это изначально, это можно сделать с помощью SetWindowLong в API user32. В вашей пользовательской функции WndProc прослушайте сообщение WM_POWERBROADCAST (0x218) . Если вы получили сообщение с wParam PBT_APMQUERYSUSPEND (0x0) , вызовите требуемую функцию и затем верните BROADCAST_QUERY_DENY (0x424D5144) вместо вызова базовой функции WndProc . Пример кода:

//At program start
//GWL_WNDPROC = -4
oldWndProc = SetWindowLong(this.hWnd, GWL_WNDPROC, &MyWndProc)

//In MyWndProc(hWnd, wMsg, wParam, lParam)
//WM_POWERBROADCAST = 0x218
//PBT_APMQUERYSUSPEND = 0x0
//BROADCAST_QUERY_DENY = 0x424D5144
if wMsg = WM_POWERBROADCAST && wParam = PBT_APMQUERYSUSPEND (
    //CALL YOUR FUNCTION HERE!
    return BROADCAST_QUERY_DENY
)
return CallWindowProc(oldWndProc, hWnd, wMsg, wParam, lParam)

//Before exiting
SetWindowLong(Me.hWnd, GWL_WNDPROC, oldWndProc)

Windows Vista и более поздние версии : (спасибо Реми Лебо за правильную настройку)

Вам нужно переопределить WndProc как для XP, но также вызвать SetThreadExecutionState в API-интерфейсе kernel32, чтобы отключить спящий режим, и RegisterPowerSettingNotification в API-интерфейсе user32, чтобы прослушивать расширенные уведомления о питании. В частности, вы будете прослушивать уведомление GUID_SYSTEM_AWAYMODE , которое отправляется, когда систему просят перейти в спящий режим, но не может этого сделать. Чтобы легко преобразовать строку в правильно сформированный LPCGUID вы можете использовать UuidFromStringA в API rpcrt4.dll. Пример кода:

typedef struct UUID{
    int d1, d2, d3, d4
} LPCGUID;

//At program start
//GWL_WNDPROC = -4
//ES_CONTINUOUS = 0x80000000
//ES_SYSTEM_REQUIRED = 0x1
//ES_AWAYMODE_REQUIRED = 0x40
//GUID_SYSTEM_AWAYMODE = "98a7f580-01f7-48aa-9c0f-44352c29e5C0"
LPCGUID uid;
oldWndProc = SetWindowLong(this.hWnd, GWL_WNDPROC, &MyWndProc)
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_AWAYMODE_REQUIRED)
UuidFromStringA(*(GUID_SYSTEM_AWAYMODE), uid)
ps = RegisterPowerSettingNotification(this.hWnd, uid, 0)

//In MyWndProc(hWnd, wMsg, wParam, lParam)
//WM_POWERBROADCAST = 0x218
//PBT_POWERSETTINGCHANGE = 0x8013
if wMsg = WM_POWERBROADCAST && wParam = PBT_POWERSETTINGCHANGE (
    //CALL YOUR FUNCTION HERE!
    //You can additionally extract data from the lParam to verify
    //this is the notification you're waiting for (see below)
)
return CallWindowProc(oldWndProc, hWnd, wMsg, wParam, lParam)

//Before exiting
SetWindowLong(Me.hWnd, GWL_WNDPROC, oldWndProc)
UnregisterPowerSettingNotification(ps)

Этот метод имеет побочный эффект: отключение физического экрана (не проблема на компьютере без монитора), а также, возможно, блокировка сеанса. Убедитесь, что вы отключили запрос пароля после сна, чтобы избежать этого. Есть некоторая дополнительная полезная информация о RegisterPowerSettingNotification доступная здесь, которая показывает, как извлечь информацию из lParam в вашей функции WndProc на случай, если вам понадобится дополнительная информация об уведомлении. Повеселись ;)

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .