Написание экстра-маленьких Win32 приложений на С++
от 1 КБ используя лишь API, на примере программы Windows
Hider
Введение Натыкаясь в Интернете на
довольно интересные программы, я часто не решался их закачивать после
того, как узнавал их размер. Какую ни возьми - все огромные. Да и ресурсов
системных потребляют немало. В этой статье я расскажу о том, как сделать
программу в среднем в 10 - 100 раз меньше размером, чем попадаются
аналогичные.
Цель Написать очень быструю и
маленькую программу, скрывающую по CTRL+F12 заданные окна. При нажатии
комбинации CTRL+F10 она должна показать спрятанные окна. Входные
данные: TXT Файл вида
------------
Internet Explorer
The Bat!
Visual C++
911
------------
Если будут найдены окна, содержащие в своем заголовке
указанные строки, они будут спрятаны. В вышеуказанном примере будут
спрятаны все окна IE, окно Microsoft Visual C++, окно почтовой программы
"The Bat!" и все окна, в заголовках которых содержится комбинация символов
"911". Итак, писать будем на чистом Win32 API. Создадим
окно, привяжем к нему горячие клавиши. По требованию будем осуществлять
перебор видимых окон в системе и в заголовке каждого будем искать заданные
комбинации символов.
Опции линкера Если ничего не
предпринимать, то нам не удастся получить в итоге файл менее 32
КБ(примерно). Поэтому пишем: #pragma comment(linker,"/MERGE:.rdata=.text")
#pragma comment(linker,"/FILEALIGN:512 /SECTION:.text,EWRX
/IGNORE:4078")
#pragma comment(linker,"/ENTRY:New_WinMain")
#pragma comment(linker,"/NODEFAULTLIB")
На что теперь стоит
обратить особое внимание? Обычно точка входа в программу выглядит
так: int WINAPI WinMain(HINSTANCE hInst,HINSTANCE hPrevInst,LPSTR
szCmdLine,int nCmdShow) (кстати, для Win32 приложений второй
параметр всегда NULL)
Но(!)... Так как мы отключили "Runtime library", нам
теперь передается в этих параметрах разный мусор. Поэтому называем точку
входа не WinMain а New_WinMain, которую объявим, как void
New_WinMain(void), чтобы не забыть о том, что нам ничего не передается. А
параметр HINSTANCE получаем функцией GetModuleHandle(NULL). Ах да, и
выходить из программы будем функцией ExitProcess. Теперь
если собрать нашу пустую программку, которая ничего делать не будет,
размер ее будет 1 Кб. Но нам нужно еще дописать 3 Кб кода. Продолжим.
Чтобы все дальнейшее было понятно даже новичку в
программировании под Windows, я прокомментирую все.
Объявим кое-какие константы Это понадобится для регистрации
"горячих" клавиш функцией RegisterHotKey. #define HOTKEYHIDE
1 #define HOTKEYSHOW 2
Размер буффера, куда будет
считываться заголовок окна функцией GetWindowText. #define SSZZ
256
Размер буфера, куда будет считываться файл со стоками
фильтрации (используется в объявлении char
FilterStrings[MAXFIL];) #define MAXFIL 1024 (Примечание: При
желании можно сделать и выделение памяти динамически - найти файл, узнать
его размер и выделить блок. Приблизительный пример:
// .....................
WIN32_FIND_DATA FindData;
HANDLE hFind=FindFirstFile(szFilterStringsFile,&FindData);
if (hFind!=INVALID_HANDLE_VALUE)
{
i=(FindData.nFileSizeHigh * MAXDWORD) + FindData.nFileSizeLow;
HGLOBAL hGA=GlobalAlloc(GMEM_ZEROINIT|GMEM_MOVEABLE,i+1);
// (+ end-ZERO)
if (hGA!=NULL)
{
LPVOID lpStrings=GlobalLock(hGA);
DWORD dw;
if (lpStrings!=NULL) ReadFile(hFile,lpStrings,i,&dw,NULL);
}
}
FindClose(hFind);
CloseHandle(hFile);
// ...............................
// Но так как вряд ли файл настроек у нас будет больше одного
// килобайта, я оставил статичный массив.
)
Зададим глобальные переменные Массив хендлов окон (вряд ли
будет у нас более 300 окон) HWND aHwnd[300];
Кол-во
инициализированных элементов в этом массиве unsigned int
cHwnd=0;
Дескрипторы окон - главное и два дочерних - кнопка
"Hide" и кнопка "Edit filter strings" HWND hwndMain, hwndButtonHide,
hwndButtonEditFilter;
Тут будет что-то типа
"c:programswinhiderwinhider.settings.txt" char
szFilterStringsFile[MAX_PATH]="(с)2002
KMiNT21";
Соответственно, хендл файла с именем "что-то
типа" HANDLE hFile;
А это место, куда будем считывать все
из этого файла char FilterStrings[MAXFIL];
Функции Обработка сообщений главного окна LRESULT CALLBACK
MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM
lParam);
Функция, которая будет вызываться для каждого окна
при переборе всех окон static BOOL FAR PASCAL my_EnumWindowsProc(HWND
hwnd, DWORD lParam);
Проверка наличия строки str2 в
str1 BOOL Contain(char* str1, char* str2);
Скрывание с
экрана очередного окна inline void HideNext(HWND hwnd){
ShowWindow(aHwnd[cHwnd++]=hwnd,SW_HIDE); }
Возврат всех
спрятанных окон на экран inline void ShowAll(void) { while(cHwnd)
ShowWindow(aHwnd[--cHwnd],SW_SHOW);}
Пройдемся по главным строкам функции NewWinMain * Получим
INSTANCE модуля. Это нам нужно для регистрации оконного
класса HINSTANCE hInst=GetModuleHandle(NULL);
*
Зарегистрируем оконный класс WNDCLASS wc;
wc.style = CS_HREDRAW|CS_VREDRAW ;
wc.lpfnWndProc = (WNDPROC)MainWndProc;
wc.hInstance = hInst;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW);
wc.lpszClassName = "CKMINT21WINDOWSHIDERPRO";
wc.hCursor = LoadCursor(NULL,IDC_ARROW);
wc.hIcon = LoadIcon(NULL,IDI_APPLICATION);
wc.lpszMenuName=NULL;
wc.cbClsExtra=0;
wc.cbWndExtra=0;
if (!RegisterClass(&wc)) MessageBox(0,"I can't register window
class.","Error:",0), ExitProcess(0);
* Создаем
главное окно приложения hwndMain=CreateWindow(wc.lpszClassName,"Small
windows hider!", WS_BORDER|WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX,
CW_USEDEFAULT,0,291,180, NULL, NULL, hInst, NULL);
И
помещаем на него две кнопки. Как видим, кнопки имеют класс "BUTTON". Они
являются дочерними окну
hwndMain. hwndButtonHide=CreateWindow("BUTTON","Hide!", WS_VISIBLE |
WS_CHILD , 10,10,261,90, hwndMain, NULL, hInst,
NULL); ShowWindow(hwndButtonHide,SW_SHOW),
UpdateWindow(hwndButtonHide); hwndButtonEditFilter=CreateWindow("BUTTON","Edit
filters", WS_VISIBLE|WS_CHILD|WS_BORDER|WS_TABSTOP , 10,110,261,30,
hwndMain, NULL, hInst, NULL); ShowWindow(hwndButtonEditFilter,SW_SHOW),
UpdateWindow(hwndButtonEditFilter);
Наконец, показываем
главное окно ShowWindow(hwndMain,SW_SHOW),
UpdateWindow(hwndMain);
Примечание: Так как кто-то этого может не знать, хочу
отметить, что в языке С++ есть "операция следования" - запятая. Т.е.
просто последовательно выполнятся обе функции ShowWindow и UpdateWindow
(как отдельный блок). В вышеуказанной строке можно было бы и просто
поставить ";", а вообще иногда это помогает избавиться от огромного
количества фигурных скобок {}, в тексте программы.
* Затем регистрируем
в системе HotKeys. Они будут привязаны к главному окну, которому будут
передаватся сообщения
WM_HOTKEY. RegisterHotKey(hwndMain,HOTKEYHIDE,MOD_CONTROL,VK_F12) RegisterHotKey(hwndMain,HOTKEYSHOW,MOD_CONTROL,VK_F10)
*
Затем считываем настройки из файла и запускаем главный цикл обработки
оконных сообщений для текущего процесса. MSG
msg; while(GetMessage(&msg,NULL,0,0)) TranslateMessage(&msg),
DispatchMessage(&msg);
Оконная процедура
// Тут все довольно стандартно. Делаем switch (msg).
// ...
case WM_HOTKEY:
if (HOTKEYSHOW == (int)wParam)
// показываем все, что мы до этого прятали, а так же главное
// окно программы
ShowAll(), ShowWindow(hwnd,SW_SHOW);
if (HOTKEYHIDE == (int)wParam)
// Скрываем наше главное окно и запускаем перебор всех окон в
// системе - EnumWindows. Теперь будет вызываться функция
// my_EnumWindowsProc для каждого обнаруженного в системе окна.
ShowWindow(hwnd,SW_HIDE), EnumWindows((int (__stdcall *)(struct
HWND__ *,long))my_EnumWindowsProc, 0);
break;
// ...
// Если программу пытаются минимизировать, просто скрываем ее
// .........................
case WM_SYSCOMMAND:
if(SC_MINIMIZE == wParam) { ShowWindow(hwnd,SW_HIDE); return 0; }
break;
// Внимание, после ShowWindow(hwnd,SW_HIDE) мы пишем return 0,
// вместо break. Почему? Да потому что не хотим, чтобы это
// сообщение пошло дальше в систему. Мы его уже обработали
// по-своему.
// ...
// А затем обрабатываем нажатия на кнопки.
case BN_CLICKED:
if (hwndButtonHide==(HWND)lParam)ShowWindow(hwndMain,SW_HIDE);
if (hwndButtonEditFilter==(HWND)lParam)ShellExecute(NULL,"open",
szFilterStringsFile,NULL,NULL,SW_SHOWMAXIMIZED);
break;
Рассмотрим функцию my_EnumWindowsProc Пропустим
все невидимые окна if (!IsWindowVisible(hwnd)) return
TRUE;
Получим TITLE очередного окна GetWindowText(hwnd,
szWindowsTitle, SSZZ)
Затем перебираем все стоки из файла
настроек for(i=0;i // если это начало строки, то
{
if (Contain(szWindowsTitle, FilterStrings+i)) HideNext(hwnd);
// скроем окно, если эта строка содержится в szWindowsTitle
while(FilterStrings[i]) i++;
// сместим указатель на следующий 0
} Продолжаем дальнейший перебор окон return
TRUE; (Если бы было return FALSE, перебор бы
закончился.)
В остальных функциях особо описывать
нечего. |