Время от времени в разделе будут появляться короткие заметки по разнообразным, зачастую необходимым на практике, фрагментам кода на языке ассемблера. В этом посте я хотел бы сохранить фрагмент кода, предназначенный для получения размеров окна приложения ассемблере и вывода их в графическое окно. Иногда у начинающих встречаются задачи, в которых необходимо получить размеры (ширина/высота) областей произвольного окна собственного приложения. Что бы не рыскать каждый раз по Сети в поисках ответов, решил сохранить код решения задачи в качестве автономной заметки на собственном блоге. Код, приведенный далее в примере, рассчитан на диалект препроцессора FASM. Исходный код небольшого приложения, выводящего в окно размеры собственного окна:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
format PE GUI 4.0 ; Формат PE entry start ; Точка входа include '%include%\win32a.inc' ; Включение описателей _style equ WS_VISIBLE+WS_DLGFRAME+WS_SYSMENU+WS_THICKFRAME ; Стиль окна. ;=== сегмент кода ============================================================ section '.text' code readable executable start: invoke GetModuleHandle,0 ; Получим дескриптор приложения. mov [wc.hInstance],eax ; Сохраним дескриптор в структуру wc mov [wc.style], CS_HREDRAW or CS_VREDRAW ; Стили перерисовки окна полностью invoke LoadIcon,0,IDI_ASTERISK ; Загружаем иконку IDI_ASTERISK из ресурсов исполняемого файла mov [wc.hIcon],eax ; Сохраним в структуре окна invoke LoadCursor,0,IDC_ARROW ; Загружаем курсор IDC_ARROW из ресурсов исполняемого файла mov [wc.hCursor],eax ; Сохраним дескриптор курсора в структуре окна mov [wc.lpfnWndProc],WindowProc ; Адрес нашей процедуры обработки окна mov [wc.lpszClassName],_class ; Имя класса окна mov [wc.hbrBackground],COLOR_WINDOW+1 ; Цвет кисти invoke RegisterClass,wc ; Регистрируем наш класс окна test eax,eax ; Проверим выходной eax jz error ; Если 0 - то ошибка. Выводим и выходим. invoke CreateWindowEx,0,_class,_title,_style,128,128,512,512,NULL,NULL,[wc.hInstance],NULL ; Создаем экземпляр окна на основе зарегистрированного класса test eax,eax ; Проверим выходной eax. jz error ; Если 0 - то ошибка. Выводим и выходим. mov [wHMain],eax ; сохраним дескриптор окна ;--- цикл обработки сообщений ------------------------------------------------ msg_loop: invoke GetMessage,msg,NULL,0,0 ; Получаем сообщение из очереди сообщений приложения, помещает его в структуру msg or eax,eax ; Проверяем eax jz end_loop ; 0 = пришло сообщение WM_QUIT (выходим из цикла ожидания сообщений), не 0 = продолжаем обрабатывать очередь msg_loop_2: invoke TranslateMessage,msg ; Дополнительная функция обработки сообщения. Конвертирует некоторые сообщения и отправляет их обратно в очередь. invoke DispatchMessage,msg ; Пересылает сообщения соответствующим процедурам обработки сообщений (WindowProc ...). jmp short msg_loop ; Цикл error: invoke MessageBox,NULL,_error,NULL,MB_ICONERROR+MB_OK ; Вывод сообщения об ошибке end_loop: invoke ExitProcess,[msg.wParam] ; Выход из программы. Код выхода сохраняется в msg.wParam. Его надо передать системе ;--- процедура обработки окна (функция окна, оконная процедура) -------------- proc WindowProc hWnd,wMsg,wParam,lParam push ebx esi edi ; сохраним все регистры cmp [wMsg],WM_CREATE ; Проверим на WM_CREATE je .wmcreate ; на обработчик wmcreate cmp [wMsg],WM_DESTROY ; Проверим на WM_DESTROY je .wmdestroy ; на обработчик wmdestroy cmp [wMsg],WM_PAINT ; Проверим на WM_PAINT je .wmpaint ; на обработчик wmpaint .defwndproc: invoke DefWindowProc,[hWnd],[wMsg],[wParam],[lParam] ; Дефолтная функция. Обрабатывает сообщения, которые наша программа не использует. jmp .finish .wmcreate: xor eax,eax jmp .finish .wmpaint: invoke BeginPaint,[hWnd],pnt mov [pHMain],eax ; Сохранить контекст (дескриптор) устройства invoke GetClientRect,[hWnd],rect_client ; Запрашиваем координаты клиентской области окна mov ebx,[rect_client.bottom] sub ebx,[rect_client.top] ; EBX = высота клиентской области mov ecx,[rect_client.right] sub ecx,[rect_client.left] ; ECX = ширина клиентской области invoke wsprintf,szValue1,szFmt,ecx,ebx ; Записываем в буфер десятичные эквиваленты ширины и высоты invoke lstrlen,_message1 ; Вычислим длину выводимой строки invoke TextOut,[pHMain],2,10,_message1,eax ; Выведем текст в клиентскую область окна invoke GetWindowRect,[hWnd],rect_nonclient ; Запрашиваем координаты неклиентской области окна mov ebx,[rect_nonclient.bottom] sub ebx,[rect_nonclient.top] ; EBX = высота неклиентской области mov ecx,[rect_nonclient.right] sub ecx,[rect_nonclient.left] ; ECX = ширина неклиентской области invoke wsprintf,szValue2,szFmt,ecx,ebx ; Записываем в буфер десятичные эквиваленты ширины и высоты invoke lstrlen,_message2 ; Вычислим длину выводимой строки invoke TextOut,[pHMain],2,30,_message2,eax ; Выведем текст в клиентскую область окна invoke EndPaint,[hWnd],pnt ; Освободить контекст устройства xor eax,eax jmp .finish .wmdestroy: ; Обработчик сообщения WM_DESTROY. invoke PostQuitMessage,0 ; Посылает сообщение WM_QUIT в очередь сообщений, что вынуждает GetMessage вернуть 0. Посылается для выхода из программы. Посылается только основным окном. xor eax,eax ; Если наша процедура окна обрабатывает какое-либо сообщение, то она должна вернуть 0 в eax. .finish: pop edi esi ebx ; восстановим все регистры ret endp ;=== сегмент данных ========================================================== section '.data' data readable writeable _class db 'FASMWIN32',0 ; Название собственного класса. Мы будем регистрировать свой. _title db 'Win32 get window size',0 ; Текст в заголовке окна. _error db 'Startup failed.',0 ; Текст ошибки szFmt db '%04u,%04u',0 ; шаблон формата для wsprintf _message1 db 'ClientSizeXY: ' szValue1 db 'XXXXXXXXX',0 _message2 db 'NonClientSizeXY: ' szValue2 db 'XXXXXXXXX',0 wHMain dd ? ; дескриптор окна pHMain dd ? ; дескриптор контекста устройства wc WNDCLASS ; Структура окна. Для функции RegisterClass msg MSG ; Структура системного сообщения. Структура содержится в INCLUDE\EQUATES\USER32.INC pnt PAINTSTRUCT ; Структура отрисовки клиентской области окна. rect_client RECT ; Структура RECT клиентской части окна rect_nonclient RECT ; Структура RECT неклиентской части окна ;=== таблица импорта ========================================================= section '.idata' import data readable writeable library kernel32,'KERNEL32.DLL',user32,'USER32.DLL',gdi32,'GDI32.DLL' include 'api\kernel32.inc' include 'api\user32.inc' include 'api\gdi32.inc' |
Структура приложения достаточно проста, я не стал писать внутренние алгоритмы для оптимизации и сокращения кода, дабы потом можно было просто использовать фрагменты кода в сторонних программах. Каркас приложения взял из примера статьи про оконное приложение на ассемблере, там и объясняются все ключевые моменты исходного кода приложения. Для того, что бы сразу сосредоточиться на основных моментах, отметим, что код, выполняющий получение размера окна приложения размещен в строках 68
-75
и 77
-84
. Размеры окна приложения на ассемблере запрашиваются у системы при помощи двух функций Win32 API GetClientRect и GetWindowRect, входными параметрами которых являются дескриптор (описатель) окна и адрес структуры типа RECT, которая принимает параметры виртуального прямоугольника, описывающего координаты углов окна.
Для получения координат областей окна могут использовать функции:
- GetClientRect - Получение координат клиентской части окна;
- GetWindowRect - Получение координат неклиентской части окна;
Про клиентскую и неклиентскую области можно почитать в этой статье про вывод текста в окно. После того, как функции возвращают заполненные структуры координат, производится простое вычисление высоты и ширины окна и вывод значений из подготовленного при помощи функции-форматера wsprintf буфера посредством функции TextOut.
Так стоит запомнить одну характерную особенность: