Программирование для Windows NT© Александр Фролов, Григорий ФроловТом 26, часть 1, М.: Диалог-МИФИ, 1996, 272 стр. Приложение VIRTUALДля демонстрации описанных выше функций, работающих со страницами виртуальной памяти, мы подготовили исходные тексты приложения VIRTUAL. Это приложение получает из адресного пространства приложения одну страницу виртуальной памяти и позволяет вам вручную устанавливать тип доступа для нее, проверяя результат на операциях чтения, записи и фиксирования страницы. При помощи меню Set protection (рис. 1.14) вы можете установить для полученной страницы памяти тип доступа PAGE_NOACCES, PAGE_READONLY XE "PAGE_READONLY" , PAGE_READWRITE XE "PAGE_READWRITE" , а также комбинацию типов доступа PAGE_READWRITE и PAGE_GUARD XE "PAGE_GUARD" . Рис. 1.14. Меню Set protection, предназначенное для установки типа доступа При помощи строк меню Memory (рис. 1.15) вы можете выполнять над полученной страницей памяти операции чтения, записи, фиксирования и расфиксирования (соответственно, строки Read, Write, Lock и Unlock). Рис. 1.15. Меню Memory, предназначенное для выполнения различных операций над страницей памяти Первоначально для страницы устанавливается код доступа PAGE_NOACCES, запрещающий любой доступ, поэтому при выборе из меню Memory любой строки вы получите сообщение об ошибке. Если установить код доступа PAGE_READONLY XE "PAGE_READONLY" , то, как и следовало ожидать, вы сможете выполнить только операции чтения, фиксирования и расфиксирования страницы. Тип доступа PAGE_READWRITE XE "PAGE_READWRITE" дополнительно разешает выполнение операции записи. В том случае, когда для страницы устанволена комбинация типов доступа PAGE_READWRITE XE "PAGE_READWRITE" и PAGE_GUARD XE "PAGE_GUARD" , при первой попытке выполнения над этой страницей любой операции появляется сообщение об ошибке, так как возникает исключение. Во второй раз эта же операция выполнится без ошибки, так как после возникновения исключения тип доступа PAGE_GUARD сбрасывается автоматически. Когда любое исключение возникает в 16-разрядном приложении Microsoft Windows версии 3.1, оно завершает свою работу с сообщением о фатальной ошибке. С этим не может ничего сделать ни пользователь, ни программист, создающий такие приложения. Что же касается операционной системы Microsoft Windows NT, то приложение может самостоятельно выполнять обработку исключений. На рис. 1.16 показано сообщение, которое возникло бы на экране при попытке приложения, не обрабатывающего исключения, обратиться к странице с типом доступа PAGE_NOACCES для чтения. Прочитав его, пользователь может нажимать кнопку OK, что приведет к аварийному завершению работы приложения. Рис. 1.16. Сообщение, возникающее на экране при попытке обращения к странице с типом доступа PAGE_NOACCES Информация, представленная в этом сообщении, ничего не дает пользователю. Не сможет он воспользоваться и предложением запустить отладчик, нажав кнопку Cancel, так как едва ли у него есть исходные тексты приложения и, что самое главное, желание разбираться с ними. Аналогично, при попытке приложения обратиться к странице с типом доступа PAGE_GUARD XE "PAGE_GUARD" , на экране появится сообщение, показанное на рис. 1.17. Рис. 1.17. Сообщение, возникающее при попытке обращения к странице с типом доступа PAGE_GUARD XE "PAGE_GUARD" Наше приложение VIRTUAL способно обрабатывать исключения, поэтому даже при попытках выполнения неразрешенного вида доступа все ограничивается выводом соответствующего сообщения, после чего работа приложения может быть продолжена. На рис. 1.18 показано такое сообщение, возникающее при обращении к памяти с типом доступа PAGE_GUARD XE "PAGE_GUARD" . Рис. 1.18. Сообщение об исключении, отображаемое приложением VIRTUAL Обработку исключений мы рассмотрим в отдельной главе одной из следующих наших книг, посвященных операционной системе Microsoft Windows NT, а сейчас только скажем, что при ее использовании вы можете значительно повысить устойчивость работы приложения, встроив в него мощную систему проверки (и, в некоторых случаях, даже исправления) различных ошибок. Исходные тексты приложенияОсновной файл исходных текстов приложения VIRTUAL представлен в листинге 1.1. Заметим, что вы можете приобрести дискету, которая продается вместе с нашей книгой, избавив себя и от необходимости набирать исходные тексты приложений вручную, и от ошибок, неизбежно возникающих при этом. Листинг 1.1. Файл virtual/virtual.c #define STRICT #include <windows.h> #include <windowsx.h> #include <stdio.h> #include "resource.h" #include "afxres.h" #include "virtual.h" // Размер блока вируальной памяти, над которым будут // выполняться операции #define MEMBLOCK_SIZE 4096 HINSTANCE hInst; char szAppName[] = "VirtualApp"; char szAppTitle[] = "Working with Virtual Memory"; // Указатель на блок памяти LPVOID lpMemoryBuffer; // Идентификатор меню Set protection HMENU hSetMenu; // ----------------------------------------------------- // Функция WinMain // ----------------------------------------------------- int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wc; HWND hWnd; MSG msg; // Сохраняем идентификатор приложения hInst = hInstance; // Преверяем, не было ли это приложение запущено ранее hWnd = FindWindow(szAppName, NULL); if(hWnd) { // Если было, выдвигаем окно приложения на // передний план if(IsIconic(hWnd)) ShowWindow(hWnd, SW_RESTORE); SetForegroundWindow(hWnd); return FALSE; } // Регистрируем класс окна memset(&wc, 0, sizeof(wc)); wc.cbSize = sizeof(WNDCLASSEX); wc.hIconSm = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICONSM), IMAGE_ICON, 16, 16, 0); wc.style = 0; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; wc.hIcon = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 32, 32, 0); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); wc.lpszMenuName = MAKEINTRESOURCE(IDR_APPMENU); wc.lpszClassName = szAppName; if(!RegisterClassEx(&wc)) if(!RegisterClass((LPWNDCLASS)&wc.style)) return FALSE; // Создаем главное окно приложения hWnd = CreateWindow(szAppName, szAppTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInst, NULL); if(!hWnd) return(FALSE); // Отображаем окно и запускаем цикл // обработки сообщений ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } // ----------------------------------------------------- // Функция WndProc // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hWnd, WM_CREATE, WndProc_OnCreate); HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy); HANDLE_MSG(hWnd, WM_COMMAND, WndProc_OnCommand); default: return(DefWindowProc(hWnd, msg, wParam, lParam)); } } // ----------------------------------------------------- // Функция WndProc_OnCreate // ----------------------------------------------------- BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct) { // Временный указатель LPVOID lpReserved; // Резервируем страницы виртуальной памяти lpReserved = VirtualAlloc(NULL, MEMBLOCK_SIZE, MEM_RESERVE, PAGE_NOACCESS); // При ошибке завершаем работу приложения if(lpReserved == NULL) { MessageBox(hWnd, "Ошибка при резервировании памяти", szAppTitle, MB_OK | MB_ICONEXCLAMATION); return FALSE; } // Получаем память в пользование lpMemoryBuffer = VirtualAlloc(lpReserved, MEMBLOCK_SIZE, MEM_COMMIT, PAGE_NOACCESS); // При ошибке освобождаем зарезервированную ранее // память и завершаем работу приложения if(lpMemoryBuffer == NULL) { MessageBox(hWnd, "Ошибка при получении памяти для использования", szAppTitle, MB_OK | MB_ICONEXCLAMATION); VirtualFree(lpReserved, 0, MEM_RELEASE); return FALSE; } // Получаем идентификатор меню Set protection hSetMenu = GetSubMenu(GetMenu(hWnd), 1); // Отмечаем строку PAGE_NOACCESS этого меню CheckMenuItem(hSetMenu, ID_SETPROTECTION_PAGENOACCESS, MF_CHECKED); return TRUE; } // ----------------------------------------------------- // Функция WndProc_OnDestroy // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnDestroy(HWND hWnd) { // Перед завершением работы приложения освобождаем // полученную ранее память if(lpMemoryBuffer != NULL) VirtualFree(lpMemoryBuffer, 0, MEM_RELEASE); PostQuitMessage(0); return 0L; } // ----------------------------------------------------- // Функция WndProc_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify) { int test; DWORD dwOldProtect; char chBuff[256]; switch (id) { // Устанавливаем тип доступа PAGE_NOACCESS case ID_SETPROTECTION_PAGENOACCESS: { VirtualProtect(lpMemoryBuffer, MEMBLOCK_SIZE, PAGE_NOACCESS, &dwOldProtect); // Отмечаем строку PAGE_NOACCESS меню Set protection CheckMenuItem(hSetMenu, ID_SETPROTECTION_PAGENOACCESS, MF_CHECKED); // Убираем отметку с других строк меню Set protection CheckMenuItem(hSetMenu, ID_SETPROTECTION_PAGEREADONLY, MF_UNCHECKED); CheckMenuItem(hSetMenu, ID_SETPROTECTION_PAGEREADWRITE, MF_UNCHECKED); CheckMenuItem(hSetMenu, ID_SETPROTECTION_PAGEGUARD, MF_UNCHECKED); break; } // Устанавливаем тип доступа PAGE_READONLY case ID_SETPROTECTION_PAGEREADONLY: { VirtualProtect(lpMemoryBuffer, MEMBLOCK_SIZE, PAGE_READONLY, &dwOldProtect); // Отмечаем строку PAGE_READONLY меню Set protection CheckMenuItem(hSetMenu, ID_SETPROTECTION_PAGEREADONLY, MF_CHECKED); // Убираем отметку с других строк меню Set protection CheckMenuItem(hSetMenu, ID_SETPROTECTION_PAGENOACCESS, MF_UNCHECKED); CheckMenuItem(hSetMenu, ID_SETPROTECTION_PAGEREADWRITE, MF_UNCHECKED); CheckMenuItem(hSetMenu, ID_SETPROTECTION_PAGEGUARD, MF_UNCHECKED); break; } // Устанавливаем тип доступа PAGE_READWRITE case ID_SETPROTECTION_PAGEREADWRITE: { VirtualProtect(lpMemoryBuffer, MEMBLOCK_SIZE, PAGE_READWRITE, &dwOldProtect); // Отмечаем строку PAGE_READWRITE меню Set protection CheckMenuItem(hSetMenu, ID_SETPROTECTION_PAGEREADWRITE, MF_CHECKED); // Убираем отметку с других строк меню Set protection CheckMenuItem(hSetMenu, ID_SETPROTECTION_PAGENOACCESS, MF_UNCHECKED); CheckMenuItem(hSetMenu, ID_SETPROTECTION_PAGEREADONLY, MF_UNCHECKED); CheckMenuItem(hSetMenu, ID_SETPROTECTION_PAGEGUARD, MF_UNCHECKED); break; } // Устанавливаем тип доступа // PAGE_READWRITE | PAGE_GUARD case ID_SETPROTECTION_PAGEGUARD: { VirtualProtect(lpMemoryBuffer, MEMBLOCK_SIZE, PAGE_READWRITE | PAGE_GUARD, &dwOldProtect); // Отмечаем строку PAGE_READWRITE & PAGE_GUARD // меню Set protection CheckMenuItem(hSetMenu, ID_SETPROTECTION_PAGEGUARD, MF_CHECKED); // Убираем отметку с других строк меню Set protection CheckMenuItem(hSetMenu, ID_SETPROTECTION_PAGENOACCESS, MF_UNCHECKED); CheckMenuItem(hSetMenu, ID_SETPROTECTION_PAGEREADONLY, MF_UNCHECKED); CheckMenuItem(hSetMenu, ID_SETPROTECTION_PAGEREADWRITE, MF_UNCHECKED); break; } // Выполняем попытку чтения case ID_MEMORY_READ: { __try { test = *((int *)lpMemoryBuffer); } // Если возникло исключение, получаем и // отображаем его код __except (EXCEPTION_EXECUTE_HANDLER) { sprintf(chBuff, "Исключение с кодом\n" "%lX\nпри чтении блока памяти", GetExceptionCode()); MessageBox(hWnd, chBuff, szAppTitle, MB_OK | MB_ICONEXCLAMATION); break; } // Если операция завершилась успешно, // сообщаем об этом пользователю MessageBox(hWnd, "Чтение выполнено", szAppTitle, MB_OK | MB_ICONEXCLAMATION); break; } // Выполняем попытку записи case ID_MEMORY_WRITE: { __try { *((int *)lpMemoryBuffer) = 1; } // Если возникло исключение, получаем и // отображаем его код __except (EXCEPTION_EXECUTE_HANDLER) { sprintf(chBuff, "Исключение с кодом\n" "%lX\nпри записи в блок памяти", GetExceptionCode()); MessageBox(hWnd, chBuff, szAppTitle, MB_OK | MB_ICONEXCLAMATION); break; } // Если операция завершилась успешно, // сообщаем об этом пользователю MessageBox(hWnd, "Запись выполнена", szAppTitle, MB_OK | MB_ICONEXCLAMATION); break; } // Выполняем попытку фиксирования блока памяти case ID_MEMORY_LOCK: { if(VirtualLock(lpMemoryBuffer, MEMBLOCK_SIZE) == FALSE) { MessageBox(hWnd, "Ошибка при фиксировании страниц в памяти", szAppTitle, MB_OK | MB_ICONEXCLAMATION); break; } MessageBox(hWnd, "Фиксирование выполнено", szAppTitle, MB_OK | MB_ICONEXCLAMATION); break; } // Выполняем попытку расфиксирования блока памяти case ID_MEMORY_UNLOCK: { if(VirtualUnlock(lpMemoryBuffer, MEMBLOCK_SIZE) == FALSE) { MessageBox(hWnd, "Ошибка при расфиксировании страниц в памяти", szAppTitle, MB_OK | MB_ICONEXCLAMATION); break; } MessageBox(hWnd, "Расфиксирование выполнено", szAppTitle, MB_OK | MB_ICONEXCLAMATION); break; } case ID_FILE_EXIT: { // Завершаем работу приложения PostQuitMessage(0); return 0L; break; } case ID_HELP_ABOUT: { MessageBox(hWnd, "Working with Virtual Memory\n" "(C) Alexandr Frolov, 1996\n" "Email: frolov@glas.apc.org", szAppTitle, MB_OK | MB_ICONINFORMATION); return 0L; break; } default: break; } return FORWARD_WM_COMMAND(hWnd, id, hwndCtl, codeNotify, DefWindowProc); } В файле virtual.h (листинг 1.2) представлены прототипы функций, определенных в нашем приложении. Листинг 1.2. Файл virtual/virtual.h LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct); void WndProc_OnDestroy(HWND hWnd); void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify); Файл resource.h (листинг 1.3) создается автоматически системой разработки Microsoft Visual C++ и содержит определения констант, использованных в файле описания ресурсов приложения. Листинг 1.3. Файл virtual/resource.h //{{NO_DEPENDENCIES}} // Microsoft Developer Studio generated include file. // Used by Virtual.RC // #define IDR_APPMENU 102 #define IDI_APPICON 103 #define IDI_APPICONSM 104 #define ID_FILE_EXIT 40001 #define ID_HELP_ABOUT 40003 #define ID_SETPROTECTION_PAGENOACCESS 40035 #define ID_SETPROTECTION_PAGEREADONLY 40036 #define ID_SETPROTECTION_PAGEREADWRITE 40037 #define ID_SETPROTECTION_PAGEGUARD 40038 #define ID_MEMORY_READ 40039 #define ID_MEMORY_WRITE 40040 #define ID_MEMORY_LOCK 40041 #define ID_MEMORY_UNLOCK 40042 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 121 #define _APS_NEXT_COMMAND_VALUE 40043 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif Файл описания ресурсов приложения virtual.rc (листинг 1.4) также автоматически создается системой разработки Microsoft Visual C++. В нем определены пиктограммы приложения, меню и текстовые строки (которые мы в данном случае не используем). Листинг 1.4. Файл virtual/virtual.rc //Microsoft Developer Studio generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 ////////////////////////////////////////////////////////////// // Menu // IDR_APPMENU MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", ID_FILE_EXIT END POPUP "&Set protection" BEGIN MENUITEM "PAGE_NOACCESS", ID_SETPROTECTION_PAGENOACCESS MENUITEM "PAGE_READONLY", ID_SETPROTECTION_PAGEREADONLY MENUITEM "PAGE_READWRITE",ID_SETPROTECTION_PAGEREADWRITE MENUITEM "PAGE_GUARD && PAGE_READWRITE", ID_SETPROTECTION_PAGEGUARD END POPUP "&Memory" BEGIN MENUITEM "&Read", ID_MEMORY_READ MENUITEM "&Write", ID_MEMORY_WRITE MENUITEM "&Lock", ID_MEMORY_LOCK MENUITEM "&Unlock", ID_MEMORY_UNLOCK END POPUP "&Help" BEGIN MENUITEM "&About...", ID_HELP_ABOUT END END #ifdef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // Icon // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_APPICON ICON DISCARDABLE "virtual.ico" IDI_APPICONSM ICON DISCARDABLE "virtsm.ico" ////////////////////////////////////////////////////////////// // String Table // STRINGTABLE DISCARDABLE BEGIN ID_FILE_EXIT "Quits the application" END #endif // English (U.S.) resources ////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 3 resource. ////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED Описание исходных текстов приложенияЕсли вы читали нашу книгу “Операционная система Microsoft Windows 95 для программиста” (22 том “Библиотеки системного программиста”), то вы уже сталкивались с 32-разрядными приложениями, работающими в сплошной модели памяти. При создании приложения VIRTUAL мы использовали те же инструментальные средства, что и в упомянутой книге, а именно Microsoft Visual C++ версии 4.0. Определения и глобальные переменныеКонстанта MEMBLOCK_SIZE определяет размер блока памяти в байтах, над которым выполняются операции. Мы работаем с блоком памяти размером 4096 байт, что соответствует одной странице, однако вы можете попробовать и другие значения. Например, вы можете попытаться задать очень большие значения и убедиться в том, что при этом хотя память и будет предоставлена в ваше распоряжение, вы не сможете выполнить фиксирование соответствующих страниц. В глобальных переменных hInst, szAppName и szAppTitle хранится, соответственно, идентификатор приложения, полученный при запуске через параметр hInstance функции WinMain, имя и заголовок главного окна приложения. Глобальная переменная lpMemoryBuffer содержит указатель на блок виртуальной памяти размером MEMBLOCK_SIZE байт. Это именно тот блок памяти, с которым в нашем приложении выполняются все операции. И, наконец, в глобальной переменной hSetMenu хранится идентификатор меню Set protection, который необходим для выполнения операции отметки строк этого меню. Функция WinMainИзучая исходный текст функции WinMain, вы по большому счету не найдете никаких отличий от исходных текстов этой функции, приведенных в приложениях Microsoft Windows 95 из 22 тома “Библиотеки системного программиста”. В этом нет ничего удивительного, так как и в Microsoft Windows 95, и в Microsoft Windows NT реализован 32-разрядный интерфейс WIN32, которым мы должны пользоваться. Более того, разрабатывая приложения для Microsoft Windows 95, вы должны проверять их работоспособность в среде Microsoft Windows NT, так как в противном случае вы не сможете стать соискателем логотипа “Designed for Microsoft Windows 95”. Итак, вернемся к нашей функции WinMain. Прежде всего эта функция сохраняет идентификатор приложения hInstance в глобальной переменной hInst для дальнейшего использования. Затем выполняется поиск нашего приложения среди уже запущенных. При этом используется функция FindWindow. Если эта функция среди активных приложений найдет приложение с именем szAppName, она возвратит идентификатор его окна, если нет - значение NULL. Если приложение найдено и его окно не свернуто в пиктограмму (что проверяется при помощи функции IsIconic), оно выдвигается на передний план при помощи функции SetForegroundWindow. После этого функция WinMain завершает свою работу. В среде Microsoft Windows NT, так же как и в среде Microsoft Windows 95, мы не можем проверить, было ли наше приложение запущенно ранее, анализируя значение параметра hPrevInstance функции WinMain, так как в отличие от Microsoft Windows версии 3.1, в указанных операционных системах через этот параметр всегда передается значение NULL. Об этом мы подробно говорили в 22 томе “Библиотеки системного программиста”. Если приложение не найдено, выполняется регистрация класса окна. В отличие от операционной системы Microsoft Windows версии 3.1, в Microsoft Windows 95 и Microsoft Windows NT следует использовать регистрацию при помощи функции RegisterClassEx XE "RegisterClassEx" , подготовив для нее структуру WNDCLASSEX XE "WNDCLASSEX" . По сравнению со знакомой вам структурой WNDCLASS операционной системы Microsoft Windows версии 3.1 в структуре WNDCLASSEX имеются два дополнительных поля с именами cbSize и hIconSm. В первое из них необходимо записать размер структуры WNDCLASSEX, а во второе - идентификатор пиктограммы малого размера. Эта пиктограмма в Microsoft Windows NT версии 4.0 будет отображаться в левом верхнем углу главного окна приложения, играя роль пиктограммы системного меню. Для загрузки пиктограмм из ресуросв приложения мы воспользовались функцией LoadImage XE "LoadImage" , описанной нами в 22 томе “Библиотеки системного программиста”. Заметим, что если вызов функции регистрации класса окна RegisterClassEx XE "RegisterClassEx" закончился неудачно (это может произойти, например, если вы запустите приложение в среде Microsoft Windows NT версии 3.5 или более ранней версии), мы выполняем регистрацию старым способом, который тоже работает, - при помощи функции RegisterClass XE "RegisterClass" . После регистрации функция WinMain создает главное окно приложения, отображает и обновляет его, а затем запускает цикл обработки сообщений. Все эти процедуры мы описывали в 11 томе “Библиотеки системного программиста”, который называется “Операционная система Microsoft Windows 3.1 для программиста. Часть первая”. Функция WndProcФункция WndProc обрабатывает сообщения WM_CREATE XE "WM_CREATE" , WM_DESTROY XE "WM_DESTROY" и WM_COMMAND XE "WM_COMMAND" . Соответствующие функции обработки WndProc_OnCreate, WndProc_OnDestroy и WndProc_OnCommand назначаются для этих сообщений при помощи макрокоманды HANDLE_MSG XE "HANDLE_MSG" . Этот метод обработки сообщений был нами подробно рассмотрен в 22 томе “Библиотеки системного программиста”, поэтому сейчас мы не будем его описывать. Функция WndProc_OnCreateНапомним, что при создании окна его функции окна передается сообщение WM_CREATE XE "WM_CREATE" . Функция WndProc_OnCreate, определенная в нашем приложении, выполняет обработку этого сообщения. Прежде всего, функция резервирует область виртуальной памяти размером MEMBLOCK_SIZE байт, вызывая функцию VirtualAlloc XE "VirtualAlloc" с параметром MEM_RESERVE XE "MEM_RESERVE" : lpReserved = VirtualAlloc(NULL, MEMBLOCK_SIZE, MEM_RESERVE, PAGE_NOACCESS); Через первый параметр мы передаем функции VirtualAlloc XE "VirtualAlloc" значение NULL, поэтому операционная система сама определит для нас начальный адрес резервируемой области. Этот адрес мы сохраняем во временной локальной переменной lpReserved. В случае ошибки выводится соответствующее сообщение. Если же резервирование адресного пространства выполнено успешно, функция получает память в использование, вызывая для этого функцию VirtualAlloc XE "VirtualAlloc" еще раз, но уже с параметром MEM_COMMIT XE "MEM_COMMIT" : lpMemoryBuffer = VirtualAlloc(lpReserved, MEMBLOCK_SIZE, MEM_COMMIT, PAGE_NOACCESS); Так как в качестве первого параметра функции VirtualAlloc XE "VirtualAlloc" передается значение lpReserved, выделение страниц памяти выполняется в зарезервированной ранее области адресов. При невозможности получения памяти в диапазоне зарезервированных адресов мы отдаем зарезервированные адреса системе и завершаем работу приложения, запрещая создание его главного окна: VirtualFree(lpReserved, 0, MEM_RELEASE); return FALSE; Заметим, что мы могли бы и не вызывать функцию VirtualFree XE "VirtualFree" , так как после завершения процесса операционная система Microsoft Windows NT автоматически освобождает все распределенные для него ранее страницы виртуальной памяти. Последнее, что делает обработчик сообщения WM_CREATE XE "WM_CREATE" , это получение идентификатора меню Set protection и отметку в этом меню строки PAGE_NOACCESS XE "PAGE_NOACCESS" : hSetMenu = GetSubMenu(GetMenu(hWnd), 1); CheckMenuItem(hSetMenu, ID_SETPROTECTION_PAGENOACCESS, MF_CHECKED); Использованные при этом функции были описаны в главе “Меню” 13 тома “Библиотеки системного программиста”, который называется “Операционная система Microsoft Windows 3.1 для программиста. Часть третья”. Функция WndProc_OnDestroyОбработчик сообщения WM_DESTROY XE "WM_DESTROY" проверяет содрежимое указателя lpMemoryBuffer и, если оно не равно NULL, освобождает память при помощи функции VirtualFree XE "VirtualFree" : if(lpMemoryBuffer != NULL) VirtualFree(lpMemoryBuffer, 0, MEM_RELEASE); Как мы уже говорили, при завершении работы приложения полученная память будет освобождена операционной системой. Однако хороший стиль программирования предполагает освобождение ресурсов, которые больше не нужны приложению, поэтому мы вызываем эту функцию сами. После освобождения памяти приложение вызывает функцию PostQuitMessage, что приводит к завершению цикла обработки сообщений и, следовательно, к завершению работы нашего приложения. Функция WndProc_OnCommandФункция WndProc_OnCommand обрабатывает сообщение WM_COMMAND XE "WM_COMMAND" , поступающее от главного меню приложения. Выбирая строки меню Set protection, пользователь может изменять тип доступа, разрешенного для блока памяти, заказанного приложением при обработке сообщения WM_CREATE XE "WM_CREATE" . Меню Memory позволяет пользователю выполнять над этим блоком операции чтения, записи, фиксирования и расфиксирования. Изменение типа доступа выполняется при помощи функции VirtualProtect. Например, установка типа доступа PAGE_NOACCESS XE "PAGE_NOACCESS" выполняется следующим образом: VirtualProtect(lpMemoryBuffer, MEMBLOCK_SIZE, PAGE_NOACCESS, &dwOldProtect); При этом старый тип доступа записывается в переменную dwOldProtect, но никак не используется нашим приложением. После изменения типа доступа обработчик сообщения WM_COMMAND XE "WM_COMMAND" изменяет соответствующим образом отметку строк меню Set protection, для чего используется макрокоманда CheckMenuItem. Теперь рассмотрим обработку сообщения WM_COMMAND XE "WM_COMMAND" в том случае, когда оно приходит от меню Memory. Если пользователь выполняет попытку чтения блока памяти, выбирая из меню Memory строку Read, выполняется следующий фрагмент кода: case ID_MEMORY_READ: { __try { test = *((int *)lpMemoryBuffer); } __except (EXCEPTION_EXECUTE_HANDLER) { sprintf(chBuff, "Исключение с кодом\n" "%lX\nпри чтении блока памяти", GetExceptionCode()); MessageBox(hWnd, chBuff, szAppTitle, MB_OK | MB_ICONEXCLAMATION); break; } MessageBox(hWnd, "Чтение выполнено", szAppTitle, MB_OK | MB_ICONEXCLAMATION); break; } Здесь в области действия оператора __try XE "__try" , ограниченной фигурными скобками, содержимое первого слова буфера lpMemoryBuffer читается во временную переменную test. Эта, безопасная на первый взгляд операция может привести в приложении Microsoft Windows NT к возникновению исключения, так как соответствующая страница памяти может оказаться недоступной для чтения. Если не предусмотреть обработку исключения, при его возникновении работа приложения завершится аварийно. Тело обработчика исключения находится в области действия оператора __except XE "__except" и выполняется только при возникновении исключения. В нашем случае обработка исключения очень проста и заключается в отображении сообшения с кодом исключения, полученного при помощи функции GetExceptionCode XE "GetExceptionCode" . Таким образом, если исключение возникнет, пользователь увидит сообщение с кодом исключения, а если нет - на экране появится сообщение о том, что чтение выполнено. Попытка записи выполняется при выборе из меню Memory строки Write. Вот фрагмент кода, выполняющий запись: __try { *((int *)lpMemoryBuffer) = 1; } Так как при записи могут возникать исключения, мы предусмотрели обработчик, аналогичный только что рассмотренному. Когда пользователь выбирает из меню Memory строку Lock, выполняется попытка зафиксировать страницы блока памяти при помощи функции VirtualLock XE "VirtualLock" , как это показано ниже: case ID_MEMORY_LOCK: { if(VirtualLock(lpMemoryBuffer, MEMBLOCK_SIZE) == FALSE) { MessageBox(hWnd, "Ошибка при фиксировании страниц в памяти", szAppTitle, MB_OK | MB_ICONEXCLAMATION); break; } MessageBox(hWnd, "Фиксирование выполнено", szAppTitle, MB_OK | MB_ICONEXCLAMATION); break; } Обратите внимание, что в данном случае мы не выполняем обработку исключений, но проверяем код возврата функции VirtualLock XE "VirtualLock" . При попытке зафиксировать страницу с кодом доступа PAGE_NOACCESS XE "PAGE_NOACCESS" не произойдет аварийного завершения работы приложения. В этом случае функция VirtualLock просто вернет значение FALSE, означающее ошибку. Расфиксирование страниц блока памяти выполняется аналогичным образом, когда пользователь выбирает из меню Memory строку Unlock. |