Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Рихтер Дж., Назар К. - Windows via C C++. Программирование на языке Visual C++ - 2009

.pdf
Скачиваний:
6269
Добавлен:
13.08.2013
Размер:
31.38 Mб
Скачать

696 Часть IV. Динамически подключаемые библиотеки

ного разрешения не сохраняются позиции ярлыков на рабочем столе. У меня на рабочем столе масса ярлыков для быстрого доступа к часто используемым программам и файлам. Стоит мне сменить разрешение, размеры рабочего стола изменяются, и ярлыки перестраиваются так, что уже ничего не найдешь. А когда я восстанавливаю прежнее разрешение, ярлыки так и остаются вперемешку. Чтобы навести порядок, приходится вручную перемешать каждый ярлык на свое место — очень интересное занятие!

В общем, мне это так осточертело, что я придумал утилиту, сохраняющую по-

зиции элементов на экране (Desktop Item Position Saver, DIPS). DIPS состоит из крошечного исполняемого файла и компактной DLL. После запуска исполняемого файла появляется следующее окно.

В этом окне поясняется, как работать с утилитой. Когда она запускается с ключом 5 в командной строке, в реестре создается подраздел:

HKEY_CURRENT_USER\Software\Wintellect\Desktop Item Position Saver

куда добавляется по одному параметру на каждый ярлык, расположенный на вашем рабочем столе. Значение каждого параметра — позиция соответствующего ярлыка. Утилиту DIPS следует запускать с ключом S перед установкой более низкого экранного разрешения. Всласть наигравшись и восстановив нормальное разрешение, вновь запустите DIPS — на этот раз с ключом R. Тогда DIPS откроет соответствующий подраздел реестра и восстановит для каждого объекта рабочего стола его исходную позицию.

На первый взгляд утилита DIPS тривиальна и очень проста в реализации. Вроде бы только и надо, что получить описатель элемента управления ListView рабочего стола, заставить его (послав соответствующие сообщения) перечислить все ярлыки и определить их координаты, а потом сохранить полученные данные в реестре. Но попробуйте, и вы убедитесь, что все не так просто. Проблема в том, что большинство оконных сообщений для стандартных элементов управления

(например, LVMGETITEM и LVM_ GETITEMPOSITION) не может преодолеть границы процессов. Почему?

Сообщение LVM_GETITEM требует, чтобы вы передали в параметре lParam адрес структуры LV_ITEM. Поскольку ее адрес имеет смысл лишь в адресном пространстве процесса — отправителя сообщения, процесс-приемник не может безопасно использовать его. Поэтому, чтобы DIPS работала так, как было обещано, в Explorer.exe надо внедрить код, посылающий сообщения LVM_GETITEM и LVMGETITEMPO SITI ON элементу управления ListView рабочего стола.

Глава 22. Внедрение DLL и перехват API-вызовов.docx 697

Примечание. В отличие от новых стандартных элементов управления встроенные (кнопки, поля, метки, списки, комбинированные списки и т. д.) позволяют передавать оконные сообщения через границы процессов. Например, окну списка, созданному каким-нибудь потоком другого процесса, можно послать сообщение LB_GETTEXT, чей параметр lParam указывает на строковый буфер в адресном пространстве процесса-отправителя. Это срабатывает, потому что операционная система специально проверяет, не отправлено ли сообщение LB_GETTEXT, и, если да, сама создает проецируемый в память файл и копирует строковые данные из адресного пространства одного процесса в адресное пространство другого.

Почему Майкрософт решила по-разному обрабатывать встроенные и новые элементы управления? Дело в том, что в 16-разрядной Windows, в которой все приложения выполняются в едином адресном пространстве, любая программа могла послать сообщение LB_GETTEXT окну, созданному другой программой. Чтобы упростить перенос таких приложений в Win32, Майкрософт и пошла на эти ухищрения. А поскольку в 16-разрядной Windows нет новых элементов управления, то проблемы их переноса тоже нет, и Майкрософт ничего подобного для них делать не стала.

Сразу после запуска DIPS получает описатель окна элемента управления List View рабочего стола:

// окно ListView рабочего стола - "внук" окна ProgMan hWndLV = GetFirstChild(

GetFirstChild(FindWindow(TEXT("ProgMan"), NULL)));

Этот код сначала ищет окно класса ProgMan. Даже несмотря на то что никакой Program Manager не запускается, новая оболочка по-прежнему создает окно этого класса — для совместимости с приложениями, рассчитанными на старые версии Windows. У окна ProgMan единственное дочернее окно класса SHELLDLL_DetView, у которого тоже одно дочернее окно — класса SysListView32. Оно-то и служит элементом управления List View рабочего стола. (Кстати, всю эту информацию я выудил благодаря Spy++.)

