Вопрос на засыпку - как ты, неизвестный читатель, начинал учиться программировать? Я имею ввиду не те скучные пары в школе/университете, а момент, когда появилось желание и интерес заняться кодингом в отрыве от необходимости - просто для себя?
Полагаю, ответов может быть бесконечное множество - кто-то и вправду подхватил интерес в некоем образовательном учреждении, кого-то посвятили в секту кодеров товарищи, а кто-то и вообще программировал с пеленок)
В моем же случае интерес пробудился отчасти благодаря занятным книгам М. Фленова, которые может и не несут особенно полезной смысловой нагрузки, но более чем наглядно демонстрируют неофиту, что между пропасть между теорией и практикой вовсе не такая непреодолимая) Именно один из его примеров-шуток для новичков я опишу далее.
Итак, каков наш замысел? Написать простенькую программку, которая охватит различные технические сферы системного программирование под windows, да к тому же еще и подарит несколько приятных минут) Дайте подумать...пусть это будет развеселый баян для параноиков, под названием "I see you...". Выглядеть будет следующим образом - после запуска нашей программки при открытии любого окна его заголовок на радость удивленному пользователю будет меняться на текст "Я за тобой наблюдаю..." (ну мы ведь пишем для русскоговорящей аудитории, я прав?)). Теперь вкратце о том, как это можно реализовать.
Для того, чтобы работать с win окнами, мы будем использовать Windows API (application programming interfaces), который предоставит все необходимые инструменты для взаимодействия с системой. Код программы будем писать на старом добром C++, а чтобы продемонстрировать, что есть жизнь вне Visual Studio, в качестве IDE выберем CodeLite.
Итак, для начала создаем проект executable...хм, хотя еще раньше мы установили CodeLite) Заранее предупрежу, что эта IDE предоставляется в нескольких версиях и скорее всего лучше будет скачать ту, что содержит в себе MinGW (да,да не пугайтесь, собирать наше win32 приложение мы будем с помощью g++)).
Итак, создаем проект executable с компилятором g++. Для этого создаем новое рабочее пространство (Workspace -> New Workspace) и в нем новый проект (Workspace -> New Project)
Здесь мы видим подключение заголовочного файла Windows.h, который и содержит все функции для работы с системой, которые нам понадобятся в дальнейшем, а также точку входа в программу WinMain. Ее параметры описывать не буду, т.к. сейчас они не важны, но если вас гложет любопытство, то эту и много другой полезной инфы можно обнаружить естественно на MSDN.
Теперь внимательно поглядим на любопытную функцию EnumWindows
Она прекрасно подойдет для наших замыслов, т.к. позволяет перебрать все окна и для каждого выполнить функцию lpEnumFunc с параметром lParam.
Использовать эту функцию будем следующим образом
Полагаю, ответов может быть бесконечное множество - кто-то и вправду подхватил интерес в некоем образовательном учреждении, кого-то посвятили в секту кодеров товарищи, а кто-то и вообще программировал с пеленок)
В моем же случае интерес пробудился отчасти благодаря занятным книгам М. Фленова, которые может и не несут особенно полезной смысловой нагрузки, но более чем наглядно демонстрируют неофиту, что между пропасть между теорией и практикой вовсе не такая непреодолимая) Именно один из его примеров-шуток для новичков я опишу далее.
Итак, каков наш замысел? Написать простенькую программку, которая охватит различные технические сферы системного программирование под windows, да к тому же еще и подарит несколько приятных минут) Дайте подумать...пусть это будет развеселый баян для параноиков, под названием "I see you...". Выглядеть будет следующим образом - после запуска нашей программки при открытии любого окна его заголовок на радость удивленному пользователю будет меняться на текст "Я за тобой наблюдаю..." (ну мы ведь пишем для русскоговорящей аудитории, я прав?)). Теперь вкратце о том, как это можно реализовать.
Для того, чтобы работать с win окнами, мы будем использовать Windows API (application programming interfaces), который предоставит все необходимые инструменты для взаимодействия с системой. Код программы будем писать на старом добром C++, а чтобы продемонстрировать, что есть жизнь вне Visual Studio, в качестве IDE выберем CodeLite.
Итак, для начала создаем проект executable...хм, хотя еще раньше мы установили CodeLite) Заранее предупрежу, что эта IDE предоставляется в нескольких версиях и скорее всего лучше будет скачать ту, что содержит в себе MinGW (да,да не пугайтесь, собирать наше win32 приложение мы будем с помощью g++)).
Итак, создаем проект executable с компилятором g++. Для этого создаем новое рабочее пространство (Workspace -> New Workspace) и в нем новый проект (Workspace -> New Project)
Уж не помню, будут ли там заготовки кода - в любом случае убираем их, и создаем файл с кодом для win32 программы.
#include <Windows.h> int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { return 0; }
Здесь мы видим подключение заголовочного файла Windows.h, который и содержит все функции для работы с системой, которые нам понадобятся в дальнейшем, а также точку входа в программу WinMain. Ее параметры описывать не буду, т.к. сейчас они не важны, но если вас гложет любопытство, то эту и много другой полезной инфы можно обнаружить естественно на MSDN.
Теперь внимательно поглядим на любопытную функцию EnumWindows
BOOL WINAPI EnumWindows( __in WNDENUMPROC lpEnumFunc, __in LPARAM lParam );
Она прекрасно подойдет для наших замыслов, т.к. позволяет перебрать все окна и для каждого выполнить функцию lpEnumFunc с параметром lParam.
Использовать эту функцию будем следующим образом
EnumWindows(&ChangeNameEnum,0);
где функция, на указатель на которую передаем, выглядит так
BOOL CALLBACK ChangeNameEnum(HWND hwnd,LPARAM lparam){ SendMessageW(hwnd, WM_SETTEXT, 0, LPARAM(L"Я за тобой наблюдаю...")); return true; }
Небольшое пояснение. Эта функция принимает на вход описатель hwnd окна, для которого вызывается, и параметры lParam. Ее предназначение - посылать нужному окну сообщение, которое говорит, что надо установить новое значение заголовка. Для справки скажу, что все события окна (например, открытие и закрытие), вызываются сообщениями, которые отправляет система или пользовательское приложение. Функция SendMessageW как раз и посылает сообщение нужному окну. W в конце названия функции означает, что она работает со строками в формате unicode (wide char) и мы можем передавать кириллицу. Т.к. параметр для сообщения должен быть типа LPARAM, то мы используем приведение типов и заодно префиксом L указываем, что передаваемая строка должна восприниматься как unicode. Уф, вроде все...
Итого, полученный код перебирает все окна в системе и отправляет сообщение о смене заголовка. Но вообще-то мы хотели, чтобы заголовки менялись постоянно, а не однократно. Можно, конечно, вызывать EnumWindows в бесконечном цикле - но это совсем не наш подход) Поэтому будем продолжать усложнять...
Итак, мы хотим, чтобы каждое новое окно меняло заголовок. В этом случае нам надо перехватывать сообщение о создании нового окна и выполнять свои темные делишки. Не будем выдумывать сплайсинг и модификацию системных библиотек - все уже украдено до нас. Существует функция SetWindowsHookEx, которая ставит хук (хм...ну перехватчик что ли)) на нужный тип сообщений.
HHOOK WINAPI SetWindowsHookEx( __in int idHook, __in HOOKPROC lpfn, __in HINSTANCE hMod, __in DWORD dwThreadId );
Одна беда - мы хотим, чтобы наш хук был глобальный. В этом случае процедура обработки хука HOOKPROC lpfn должна располагаться в dll библиотеке. Для справки
DLL (англ. dynamic-link library — «библиотека динамической компоновки», «динамически подключаемая библиотека») — понятие операционных систем Microsoft Windows и IBM OS/2, динамическая библиотека, позволяющая многократное использование различными программными приложениями.Грубо говоря, это бинарный файл, содержащий набор функций, которые прикладная может вызвать, если подключит библиотеку. Собственно, это и вытекает из глобальности нашего хука - система будет инжектить библиотеку с функцией lpfn в каждый нужный процесс системы.
Поехали)
Создаем в CodeLite проект dynamic library с g++.
CodeLite - кроссплатформенная IDE, поэтому бинарный файл она создаст с расширением *.so - все-таки есть смысл поменять его на *.dll в настройках проекта.
Какой код будет содержать библиотека? Ну как минимум, следующий
#include <Windows.h> extern "C" __declspec(dllexport) BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
DllMain - точка входа для библиотеки. Она будет вызываться каждый раз, когда библиотека будет подгружаться/выгружаться в адресное пространство процесса/потока. А теперь добавим две функции, которые библиотека будет экспортировать
#pragma data_seg("SHARED") HHOOK hook; #pragma data_seg() #pragma comment(linker, "/SECTION:SHARED,RWS") HINSTANCE instance; extern "C" __declspec(dllexport) void installHook() { hook = SetWindowsHookEx(WH_CBT,WndHook, instance, 0); if(hook==0){ //error } } extern "C" __declspec(dllexport) void uninstallHook() { if(UnhookWindowsHookEx(hook)==0){ //error } }и немного поправим код в DllMain
switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: instance=hModule; break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; }Итак, для начала мы объявили нужные переменные - HOOK hook и HINSTANCE instance - которые представляют собой соответственно объект самого хука и экземпляр нашей библиотеки. Первый возвращает нам SetWindowsHookEx, второй получаем из параметра hModule для точки входа. Т.к. объектом хука необходимо пользоваться не только самой библиотеке, но и всем остальным процессам, которые мы ловим, то необходимо объявить его в общем сегменте данных под названием SHARED. Это делается с помощью директив препроцессора - data_seg создает нужную секцию и все переменные, расположенные до закрытия data_seg будут помещаться в нее, а comment указывает линковщику, что секция будет доступной для чтения/записи.
Хм...так, что еще не осветил? Самое главное пока осталось в тени)
SetWindowsHookEx мы вызываем с четырьмя параметрами
- первый из которых WH_CBT указывает, что мы будем ловить сообщения для окон
- второй WndHook - указывает функцию, вызываемую для каждого пойманного сообщения
- третий instance - содержит экземпляр библиотеки, где расположена WndHook
- четвертый 0 - говорит, что хук будет глобальный
Теперь кульминация всего этого безобразия - функция, которая будет вызываться при каждом отлове сообщения окну и менять заголовок.
BOOL CALLBACK ChangeNameEnum(HWND hwnd,LPARAM lparam){ SendMessageW(hwnd, WM_SETTEXT, 0, LPARAM(L"Я за тобой наблюдаю...")); return true; } LRESULT CALLBACK WndHook(int code,WPARAM wParam,LPARAM lParam) { if(code==HCBT_CREATEWND){ EnumWindows(&ChangeNameEnum,0); } return CallNextHookEx(hook, code, wParam, lParam); }Собственно, что мы здесь делаем. Для начала проверяем код отловленного сообщения и если он совпадает с нужным (HCBT_CREATEWND - константа, определенная в Windows.h, указывает, что сообщение должно создать окно), то просто меняем заголовки у всех окон в системе. Затем в любом случае нам надо передать выполнение следующему хуку - иначе могут произойти некоторые неприятные моменты, да и вообще это будет верх неприличия)
В качестве завершающего аккорда для библиотеки есть смысл создать def-файл, который будет содержать имена всех экспортируемых функций. Прямо в проекте создаем новый файл и обзываем его library.def, где пишем следующее
LIBRARY ISeeYouHook EXPORTS installHook @1 DllMain @2 uninstallHook @3
Собираем библиотеку и выходим на финишную прямую. Теперь нам нужно написать загрузчик, который будет вызывать функцию установки хука из библиотеки. Для этого вновь понадобится создать Simple executable с g++ и написать следующий код
#include <Windows.h> typedef void (*InstallHook)(); typedef void (*UninstallHook)(); int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { HINSTANCE library = LoadLibraryW(L"ISeeYouHook.dll"); if (library == NULL) { return 1; } InstallHook install = (InstallHook) GetProcAddress(library, "installHook"); UninstallHook uninstall = (UninstallHook) GetProcAddress(library, "uninstallHook"); install(); Sleep(30000); uninstall(); FreeLibrary(library); return 0; }Здесь описана простая загрузка созданной нами библиотеки с названием ISeeYouHook.dll функцией LoadLibraryW и вызов экспортируемых функций, для каждой из которых мы создаем свой тип (InstallHook/UninstallHook) и свои переменные, с помощью которых будем производить вызов. Хук будет жить на протяжении всего времени работы нашего загрузчика, т.к. после его завершения все ресурсы будут освобождены. Соответственно, нам надо продлить это время путем вызова функции Sleep, которая принимает на вход количество миллисекунд для сна.
Вот, собственно, и все) Компилируем загрузчик, копируем dll библиотеку рядом с исполняемым файлом в директории Debug/Release, запускаем, любуемся результатом).
Полный код можно обнаружить на github.



0коммент.:
Отправить комментарий