All streams
Search
Write a publication
Pull to refresh

Comments 10

Не буду касаться стиля кода, хотя очень хочется. Явного непонимания, ложных измышлений и пренебрежения к авторам подсистемы win32k в статье и так полно. Остановлюсь лишь на паре моментов:

Член hbrBackground структуры WNDCLASSEX не заполнен, чтобы создать прозрачное окно и на моей 7-й винде так оно и есть, но на 10-й винде окно белое; видимо, чего-то я не понимаю :) .

Вас вообще не смущает, что WNDCLASSEX wcx у вас локальная переменная? Что она на стеке, а там изначально может лежать любой мусор, потенциально могущий меняться от запуска к запуску? Вы не инициализируете один из элементов структуры, относящихся к фону окна, а потом удивляетесь. Да и hbrBackground вообще не о прозрачности. Для прозрачного окна можно использовать, например, WS_EX_LAYERED и SetLayeredWindowAttributes, но это уже детали.

PeekMessage используется в бесконечном цикле с минимальной задержкой для прослушивания потока, GetMessage я не рекомендую (для новичков это будет сложней, а для профи мои советы не нужны).Малый цикл и Sleep(1) нужны для разгрузки процессора, переменную i выставьте под свои нужды.

Это вообще ни в какие ворота, тут просто косяк на косяке. Sleep(1) вызывает принудительное переключение контекста потока, а зачастую и между ядрами, что явно не разгружает процессор. PeekMessage упирается в NtUserPeekMessage - соответствующий сисколл, переключение в режим ядра и соответствующее переключение контекста. Sleep(1) отработает как повезёт (хотя и почти всега так, как задумывается). А нормальный GetMessage спокойно висит на эвенте и не даёт практически никакого оверхеда.

P.S.

И какой альтернативно одаренный программист научил вас не использовать предопределенные константы типа WM_KEYUP, а заменять их на числовые значения?

Вас вообще не смущает, что WNDCLASSEX wcx у вас локальная переменная? Что она на стеке, а там изначально может лежать любой мусор, потенциально могущий меняться от запуска к запуску? Вы не инициализируете один из элементов структуры, относящихся к фону окна, а потом удивляетесь. Да и hbrBackground вообще не о прозрачности.

Тут явно кто-то что-то не понимает, давайте разберёмся, но только спокойно, без ругани. Вот эта строка разве не является инициализацией структуры?

WNDCLASSEX wcx = {sizeof(WNDCLASSEX)};

Я и не задавался целью сделать фон прозрачным, просто у меня он становится прозрачным, когда hbrBackground явно не задана, вернее, задана значением по умолчанию, видимо 0. На другой винде фон белый.

Это вообще ни в какие ворота, тут просто косяк на косяке. Sleep(1) вызывает принудительное переключение контекста потока, а зачастую и между ядрами, что явно не разгружает процессор.

Явно вы настроены агрессивно, но меня это не задевает, я тёртый калач в форумных баталиях, но не люблю, когда со старта кто-то создаёт негативный фон.

Sleep(1) работает стабильно и очень эффективно разгружает процессор ‒ если в цикле идёт только прослушивание потока, то нагрузка меньше 1%; считывание экрана в буфер в цикле может занимать до 5%, но у меня старый комп.

Если вы такой опытный, то сможете использовать более эффективный код, но странно, что вы его не предложили читателям, ведь кроме вашей громкой критики он ничего полезного из вашего коммента не получит. Так есть у вас предложение лучше, чем Sleep(1)? И чтобы это было компактно и понятно новичку!

PeekMessage упирается в NtUserPeekMessage - соответствующий сисколл, переключение в режим ядра и соответствующее переключение контекста. Sleep(1) отработает как повезёт (хотя и почти всега так, как задумывается). А нормальный GetMessage спокойно висит на эвенте и не даёт практически никакого оверхеда.

Куда бы там ни упирался PeekMessage, но зачастую это единственно верный вариант, потому что не блокирует поток. GetMessage я тоже использую, но там есть свои нюансы. Кстати, а давайте проверим, знаете ли вы про эти нюансы ‒ покажите нам блок кода с GetMessage, который будет компактным и при этом работать в паре с хуком и без создания окна. Уверен, вы напишите его в момент и он сразу будет рабочим.

З.Ы. Признаю, что я не профи, а только учусь, но вот что интересно ‒ до меня никто на хабре толковой статьи на эти темы не создал, а почему, если здесь столько профи? Основной целью статьи является показать концепцию и основные пути реализации, а не чтобы научить новичка красиво писать код.

Куда бы там ни упирался PeekMessage, но зачастую это единственно верный вариант, потому что не блокирует поток.