Получив описатель окна List View, я определяю идентификатор создавшего его потока, для чего вызываю GetWindowThreadProcessId. Этот идентификатор я передаю функции SetDIPSHook, реализованной в DIPSLib.cpp. Последняя функция устанавливает ловушку WH_GETMESSAGE для данного потока и вызывает:

PostThreadMessage(dwThreadId, WM_NULL, 0, 0);

чтобы разбудить поток Windows Explorer. Поскольку для него установлена ловушка WH_GETMESSAGE, операционная система автоматически внедряет мою

DIPSLib.dll в адресное пространство Explorer и вызывает

мою

функцию

GetMsgProc. Та сначала проверяет, впервые ли она вызвана, и,

если да, создает

скрытое окно с заголовком «Richter DIPS». Возьмите на

заметку,

698 Часть IV. Динамически подключаемые библиотеки

что это окно создается потоком, принадлежащим Explorer. Пока окно создается, поток DIPS.exe возвращается из функции SetDIPSHook и вызывает:

GetMessage(&msg, NULL, 0, 0);

Этот вызов «усыпляет» поток до появления в очереди какого-нибудь сообщения. Хотя DIPS.exe сам не создает ни одного окна, у него все же есть очередь сообщений, и они помещаются туда исключительно в результате вызовов PostThreadMessage. Взгляните на код GetMsgProc в DIPSLib.cpp: сразу после обращения к CreateDialog стоит вызов PostThreadMessage, который вновь пробуждает поток DIPS.exe. Идентификатор потока сохраняется в разделяемой переменной внутри функции SetDIPSHook.

Очередь сообщений я использую для синхронизации потоков. В э4ом нет ничего противозаконного, и иногда гораздо проще синхронизировать потоки именно так, не прибегая к объектам ядра — мьютексам, семафорам, событиям и т. д. (В Windows очень богатый API; пользуйтесь этим.)

Когда поток DIPS.exe пробуждается, он узнает, что серверное диалоговое окно уже создано, и обращается к FindWindow, чтобы получить его описатель. С этого момента для организации взаимодействия между клиентом (утилитой DIPS) и сервером (скрытым диалоговым окном) можно использовать механизм оконных сообщений. Поскольку это диалоговое окно создано потоком, выполняемым в контексте процесса Explorer, нас мало что ограничивает в действиях с Explorer.

Чтобы сообщить своему диалоговому окну сохранить или восстановить позиции ярлыков на экране, достаточно послать сообщение:

//сообщаем окну DIPS, с каким окном ListView работать

//и что делать: сохранять или восстанавливать позиции ярлыков

SendMessage(hWndDIPS, WM_APP, (WPARAM) hWndLV, bSave);

Процедура диалогового окна проверяет сообщение WM_APP. Когда она принимает это сообщение, параметр wParam содержит описатель нужного элемента управления ListView, а lParam — булево значение, определяющее, сохранять текущие позиции ярлыков в реестре или восстанавливать.

Так как здесь используется SendMessage, а не PostMessage, управление не передается до завершения операции. Если хотите, определите дополнительные сообщения для процедуры диалогового окна — это расширит возможности программы в управлении Explorer. Закончив, я завершаю работу сервера, для чего посылаю ему сообщение WM_CLOSE, которое говорит диалоговому окну о необходимости самоуничтожения.

Наконец, перед своим завершением DIPS вновь вызывает SetDIPSHook, но на этот раз в качестве идентификатора потока передается 0. Получив нулевое значение, функция снимает ловушку WH_GETMESSAGE. А когда ловушка удаляется, операционная система автоматически выгружает DIPSLib.dll из адресного пространства процесса Explorer, и это означает, что теперь процедура диалогового окна больше не принадлежит данному адрес-

Глава 22. Внедрение DLL и перехват API-вызовов.docx 699

ному пространству. Поэтому важно уничтожить диалоговое окно заранее — до снятия ловушки. Иначе очередное сообщение, направленное диалоговому окну, вызовет нарушение доступа. И тогда Explorer будет аварийно завершен операционной системой — с внедрением DLL шутки плохи!

DIPSLib.cpp

/****************************************************************************** Module: DIPS.cpp

Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre

******************************************************************************/

#include "..\CommonFiles\CmnHdr.h" /* See Appendix A. */ #include <WindowsX.h>

#include <tchar.h> #include "Resource.h"

#include "..\22-DIPSLib\DIPSLib.h"

///////////////////////////////////////////////////////////////////////////////

BOOL Dlg_OnInitDialog(HWND hWnd, HWND hWndFocus, LPARAM lParam) {

chSETDLGICONS(hWnd, IDI_DIPS); return(TRUE);

}

///////////////////////////////////////////////////////////////////////////////

