Выполнение этой задачи распадается на два этапа.
Сначала нужно каким-то образом определить хэндл окна, которым
мы собираемся манипулировать. Основным инструментом здесь
являются функции FindWindow(Ex), которые ищут окно по заданному
классу и/или заголовку. В определении и того, и другого сильно
помогает программа Spy++. Рассмотрим пример поиска HWND
стандартной кнопки "Пуск". Сначала используем Spy++, чтобы
определить классы панели задач и самой кнопки; оказывается, их
имена "Shell_TrayWnd" и "Button" соответственно. Затем
используем FindWindow(Ex).
Код:
HWND hWnd;
hWnd = FindWindow("Shell_TrayWnd", NULL);
hWnd = FindWindowEx(hWnd, NULL, "Button", NULL);
if (IsWindow(hWnd))
{
// Кнопка найдена, работаем с ней
}
Ещё один набор функций, которые могут помочь в поиске хэндла
чужого окна - это EnumChildWindows, EnumThreadWindows и
EnumWindows, перечисляющие все окна, принадлежащие заданному
окну, все окна заданного потока и все окна в системе
соответственно. За описанием этих функций следует обратиться к
документации. Кроме перечисленного можно упомянуть случай,
когда приложение специально проектируется для взаимодействие с
другим посредством обмена сообщениями. Например, одно
приложение запускает другое, а затем обменивается с ним данными
посредством WM_COPYDATA. В этом случае вполне уместно передать
хэндл окна (это 4-хбайтовое целое) как параметр командной
строки. После того, как хэндл окна определён, можно переходить
ко второму этапу - управлению окном. Многие функции позволяют
работать с окном, вне зависимости от того, какому процессу оно
принадлежит. Характерные примеры таких функций - ShowWindow и
SetForegroundWindow. Для примера рассмотрим, как спрятать
кнопку "Пуск", получать хэндл которой мы уже научились.
Код:
HWND hWnd;
hWnd = FindWindow("Shell_TrayWnd", NULL);
hWnd = FindWindowEx(hWnd, NULL, "Button", NULL);
if (IsWindow(hWnd))
{
ShowWindow(hWnd, SW_HIDE);
Sleep(5000);
ShowWindow(hWnd, SW_SHOW); // Показываем обратно
}
Кроме использования подобных функций, можно посылать окну
сообщения. Например, послав кнопке BM_CLICK (с помощью
PostMessage), мы как бы нажимаем на неё.
Проблемы возникают с функциями, которые позволяют работать
только с окнами, созданными в том же потоке, в котором
вызывается функция. В качестве примера приведу функцию
DestroyWindow. Похожая проблема возникает, когда нужно
"сабкласить" окно чужого процесса. В этих случаях необходимо
внедрить свой код в чужой процесс и выполнить его в чужом
потоке; удобнее всего сделать эт о, если код оформлен в виде
DLL.
Существует несколько способов внедрить DLL в чужой процесс. Я
покажу один из них; он достаточно прост и работает на всех
Win32-платформах (Windows 9x, Windows NT), но в некоторых
случаях недостаточно точен. Этот способ подразумевает установку
хука на поток, создавший интересующее нас окно. При этом DLL,
содержащая функцию хука, загружается системой в адресное
пространство чужого процесса. Это как раз то, что нам нужно. А
функцию хука вполне можно оставить пустой.
Рассмотрим пример DLL, которая уничтожает окно чужого процесса.
(Такие вещи нужно делать, только полностью отдавая себе отчёт о
возможных последствиях. Процесс, оставленный без окна, имеет
хорошие шансы "рухнуть").
// _KillDll.cpp : Defines the entry point for the DLL application.
//
Код:
#include <windows.h>
// Создаём переменную в разделяемом сегменте,
// чтобы передать HWND из программы в DLL в чужом процессе.
#pragma comment(linker, "/SECTION:SHARED,RWS")
#pragma data_seg("SHARED")
__declspec(allocate("SHARED")) HWND hWndToKill = NULL;
#pragma data_seg()
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved )
{
if (ul_reason_for_call == DLL_PROCESS_ATTACH &&
IsWindow(hWndToKill) &&
GetWindowThreadProcessId(hWndToKill, NULL) == GetCurrentThreadId())
{
// Если окно существует и принадлежит текущему потоку, убиваем его.
HANDLE hEvent = OpenEvent(NULL, FALSE,
"{1F6C5480-155E-11d5-93A8-444553540000}");
DestroyWindow(hWndToKill);
SetEvent(hEvent);
CloseHandle(hEvent);
}
return TRUE;
}
// Пустая функция хука.
LRESULT CALLBACK GetMsgProc(int code, WPARAM wParam, LPARAM lParam)
{
return 1;
}
extern "C" __declspec(dllexport) void KillWndNow(HWND hWnd)
{
if (!IsWindow(hWnd))
return;
hWndToKill = hWnd;
HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE,
"{1F6C5480-155E-11d5-93A8-444553540000}");
DWORD dwThread = GetWindowThreadProcessId(hWnd, NULL);
HHOOK hHook = SetWindowsHookEx(
WH_GETMESSAGE,
GetMsgProc,
GetModuleHandle("_KillDll.dll"),
dwThread);
PostThreadMessage(dwThread, WM_NULL, 0, 0);
WaitForSingleObject(hEvent, INFINITE);
CloseHandle(hEvent);
UnhookWindowsHookEx(hHook);
}
Чтобы использовать эту DLL, просто подключите её к программе
(проще всего сделать это неявным методом), а затем выполните
код:
Код:
extern "C" void KillWndNow(HWND hWnd);
...
HWND hWnd;
// Ищем окно
KillWndNow(hWnd);
Хотя код DLL сам по себе и небольшой, в нём есть несколько
тонкостей, на которые я хотел бы обратить ваше внимание.
Во-первых, я поместил переменную hWndToKill в разделяемый
сегмент. Поскольку функция DestroyWindow вызывается в потоке
чужого процесса, необходимо предусмотреть некоторый способ
передачи хэндла окна через границы процессов. Разделяемая
переменная - наиболее простое средство достичь цели. Во-вторых,
DLL, содержащая функцию хука, не будет спроектирована на
адресное пространство чужого процесса, пока функция хука
реально не понадобится. В нашем случае хук имеет тип
WH_GETMESSAGE, а значит DLL не загрузится, пока поток не
получит какое-либо сообщение. Поэтому я посылаю ему сообщение
WM_NULL (с кодом 0), чтобы вынудить ОС загрузить DLL.
В-третьих, обратите внимание на применение события для
синхронизации потоков в нашем и целевом процессах. Разумеется,
для этой цели можно использовать и любой другой механизм
синхронизации потоков.