Нечего писать функционал в потоке, обрабатывающем оконные сообщения. Кстати, SetForegroundWindow для фокуса - не панацея.

покажите нам блок кода с GetMessage, который будет компактным и при этом работать в паре с хуком и без создания окна.

Навскидку:

#include "stdafx.h"

LRESULT CALLBACK LowLevelKeyboardProc(
	int    nCode,
	WPARAM wParam,
	LPARAM lParam)
{
	if (nCode == HC_ACTION && wParam == WM_KEYDOWN)
	{
		PKBDLLHOOKSTRUCT pKbdHk = (PKBDLLHOOKSTRUCT)lParam;
		if(pKbdHk->vkCode == VK_LWIN)
		{
			MessageBeep(MB_OK);				// if LWIN was released

			return 1;						// it blocks LWIN for next hooks
		}
		else if(pKbdHk->vkCode == VK_ESCAPE)
		{
			PostQuitMessage(0);				// exit if Esc was pressed
		}
	}

	return CallNextHookEx(NULL, nCode, wParam, lParam);
}

int APIENTRY _tWinMain(
	HINSTANCE hInstance, 
	HINSTANCE hPrevInstance, 
	LPTSTR	  lpCmdLine,
	int		  nCmdShow)
{
	HHOOK hHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, NULL, 0);
	if (hHook != NULL) 
	{
		MSG Msg;
		while (GetMessage(&Msg, NULL, 0, 0))
		{
		}

		UnhookWindowsHookEx(hHook);
	}
	else
	{
		MessageBox(0, L"Keyboard hook failed", 0, MB_ICONERROR);
	}

	return 0;
}

Кстати, SetForegroundWindow для фокуса - не панацея.

Предложите панацею.

По поводу вашего кода: GetMessage не работает в консольном приложении, в отличии от PeekMessage, вернее, сам хук будет работать, но приложение нет, оно "замёрзнет". А ещё после GetMessage нужна одна функция, надеюсь, вы знаете, какая. Поэтому для новичка PeekMessage будет универсальным вариантом, рабочим в любых условиях.

Предложите панацею.

void GoToTop(HWND hWnd)
{
	HWND hCurrentWnd = ::GetForegroundWindow();
	DWORD dwCurrentThreadId = ::GetCurrentThreadId();
	DWORD dwProcessId = ::GetWindowThreadProcessId(hCurrentWnd, 0);

	::AttachThreadInput(dwCurrentThreadId, dwProcessId, TRUE);
	::SetForegroundWindow(hWnd);
	::AttachThreadInput(dwCurrentThreadId, dwProcessId, FALSE);
}

В сочетании с LL-хуком много лет стабильно выводит мои окна поверх всех при нажатии на хоткей.

GetForegroundWindow

Я не понял, вы делаете активным окно, которое уже активно? Иначе какой тогда смысл этой функции?

Смысл в том, что как только нехорошие люди стали использовать ::SetForegroundWindow() для вывода рекламных попапов, Майкрософт поменял её поведение (на мигание кнопки в таскбаре). Было это лет тридцать назад. В 98-й, кажется. Поэтому

SetForegroundWindow для фокуса - не панацея

и стоит использовать её в сочетании с ::AttachThreadInput(). Тем более, для юз-кейса «поймали LL-хуком нажатие клавиши — отобразили OSD», о котором, я так понял, и идёт речь в статье. (Обычно ещё OSD делают layered window). Это решение стабильно работает тридцать лет, и, хочется надеяться, не будет сломано в будущем.

Есть ещё верный способ активировать окно: вывести его поверх всех и эмулировать по нему клик.

Для тех, кто работает с LL-хуками под Вынь 11: обратите внимание на ключ HKCU\Control Panel\Desktop\LowLevelHooksTimeout.

Периодически начал слетать (1 вместо 5000), никогда раньше не сталкивался. После чего винды принудительно дерегистрируют каждого, кто не уложится в 1мс.

За наводку спасибо @alzneo.

Обнаружил очень любопытную вещь и спешу поделиться. Проводил эксперименты с прозрачным окном, стиль WS_EX_LAYERED ‒ оказалось, что оно вообще никак не реагирует на GetDC(0), т.е. его содержимое не считывается прямо с экрана и на него нельзя наложить растр, но при этом всё, что есть под ним, считывается через GetDC(0). Получается довольно прикольная штука ‒ я могу накрыть этим окном всё что угодно и менять там растр, а окно всякий раз будет накрывать содержимое с той степенью прозрачности, что я указал. Видимо, для таких окон существует независимый буфер, который не пересекается с буфером экрана.

Sign up to leave a comment.

Articles