void Dlg_OnCommand(HWND hWnd, int id, HWND hWndCtl, UINT codeNotify) {

switch (id) { case IDC_SAVE:

case IDC_RESTORE: case IDCANCEL:

EndDialog(hWnd, id); break;

}

}

///////////////////////////////////////////////////////////////////////////////

INT_PTR WINAPI Dlg_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {

switch (uMsg) {

chHANDLE_DLGMSG(hWnd, WM_INITDIALOG,

Dlg_OnInitDialog);

chHANDLE_DLGMSG(hWnd, WM_COMMAND,

Dlg_OnCommand);

}

 

700 Часть IV. Динамически подключаемые библиотеки

return(FALSE);

}

///////////////////////////////////////////////////////////////////////////////

int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR pszCmdLine, int) {

// Convert command-line character to uppercase. CharUpperBuff(pszCmdLine, 1);

TCHAR cWhatToDo = pszCmdLine[0];

if ((cWhatToDo != TEXT('S')) && (cWhatToDo != TEXT('R'))) {

// An invalid command-line argument; prompt the user. cWhatToDo = 0;

}

if (cWhatToDo == 0) {

//No command-line argument was used to tell us what to

//do; show usage dialog box and prompt the user.

switch (DialogBox(hInstExe, MAKEINTRESOURCE(IDD_DIPS), NULL, Dlg_Proc)) { case IDC_SAVE:

cWhatToDo = TEXT('S'); break;

case IDC_RESTORE: cWhatToDo = TEXT('R'); break;

}

}

if (cWhatToDo == 0) {

// The user doesn't want to do anything. return(0);

}

//The Desktop ListView window is the grandchild of the ProgMan window. HWND hWndLV = GetFirstChild(GetFirstChild(

FindWindow(TEXT("ProgMan"), NULL))); chASSERT(IsWindow(hWndLV));

//Set hook that injects our DLL into the Explorer's address space. After

//setting the hook, the DIPS hidden modeless dialog box is created. We

//send messages to this window to tell it what we want it to do. chVERIFY(SetDIPSHook(GetWindowThreadProcessId(hWndLV, NULL)));

Глава 22. Внедрение DLL и перехват API-вызовов.docx 701

//Wait for the DIPS server window to be created.

MSG msg;

GetMessage(&msg, NULL, 0, 0);

//Find the handle of the hidden dialog box window.

HWND hWndDIPS = FindWindow(NULL, TEXT("Wintellect DIPS"));

//Make sure that the window was created. chASSERT(IsWindow(hWndDIPS));

//Tell the DIPS window which ListView window to manipulate

//and whether the items should be saved or restored.

BOOL bSave = (cWhatToDo == TEXT('S'));

SendMessage(hWndDIPS, WM_APP, (WPARAM) hWndLV, bSave);

//Tell the DIPS window to destroy itself. Use SendMessage

//instead of PostMessage so that we know the window is

//destroyed before the hook is removed. SendMessage(hWndDIPS, WM_CLOSE, 0, 0);

//Make sure that the window was destroyed. chASSERT(!IsWindow(hWndDIPS));

//Unhook the DLL, removing the DIPS dialog box procedure

//from the Explorer's address space.

SetDIPSHook(0);

return(0);

}

//////////////////////////////// End of File //////////////////////////////////

DIPSLib.cpp

/****************************************************************************** Module: DIPSLib.cpp

Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre

******************************************************************************/

#include "..\CommonFiles\CmnHdr.h" /* See Appendix A. */ #include <WindowsX.h>

#include <CommCtrl.h>

#define DIPSLIBAPI __declspec(dllexport) #include "DIPSLib.h"

#include "Resource.h"

702 Часть IV. Динамически подключаемые библиотеки

///////////////////////////////////////////////////////////////////////////////

#ifdef _DEBUG

// This function forces the debugger to be invoked void ForceDebugBreak() {

__try { DebugBreak(); } __except(UnhandledExceptionFilter(GetExceptionInformation())) { }

}

#else

#define ForceDebugBreak() #endif

///////////////////////////////////////////////////////////////////////////////

// Forward references

LRESULT WINAPI GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam);

INT_PTR WINAPI Dlg_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

///////////////////////////////////////////////////////////////////////////////

//Instruct the compiler to put the g_hHook data variable in

//its own data section called Shared. We then instruct the

//linker that we want to share the data in this section

//with all instances of this application.

#pragma data_seg("Shared") HHOOK g_hHook = NULL; DWORD g_dwThreadIdDIPS = 0; #pragma data_seg()

//Instruct the linker to make the Shared section

//readable, writable, and shared.

#pragma comment(linker, "/section:Shared,rws")

///////////////////////////////////////////////////////////////////////////////

// Nonshared variables HINSTANCE g_hInstDll = NULL;

///////////////////////////////////////////////////////////////////////////////

BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD fdwReason, PVOID fImpLoad) {

switch (fdwReason) {

case DLL_PROCESS_ATTACH:

Глава 22. Внедрение DLL и перехват API-вызовов.docx 703

// DLL is attaching to the address space of the current process. g_hInstDll = hInstDll;

break;

case DLL_THREAD_ATTACH:

// A new thread is being created in the current process. break;

case DLL_THREAD_DETACH:

// A thread is exiting cleanly. break;

case DLL_PROCESS_DETACH:

// The calling process is detaching the DLL from its address space. break;

}

return(TRUE);

}

///////////////////////////////////////////////////////////////////////////////

BOOL WINAPI SetDIPSHook(DWORD dwThreadId) {

BOOL bOk = FALSE;

if (dwThreadId != 0) {

//Make sure that the hook is not already installed. chASSERT(g_hHook == NULL);

//Save our thread ID in a shared variable so that our GetMsgProc

//function can post a message back to the thread when the server

//window has been created.

g_dwThreadIdDIPS = GetCurrentThreadId();

// Install the hook on the specified thread

g_hHook = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, g_hInstDll, dwThreadId);

bOk = (g_hHook != NULL); if (bOk) {

//The hook was installed successfully; force a benign message to

//the thread's queue so that the hook function gets called.

bOk = PostThreadMessage(dwThreadId, WM_NULL, 0, 0);

}

} else {

704Часть IV. Динамически подключаемые библиотеки

//Make sure that a hook has been installed. chASSERT(g_hHook != NULL);

bOk = UnhookWindowsHookEx(g_hHook); g_hHook = NULL;

}

return(bOk);

}

///////////////////////////////////////////////////////////////////////////////

LRESULT WINAPI GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam) { static BOOL bFirstTime = TRUE;

if (bFirstTime) {

//The DLL just got injected. bFirstTime = FALSE;

//Uncomment the line below to invoke the debugger

//on the process that just got the injected DLL.

//ForceDebugBreak();

//Create the DIPS Server window to handle the client request. CreateDialog(g_hInstDll, MAKEINTRESOURCE(IDD_DIPS), NULL, Dlg_Proc);

//Tell the DIPS application that the server is up

//and ready to handle requests.

PostThreadMessage(g_dwThreadIdDIPS, WM_NULL, 0, 0);

}

return(CallNextHookEx(g_hHook, nCode, wParam, lParam));

}

///////////////////////////////////////////////////////////////////////////////

void Dlg_OnClose(HWND hWnd) {

DestroyWindow(hWnd);

}

///////////////////////////////////////////////////////////////////////////////

static const TCHAR g_szRegSubKey[] = TEXT("Software\\Wintellect\\Desktop Item Position Saver");

///////////////////////////////////////////////////////////////////////////////

Глава 22. Внедрение DLL и перехват API-вызовов.docx 705

void SaveListViewItemPositions(HWND hWndLV) {

int nMaxItems = ListView_GetItemCount(hWndLV);

//When saving new positions, delete the old position

//information that is currently in the registry.

LONG l = RegDeleteKey(HKEY_CURRENT_USER, g_szRegSubKey);

// Create the registry key to hold the info HKEY hkey;

l = RegCreateKeyEx(HKEY_CURRENT_USER, g_szRegSubKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hkey, NULL);

chASSERT(l == ERROR_SUCCESS);

for (int nItem = 0; nItem < nMaxItems; nItem++) {

// Get the name and position of a ListView item. TCHAR szName[MAX_PATH];

ListView_GetItemText(hWndLV, nItem, 0, szName, _countof(szName));

POINT pt;

ListView_GetItemPosition(hWndLV, nItem, &pt);

// Save the name and position in the registry.

l = RegSetValueEx(hkey, szName, 0, REG_BINARY, (PBYTE) &pt, sizeof(pt)); chASSERT(l == ERROR_SUCCESS);

}

RegCloseKey(hkey);

}

///////////////////////////////////////////////////////////////////////////////

void RestoreListViewItemPositions(HWND hWndLV) {

HKEY hkey;

LONG l = RegOpenKeyEx(HKEY_CURRENT_USER, g_szRegSubKey, 0, KEY_QUERY_VALUE, &hkey);

if (l == ERROR_SUCCESS) {

// If the ListView has AutoArrange on, temporarily turn it off. DWORD dwStyle = GetWindowStyle(hWndLV);

if (dwStyle & LVS_AUTOARRANGE)

SetWindowLong(hWndLV, GWL_STYLE, dwStyle & ~LVS_AUTOARRANGE);

l = NO_ERROR;

for (int nIndex = 0; l != ERROR_NO_MORE_ITEMS; nIndex++) {

Соседние файлы в предмете Программирование на C++