СмещениеДлинаЗначение
+4 | 4 | Младшее слово - Х-координата курсора мыши, старшее слово - Y-координата мыши. |
+8 | 4 | Описывает состояние кнопок мыши. Первый бит - левая кнопка, второй бит - правая кнопка, третий бит - средняя кнопка. Бит установлен - кнопка нажата. |
+12 | 4 | Состояние управляющих клавиш. Аналогично предыдущей таблице. |
+16 | 4 | Может содержать следующие значения: |
MOUSE_MOV equ 1h; было движение мыши
DOUBLE_CL equ 2h; был двойной щелчок
Событие WINDOW_BUFFER_SIZE_EVENT
По смещению +4 находится двойное слово, содержащее новый размер консольного окна. Младшее слово - это размер по X, старшее слово - размер по Y. Да, когда речь идет о консольном окне, все размеры и координаты даются в "символьных" единицах.
Что касается последних двух событий, то там также значимым является двойное слово по смещению +4, Ниже на Рисунок 2.2.4 дана простая программа обработки консольных событий.
.386P ; плоская модель .MODEL FLAT, stdcall
; константы STD_OUTPUT_HANDLE equ -11 STD_INPUT_HANDLE equ -10
; тип события KEY_EV equ 1h MOUSE_EV equ 2h
; константы - состояния клавиатуры RIGHT_ALT_PRESSED equ 1h LEFT_ALT_PRESSED equ 2h RIGHT_CTRL_PRESSED equ 4h LEFT_CTRL_PRESSED equ 8h SHIFT_PRESSED equ 10h NUMLOCK_ON equ 20h SCROLLLOCK_ON equ 40h CAPSLOCK_ON equ 80h ENHANCED_KEY equ 100h
; прототипы внешних процедур EXTERN wsprintfA:NEAR EXTERN GetStdHandle@4:NEAR EXTERN WriteConsoleA@20:NEAR EXTERN SetConsoleCursorPosition@8:NEAR EXTERN SetConsoleTitleA@4:NEAR EXTERN FreeConsole@0:NEAR EXTERN AllocConsole@0:NEAR EXTERN CharToOemA@8:NEAR EXTERN SetConsoleTextAttribute@8:NEAR EXTERN ReadConsoleInputA@16:NEAR EXTERN ExitProcess@4:NEAR
; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ;------------------------------------------------- ; структура для определения событий COOR STRUC Х WORD ? Y WORD ? COOR ENDS
; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' HANDL DWORD ? HANDL1 DWORD ? TITL DB "Обработка событий мыши",0 BUF DB 200 dup (?) LENS DWORD ? ; количество выведенных символов C0 DWORD ? FORM DB "Координаты: %u %u " CRD COOR <?> STR1 DB "Для выхода нажмите ESC",0 MOUS_KEY WORD 9 dup (?) _DATA ENDS
; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; образовать консоль ; вначале освободить уже существующую CALL FreeConsole@0 CALL AllocConsole@0 ; получить HANDL1 ввода PUSH STD_INPUT_HANDLE CALL GetStdHandle@4 MOV HANDL1,EAX ; получить HANDL вывода PUSH STD_OUTPUT_HANDLE CALL GetStdHandle@4 MOV HANDL,EAX ; задать заголовок окна консоли PUSH OFFSET TITL CALL SetConsoleTitleA@4 ;********************************** ; перекодировка строки PUSH OFFSET STR1 PUSH OFFSET STR1 CALL CharToOemA@8 ; длина строки PUSH OFFSET STR1 CALL LENSTR ; вывести строку PUSH 0 PUSH OFFSET LENS PUSH EBX PUSH OFFSET STR1 PUSH HANDL CALL WriteConsoleA@20 ; цикл ожиданий: движение мыши или двойной щелчок L00: ; координаты курсора MOV CRD.X,0 MOV CRD.Y,10 PUSH CRD PUSH HANDL CALL SetConsoleCursorPosition@8 ; прочитать одну запись о событии PUSH OFFSET C0 PUSH 1 PUSH OFFSET MOUS_KEY PUSH HANDL1 CALL ReadConsoleInputA@16 ; проверим, не с мышью ли что? CMP WORD PTR MOUS_KEY, MOUSE_EV JNE L001 ; здесь преобразуем координаты мыши в строку MOV AX, WORD PTR MOUS_KEY+6 ; Y-мышь ; копирование с обнулением старших битов MOVZX EAX,AX PUSH EAX MOV AX, WORD PTR MOUS_KEY+4 ; Х-мышь ; копирование с обнулением старших битов MOVZX EAX,AX PUSH EAX PUSH OFFSET FORM PUSH OFFSET BUF CALL wsprintfA ; восстановить стек ADD ESP,16 ; перекодировать строку для вывода PUSH OFFSET BUF PUSH OFFSET BUF CALL CharToOemA@8 ; длина строки PUSH OFFSET BUF CALL LENSTR ; вывести на экран координаты курсора PUSH 0 PUSH OFFSET LENS PUSH EBX PUSH OFFSET BUF PUSH HANDL CALL WriteConsoleA@20 JMP L00 ; к началу цикла L001: ; нет ли события от клавиатуры? CMP WORD PTR MOUS_KEY,KEY_EV JNE L00 ; есть, какое? CMP BYTE PTR MOUS_KEY+14,27 JNE L00 ;******************************** ; закрыть консоль CALL FreeConsole@0 PUSH 0 CALL ExitProcess@4 RET ; процедура определения длины строки ; строка - [EBP+08Н] ; длина в EBX LENSTR PROC ENTER 0,0 PUSH EAX CLD MOV EDI, DWORD PTR [EBP+08Н] MOV EBX, EDI MOV ECX, 100 ; ограничить длину строки XOR AL,AL REPNE SCASB ; найти символ 0 SUB EDI, EBX ; длина строки, включая 0 MOV EBX, EDI DEC EBX POP EAX LEAVE RET 4 LENSTR ENDP _TEXT ENDS END START
Рисунок 2.2.4. Пример обработки событий от мыши и клавиатуры для консольного приложения.
После того как вы познакомились с программой на Рисунок 2.2.4, давайте ее подробнее обсудим.
Начнем с функции wsprintfA. Как я уже заметил, функция необычная.
Она имеет переменное число параметров. Первые два параметра обязательны. Вначале идет указатель на буфер, куда будет скопирована результирующая строка. Вторым идет указатель на форматную строку. Форматная строка может содержать текст, а также формат выводимых параметров. Поля, содержащие информацию о параметре, начинаются с символа "%". Формат этих полей в точности соответствует формату полей, используемых в стандартных Си-функциях printf, sprintf и др. Исключением является отсутствие в формате для функции wsprintf вещественных чисел. Нет нужды излагать этот формат, заметим только, что каждое поле в форматной строке соответствует параметру (начиная с третьего). В нашем случае форматная строка была равна: "Координаты: %u %u". Это означало, что далее в стек будет отправлено два числовых параметра типа WORD. Конечно, в стек мы отправили два двойных слова, позаботившись лишь о том, чтобы старшие слова были обнулены. Для такой операции очень удобна команда микропроцессора MOVZX, которая копирует второй операнд в первый так, чтобы биты старшего слова были заполнены нулями. Если бы параметры были двойными словами, то вместо поля %u мы бы поставили %lu. В случае, если поле форматной строки определяет строку-параметр, например "%S", в стек следует отправлять указатель на строку (что естественно).29
Поскольку функция "не знает", сколько параметров может быть в нее отправлено, разработчики не стали усложнять текст этой функции, и оставили нам проблему освобождения стека30. Это производится командой ADD ESP,N. Здесь N - это количество освобождаемых байтов.
Обратимся теперь к функции ReadConsoleInputA. К уже сказанному о ней добавлю только, что если буфер событий пуст, то функция будет ждать, пока "что-то" не случится с консольным окном, и только тогда возвратит управление. Кроме того, мы можем указать, чтобы функция возвращала не одну, а несколько записей о происшедших с консолью событиях. В этом случае в буфер будет помещена не одна, а несколько информационных записей. Но мы на этом останавливаться не будем.
По обыкновению отмечу, как откомпилировать данную программу в TASM32. Как обычно, удаляем все значки @N, указываем библиотеку import32.lib и наконец wsprintfA меняем на _wsprintfA.
29 В этой связи не могу не отметить, что встречал в литературе по ассемблеру (!) утверждение, что все помещаемые в стек для этой функции параметры являются указателями. Как видим, вообще говоря, это не верно.
30 Компилятор Си, естественно, делает это за нас.
Данный раздел посвящен диалоговому окну с двумя списками
III
Данный раздел посвящен диалоговому окну с двумя списками. Двойным щелчком по элементу левого списка заполняется правый список. При этом мы учитываем возможность повторного щелчка по одному и тому же элементу. В принципе, в программе нет ничего сложного. Ниже будет дан комментарий к ней. Но мне хотелось бы немного поговорить о таком элементе, как список, остановившись на некоторых важных моментах.
Средства управления списком можно разделить на сообщения и свойства32. Свойства задаются в файле ресурсов. Например, свойство LBS_SORT приводит к тому, что содержимое списка будет автоматически сортироваться при добавлении туда элемента. Очень важным является свойство LBS_WANTKEYBOARDINPUT. При наличии такого свойства приложение получает сообщение WM_VKEYTOITEM, которое посылается приложению, когда нажимается какая-либо клавиша при наличии фокуса на данном списке. Вы можете выбрать самостоятельную обработку - клавиша PgUp, или оставить стандартную обработку. В том случае, если стандартная обработка не нужна, следует возвратить из функции диалогового окна отрицательное значение.
32
Впрочем, это можно сказать о любых элементах на диалоговом окне. Не правда ли, это весьма похоже на методы и свойства в объектном программировании. Но мы то с Вами знаем, что если углубиться еще дальше, то мы обнаружим, что значительная часть свойств опять сведется к обработке сообщений (см. комментарий к программе на Рисунок 2.4.3).
// файл diallst.rc
// определение констант #define WS_SYSMENU 0x00080000L #define WS_MINIMIZEBOX 0x00020000L #define WS_MAXIMIZEBOX 0x00010000L #define WS_VISIBLE 0x10000000L #define WS_TABSTOP 0x00010000L #define WS_VSCROLL 0x00200000L #define WS_THICKFRAME 0x00040000L #define LBS_NOTIFY 0x0001L #define LBS_SORT 0x0002L #define LBS_WANTKEYBOARDINPUT 0x0400L
// идентификаторы #define LIST1 101 #define LIST2 102 #define IDI_ICON1 3
// определили иконку IDI_ICON1 ICON "ico1.ico"
// определение диалогового окна DIAL1 DIALOG 0, 0, 210, 110 STYLE WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX CAPTION "Пример диалогового окна" FONT 8, "Arial" { CONTROL "ListBox1",LIST1, "listbox", WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | WS_THICKFRAME | LBS_NOTIFY | LBS_WANTKEYBOARDINPUT, 16, 16, 70, 75 CONTROL "ListBox2", LIST2, "listbox", WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | WS_THICKFRAME | LBS_NOTIFY | LBS_SORT, 116, 16, 70, 75 }
; файл diallst.inc ; константы ; сообщение приходит при закрытии окна WM_CLOSE equ 10h WM_INITDIALOG equ 110h WM_SETICON equ 80h WM_COMMAND equ 111h WM_VKEYTOITEM equ 2Eh LB_ADDSTRING equ 180h LBN_DBLCLK equ 2 LB_GETCURSEL equ 188h LB_GETTEXT equ 189h LB_FINDSTRING equ 18Fh VK_INSERT equ 2Dh
; прототипы внешних процедур EXTERN ExitProcess@4:NEAR EXTERN GetModuleHandleA@4:NEAR EXTERN DialogBoxParamA@20:NEAR EXTERN EndDialog@8:NEAR EXTERN LoadIconA@8:NEAR EXTERN SendMessageA@16:NEAR EXTERN SendDlgItemMessageA@20:NEAR EXTERN MessageBoxA@16:NEAR
; структуры ; структура сообщения MSGSTRUCT STRUC MSHWND DD ? MSMESSAGE DD ? MSWPARAM DD ? MSLPARAM DD ? MSTIME DD ? MSPT DD ? MSGSTRUCT ENDS
; файл diallst.asm .386P ; плоская модель .MODEL FLAT, stdcall include dial.inc ; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ;------------------------------------------------- ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' MSG MSGSTRUCT <?> HINST DD 0 ; дескриптор приложения PA DB "DIAL1",0 BUFER DB 100 DUP (0) STR1 DB "Первый",0 STR2 DB "Второй",0 STR3 DB "Третий",0 STR4 DB "Четвертый",0 STR5 DB "Пятый ",0 STR6 DB "Шестой",0 STR7 DB "Седьмой",0 STR8 DB "Восьмой",0 STR9 DB "Девятый",0 STR10 DB "Десятый",0 STR11 DB "Одиннадцатый",0 STR12 DB "Двенадцатый",0 STR13 DB "Тринадцатый",0 STR14 DB "Четырнадцатый",0 STR15 DB "Пятнадцатый",0 INDEX DD OFFSET STR1 DD OFFSET STR2 DD OFFSET STR3 DD OFFSET STR4 DD OFFSET STR5 DD OFFSET STR6 DD OFFSET STR7 DD OFFSET STR8 DD OFFSET STR9 DD OFFSET STR10 DD OFFSET STR11 DD OFFSET STR12 DD OFFSET STR13 DD OFFSET STR14 DD OFFSET STR15 _DATA ENDS
; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA@4 MOV [HINST], EAX ;---------------------------------------- PUSH 0 PUSH OFFSET WNDPROC PUSH 0 PUSH OFFSET PA PUSH [HINST] CALL DialogBoxParamA@20 CMP EAX,-1 JNE KOL ; сообщение об ошибке KOL: ;---------------------------------------- PUSH 0 CALL ExitProcess@4 ;---------------------------------------- ; процедура окна ; расположение параметров в стеке ; [EBP+014Н] ; LPARAM ; [EBP+10H] ; WAPARAM ; [EBP+0CH] ; MES ; [EBP+8] ; HWND WNDPROC PROC PUSH EBP MOV EBP, ESP PUSH EBX PUSH ESI PUSH EDI ;---------------------------------------- CMP DWORD PTR [EBP+0CH],WM_CLOSE JNE L1 PUSH 0 PUSH DWORD PTR [EBP+08H] CALL EndDialog@8 JMP FINISH L1: CMP DWORD PTR [EBP+0CH], WM_INITDIALOG JNE L2 ; загрузить иконку PUSH 3 ; идентификатор иконки PUSH [HINST] ; идентификатор процесса CALL LoadIconA@8 ; установить иконку PUSH EAX PUSH 0 ; тип иконки (маленькая) PUSH WM_SETICON PUSH DWORD PTR [EBP+08H] CALL SendMessageA@16 ; заполнить левый список MOV ECX, 15 MOV ESI, 0 L01: PUSH ECX ; сохранить параметр цикла PUSH INDEX[ESI] PUSH 0 PUSH LB_ADDSTRING PUSH 101 PUSH DWORD PTR [EBP+08H] CALL SendDlgItemMessageA@20 ADD ESI, 4 POP ECX LOOP L01 JMP FINISH L2: CMP DWORD PTR [EBP+0CH],WM_COMMAND JNE L3 ; не сообщение ли от левого списка? CMP WORD PTR [EBP+10Н],101 JNE FINISH ; не было ли двойного щелчка? CMP WORD PTR [EBP+12H], LBN_DBLCLK JNE FINISH ; был двойной щелчок, теперь определим элемент ; получить индекс выбранного элемента L4: PUSH 0 PUSH 0 PUSH LB_GETCURSEL PUSH 101 PUSH DWORD PTR [EBP+08H] CALL SendDlgItemMessageA@20 ; скопировать элемент списка в буфер PUSH OFFSET BUFER PUSH EAX ; индекс записи PUSH LB_GETTEXT PUSH 101 PUSH DWORD PTR [EBP+08H] CALL SendDlgItemMessageA@20 ; определить, нет ли элемента в правом списке PUSH OFFSET BUFER PUSH -1 ; искать во всем списке PUSH LB_FINDSTRING PUSH 102 PUSH DWORD PTR [EBP+08H] CALL SendDlgItemMessageA@20 CMP EAX,-1 JNE FINISH ; элемент нашли ; не нашли, можно добавлять PUSH OFFSET BUFER PUSH 0 PUSH LB_ADDSTRING PUSH 102 PUSH DWORD PTR [EBP+08H] CALL SendDlgItemMessageA@20 MOV EAX,-1 JMP FIN L3: ; здесь проверка, не нажата ли клавиша CMP DWORD PTR [EBP+0CH],WM_VKEYTOITEM JNE FINISH CMP WORD PTR [EBP+10H],VK_INSERT JE L4 MOV EAX,-1 JMP FIN FINISH: MOV EAX, 0 FIN: POP EDI POP ESI POP EBX POP EBP RET 16 WNDPROC ENDP _TEXT ENDS END START
Длина файла
Длина файла
в байтах хранится обычно в двух 32-битных величинах либо в одной 64-битной величине. Если 32-битные величины обозначить как l1 (младшая часть) и l2 (старшая часть), то 64-битная величина выразится формулой l2*0FFFFH+l1. Paзмер файла можно получить функцией GetFileSize.
Если вы только начинаете программировать под Windows
I
Если вы только начинаете программировать под Windows, то в программе на Рисунок 2.1.1 найдете много нового. Поэтому приступим к подробному разбору программы.
В данной программе мы определяем цвет окна и текста через комбинацию трех цветов: красного, зеленого и синего. Цвет определяется одним 32-битным числом. В этом числе первый байт - интенсивность красного, второй байт - интенсивность зеленого, третий байт - интенсивность синего цвета. Последний байт равен нулю. Механизм получения этого числа продемонстрирован в определении константы RGBW. Цвет окна задается посредством определения кисти через функцию CreateSolidBrush. Поскольку при перерисовке окна системой посылается сообщение WM_PAINT, именно при получении этого сообщения и следует перерисовывать содержимое этого окна. В данном случае мы выводим всего лишь одну строку текста. Для того чтобы осуществить вывод информации в окно, необходимо сначала получить контекст окна (контекст устройства - Device Context). Для нас это — просто некоторое число, посредством которого осуществляется связь между приложением и окном. Обычно контекст устройства определяется посредством функции GetDC. При получении сообщения WM_PAINT контекст устройства получается посредством функции BeginPaint. Аргументом для этой функции является указатель на специальную структуру, которая у нас называется PAINTSTR и поля которой, впрочем, мы пока не используем. Текст, как Вы уже, надеюсь, поняли из текста программы, выводится посредством функции OutText. Предварительно, посредством функций SetBkColor и SetTextColor, мы определяем цвет фона и цвет букв. Цвет фона, соответственно, совпадает с цветом окна. Несколько слов о системе координат. Центр системы координат находится в левом верхнем углу, ось Y направлена вниз, ось Х - вправо. Впрочем, это общепринятый вариант для графических экранов. Еще один момент также связан с выводом текста в окно. Одним из параметров функции OutText является количество символов выводимой строки. И здесь начинается самое интересное. Определить длину строки (за минусом нулевого элемента) можно по-разному. Например, можно использовать операторы макроассемблера SIZEOF или LENGTHOF. Но вот беда, в Турбо Ассемблере этих операторов нет. Можно, конечно, решить эту проблему, поставив метку в конце строки или используя старые директивы LENGTH и SIZE. Но, как Вы, наверное, уже поняли, для того чтобы легко переходить от MASM32 к TASM32, следует как можно меньше использовать макросредства. Кроме того, раз уже мы употребляем определение строк, как это принято в Си, — естественно и определить функции для работы со строковыми переменными (см. замечание в конце главы). В данном примере мы определили функцию, которая возвращает длину строки. Не смущайтесь, что функция помещает результат в регистр EBX. Нам просто так удобнее. У функции, кроме того, есть одно очень важное преимущество перед макросредствами - она получает длину при выполнении программы, а не во время ее трансляции.
Теперь, чтобы добиться транслируемости программы на Турбо Ассемблере, нужно проделать те же манипуляции, которые мы производили раньше: убрать суффиксы @N и подключить библиотеку import32.lib.
; файл text1.inc ; константы ; сообщение приходит при закрытии окна WM_DESTROY equ 2 ; сообщение приходит при создании окна WM_CREATE equ 1 ; сообщение приходит при перерисовке окна WM_PAINT equ 0FH ; свойства окна CS_VREDRAW equ 1h CS_HREDRAW equ 2h CS_GLOBALCLASS equ 4000h WS_OVERLAPPEDWINDOW equ 000CF0000H stylcl equ CS_HREDRAW + CS_VREDRAW + CS_GLOBALCLASS DX0 equ 300 DY0 equ 200 ; компоненты цветов RED equ 50 GREEN equ 50 BLUE equ 255 RGBW equ (RED or (GREEN shl 8)) or (BLUE shl 16) RGBT equ 255 ; красный ; идентификатор стандартной иконки IDI_APPLICATION equ 32512 ; идентификатор курсора IDC_CROSS equ 32515 ; режим показа окна — нормальный SW_SHOWNORMAL equ 1 ; прототипы внешних процедур EXTERN CreateWindowExA@48:NEAR EXTERN DefWindowProcA@16:NEAR EXTERN DispatchMessageA@4:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetMessageA@16:NEAR EXTERN GetModuleHandleA@4:NEAR EXTERN LoadCursorA@8:NEAR EXTERN LoadIconA@8:NEAR EXTERN PostQuitMessage@4:NEAR EXTERN RegisterClassA@4:NEAR EXTERN ShowWindow@8:NEAR EXTERN TranslateMessage@4:NEAR EXTERN UpdateWindow@4:NEAR EXTERN BeginPaint@8:NEAR EXTERN EndPaint@8:NEAR EXTERN TextOutA@20:NEAR EXTERN GetStockObject@4:NEAR EXTERN CreateSolidBrush@4:NEAR EXTERN SetBkColor@8:NEAR EXTERN SetTextColor@8:NEAR ; структуры ; структура сообщения MSGSTRUCT STRUC MSHWND DD ? ; идентификатор окна, ; получающего сообщение MSMESSAGE DD ? ; идентификатор сообщения MSWPARAM DD ? ; доп. информация о сообщении MSLPARAM DD ? ; доп. информация о сообщении MSTIME DD ? ; время посылки сообщения MSPT DD ? ; положение курсора во время ; посылки сообщения MSGSTRUCT ENDS
WNDCLASS STRUC CLSSTYLE DD ? ; стиль окна CLSLPFNWNDPROC DD ? ; указатель на процедуру окна CLSCBCLSEXTRA DD ? ; информация о доп. байтах для ; данной структуры CLSCBWNDEXTRA DD ? ; информация о доп. байтах для окна CLSHINSTANCE DD ? ; дескриптор приложения CLSHICON DD ? ; идентификатор иконы окна CLSHCURSOR DD ? ; идентификатор курсора окна CLSHBRBACKGROUND DD ? ; идентификатор кисти окна MENNAME DD ? ; имя-идентификатор меню CLSNAME DD ? ; специфицирует имя класса окон WNDCLASS ENDS
PAINTSTR STRUC hdc DWORD 0 fErase DWORD 0 left DWORD 0 top DWORD 0 right DWORD 0 bottom DWORD 0 fRes DWORD 0 fIncUp DWORD 0 Reserv DB 32 dup(0) PAINTSTR ENDS
; файл text1.asm .386P ; плоская модель .MODEL FLAT, stdcall ;------------------------------------------------------------ include text1.inc ; подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib includelib c:\masm32\lib\gdi32.lib ;------------------------------------------------------------
; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' NEWHWND DD 0 MSG MSGSTRUCT <?> WC WNDCLASS <?> PNT PAINTSTR <?> HINST DD 0 TITLENAME DB 'Текст в окне',0 NAM DB 'CLASS32',0 XT DWORD 30 YT DWORD 30 TEXT DB 'Текст в окне красный',0 _DATA ENDS
; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA@4 MOV [HINST], EAX REG_CLASS: ; заполнить структуру окна стиль MOV [WC.CLSSTYLE] , stylcl ; процедура обработки сообщений MOV [WC.CLSLPFNWNDPROC], OFFSET WNDPROC MOV [WC.CLSCBCLSEXTRA],0 MOV [WC.CLSCBWNDEXTRA],0 MOV EAX, [HINST] MOV [WC.CLSHINSTANCE],EAX ; иконка окна PUSH IDI_APPLICATION PUSH 0 CALL LoadIconA@8 MOV [WC.CLSHICON], EAX ;----------курсор окна PUSH IDC_CROSS PUSH 0 CALL LoadCursorA@8 MOV [WC.CLSHCURSOR],EAX PUSH RGBW ; цвет кисти CALL CreateSolidBrush@4 ; создать кисть MOV [WC.CLSHBRBACKGROUND],EAX MOV DWORD PTR [WC.MENNAME],0 MOV DWORD PTR [WC.CLSNAME], OFFSET NAM PUSH OFFSET WC CALL RegisterClassA@4 ; создать окно зарегистрированного класса PUSH 0 PUSH [HINST] PUSH 0 PUSH 0 PUSH DY0 ; DY0 - высота окна PUSH DX0 ; DX0 - ширина окна PUSH 100 ; координата Y PUSH 100 ; координата X PUSH WS_OVERLAPPEDWINDOW PUSH OFFSET TITLENAME ; имя окна PUSH OFFSET NAM ; имя класса PUSH 0 CALL CreateWindowExA@48 ; проверка на ошибку CMP EAX, 0 JZ _ERR MOV [NEWHWND], EAX ; дескриптор окна ;------------------------------------------------------------ PUSH SW_SHOWNORMAL PUSH [NEWHWND] CALL ShowWindow@8 ; показать созданное окно ; ———————————————————— PUSH [NEWHWND] CALL UpdateWindow@4 ; перерисовать видимую часть окна ; петля обработки сообщений MSG_LOOP: PUSH 0 PUSH 0 PUSH 0 PUSH OFFSET MSG CALL GetMessageA@16 CMP AX, 0 JE END_LOOP PUSH OFFSET MSG CALL TranslateMessage@4 PUSH OFFSET MSG CALL DispatchMessageA@4 JMP MSG_LOOP END_LOOP: ; выход из программы (закрыть процесс) PUSH [MSG.MSWPARAM] CALL ExitProcess@4 _ERR: JMP END_LOOP ; процедура окна ; расположение параметров в стеке ; [EBP+014Н] ; LPARAM ; [EBP+10H] ; WAPARAM ; [EBP+0CH] ; MES ; [EBP+8] ; HWND WNDPROC PROC PUSH EBP MOV EBP,ESP PUSH EBX PUSH ESI PUSH EDI CMP DWORD PTR [EBP+0CH], WM_DESTROY JE WMDESTROY CMP DWORD PTR [EBP+0CH], WM_CREATE JE WMCREATE CMP DWORD PTR [EBP+0CH], WM_PAINT JE WMPAINT JMP DEFWNDPROC WMPAINT: PUSH OFFSET PNT PUSH DWORD PTR [EBP+08H] CALL BeginPaint@8 PUSH EAX ; сохранить контекст (дескриптор) ;---------------- цвет фона = цвет окна PUSH RGBW PUSH EAX CALL SetBkColor@8 ;---------------- контекст POP EAX PUSH EAX ;---------------- цвет текста (красный) PUSH RGBT PUSH EAX CALL SetTextColor@8 ;---------------- контекст POP EAX ;---------------- вывести текст PUSH OFFSET TEXT CALL LENSTR PUSH EBX ; длина строки PUSH OFFSET TEXT ; адрес строки PUSH YT ; Y PUSH XT ; X PUSH EAX ; контекст окна CALL TextOutA@20 ;---------------- закрыть PUSH OFFSET PNT PUSH DWORD PTR [EBP+08H] CALL EndPaint@8 MOV EAX, 0 JMP FINISH WMCREATE: MOV EAX, 0 JMP FINISH DEFWNDPROC: PUSH DWORD PTR [EBP+14H] PUSH DWORD PTR [EBP+10H] PUSH DWORD PTR [EBP+0CH] PUSH DWORD PTR [EBP+08H] CALL DefWindowProcA@16 JMP FINISH WMDESTROY: PUSH 0 CALL PostQuitMessage@4 ; WM_QUIT MOV EAX, 0 FINISH: POP EDI POP ESI POP EBX POP EBP RET 16 WNDPROC ENDP
;----------- функция -------------------------- ; длина строки ; [EBP+08H] - указатель на строку LENSTR PROC PUSH EBP MOV EBP, ESP PUSH ESI MOV ESI, DWORD PTR [EBP+8] XOR EBX, EBX LBL1: CMP BYTE PTR [ESI], 0 JZ LBL2 INC EBX INC ESI JMP LBL1 LBL2: POP ESI POP EBP RET 4 LENSTR ENDP _TEXT ENDS END START
Файл имеет три временные характеристики
Файл имеет три временные характеристики
: время создания, время последней модификации, время последнего доступа. Время отсчитывается в наносекундных интервалах начиная с 12.00 по полудни 1 января 1600 года и хранится в двух 32-битных величинах. Надо сказать, что время хранится в так называемых универсальных координатах и должно еще быть преобразовано в локальное время (функция FileTimeToLocalFileTime). Получить значение всех трех времен можно функцией GetFileTime.
Фрагмент программы выводящей текст с заданным шрифтом (см Рисунок )
Рисунок 2.1.3. Фрагмент программы, выводящей текст с заданным шрифтом (см. Рисунок 2.1.5).
Как видно из фрагмента, создание шрифта производится по следующей схеме: надо задать шрифт при помощи функции CreateFontIndirect, выбрать шрифт функцией SelectObject, вывести текст заданным шрифтом, удалить созданный шрифт (объект). Поле LfFaceName структуры LOGFONT должно содержать название шрифта. Если такого шрифта нет, выводится шрифт по умолчанию. Название шрифта у нас задано в строке NFONT, и мы копируем его в поле LfFaceName при помощи функции COPYSTR, текст которой приводится на Рисунок 2.1.4.
; процедура копирования одной строки в другую ; строка, куда копировать [EBP+08H] ; строка, что копировать [EBP+0CH] COPYSTR PROC PUSH EBP MOV EBP,ESP MOV ESI, DWORD PTR [EBP+0CH] MOV EDI, DWORD PTR [EBP+08H] L1: MOV AL,BYTE PTR [ESI] MOV BYTE PTR [EDI],AL CMP AL,0 JE L2 INC ESI INC EDI JMP L1 L2: POP EBP RET 8 COPYSTR ENDP
Макросредства ассемблера и программирование в Windows
Глава 6. Макросредства ассемблера и программирование в Windows
Понятие ресурса Редакторы и трансляторы ресурсов
Глава 3. Понятие ресурса. Редакторы и трансляторы ресурсов
В операционную систему Windows введено понятие ресурса. Ресурс представляет собой некий визуальный элемент с заданными свойствами, хранящийся в исполняемом файле отдельно от кода и данных, который может отображаться специальными функциями.
Использование ресурсов дает две вполне определенные выгоды:
Ресурсы загружаются в память лишь при обращении к ним, т.е. реализуется экономия памяти. Свойства ресурсов поддерживаются системой автоматически, не требуя от программиста написания дополнительного кода.
Описание ресурсов хранится отдельно от программы в текстовом файле (*.rc) и компилируется (*.res) специальным транслятором ресурсов. В исполняемый файл ресурсы включаются компоновщиком. Транслятором ресурсов в пакете MASM32 является RC.EXE, в пакете TASM32 - BRCC32.EXE.
Примеры программ использующих ресурсы
Глава 4. Примеры программ использующих ресурсы
Вопрос использования ресурсов при программировании в Windows весьма важен, поэтому я посвящаю ему еще одну главу. Здесь будет приведено три более сложных примера на использование ресурсов и подробное их разъяснение.
Примеры простейших программ
Глава 1. Примеры простейших программ
В данной главе мы серьезно начинаем работать с сообщением WM_PAINT. В главе 1.3 мы уже рассматривали это сообщение, но не применяли его. Причиной было то, что в окне у нас были лишь управляющие элементы, но не было текстовой информации и графики. Теперь мы исправляем положение. Кроме сообщения WM_PAINT, речь в этой главе пойдет о множестве проблем, возникающих при программировании в Windows.
Управление файлами
Глава 5. Управление файлами
Файл - важнейшая образующая практически любой программы. С появлением скоростных дисков больших объемов значение файлов сильно возросло. Использование API-функций управления файлами может сделать вашу программу более эффективной и производительной. Большинство программ данной главы являются консольными, потому что консольная программа как никакая другая подходит для демонстрации файловой обработки.
Итак продолжим рассматривать ресурсы
II
Итак, продолжим рассматривать ресурсы. Хочется рассказать о весьма интересном приеме, который можно использовать при работе с окнами редактирования. Наверное, Вы работали с визуальными языками типа Visual Basic, Delphi и пр. и обратили внимание, что окна редактирования можно так запрограммировать, а точнее, задать их свойства, что они позволят вводить только вполне определенные символы. В Delphi это свойство называется EditMask. Я думаю. Вам хотелось бы понять, как подобное реализовать только API-средствами. Но обо всем по порядку.
Обычное окно при нажатии клавиши (если в нем находится фокус) получает сообщения WM_KEYDOWN, WM_KEYUP и их квинтэссенцию WM_CHAR. Но в данном случае мы имеем дело не с обычным окном, а с диалоговым. Диалоговое окно таких сообщений не получает. Остается надеяться на сообщения, посылаемые на события, происходящие с самим элементом "окном редактирования". Но, увы, и здесь нас ждут разочарования. Данный элемент получает лишь два сообщения из тех, которые нас хоть как-то могут заинтересовать. Это сообщение EN_UPDATE и сообщение EN_CHANGE. Оба сообщения приходят, когда уже произведено изменение в окне редактирования. Но сообщение EN_UPDATE приходит, когда изменения на экране еще не произведены, а EN_CHANGE - после таких изменений. Нам придется сначала получить содержимое окна редактирования, определить, какой символ туда поступил последним, и если он недопустим, удалить его из строки и послать строку в окно снова. Добавьте сюда еще проблему, связанную с положением курсора и вторичным приходом сообщения EN_UPDATE. Лично я по такому пути бы не пошел.
Есть другой более изящный и короткий путь: использовать понятие горячей клавиши (HOTKEY). Мы ограничимся лишь программными свойствами горячих клавиш, то есть свойствами, которые необходимо знать программисту, чтобы использовать горячие клавиши в своих программах.
Горячая клавиша может быть определена для любой виртуальной клавиши, клавиши, определяемой через макроконстанты с префиксом VK. Для обычных алфавитно-цифровых клавиш значение этих констант просто совпадает с кодами ASCII. Возможны также сочетания с управляющими клавишами Alt, Control, Shift. После того как для данного окна определена горячая клавиша, при ее нажатии на функцию окна приходит сообщение WM_HOTKEY. По параметрам можно определить, какая именно горячая клавиша была нажата. Существенно, что понятие горячей клавиши глобально, т.е. она будет срабатывать, если будут активны другие окна и даже окна других приложений. Это требует от программиста весьма аккуратной работы, так как вы можете заблокировать нормальную работу других приложений. Т.е. необходимо отслеживать, когда данное окно активно, а когда нет. Этому весьма могут помочь сообщения WM_ACTIVATE и WM_ACTIVATEAPP. Первое сообщение всегда приходит на функцию окна тогда, когда окно активизируется или деактивизируется. Первый раз сообщение приходит при создании окна. Вот при получении этого сообщения и есть резон зарегистрировать горячие клавиши. Второе сообщение всегда приходит на функцию окна, когда окно теряет "фокус" - активизируется другое окно. Соответственно, при получении этого сообщения и следует отменить регистрацию этих клавиш.
Для работы с горячими клавишами используют в основном две функции: RegisterHotKey и UnregisterHotKey. Функция RegisterHotKey имеет следующие параметры:
первый - дескриптор окна; второй - идентификатор горячей клавиши; третий - модификатор, определяющий, не нажата ли управляющая клавиша; четвертый - виртуальный код клавиши.
Функция UnregisterHotKey имеет всего два параметра:
первый - дескриптор окна; второй - идентификатор.
Важно то, что если мы определили горячую клавишу, она перестает участвовать в каких-либо событиях, фактически оказывается заблокированной. Единственный метод, с помощью которого можно судить о нажатии этой клавиши - сообщение WM_HOTKEY.
Рассмотрим простой пример диалогового окна, на котором расположены два окна редактирования и кнопка выхода. Поставим перед собой такую цель. Первое окно редактирования должно пропускать только цифры от 0 до 9. Во второе окно можно вводить все символы. Выше рассматривался возможный механизм использования горячих клавиш с сообщениями WM_ACTIVATE и WM_ ACTIVATEAPP. Ясно, что эти события в данном случае нам ничем не помогут. Здесь дело тоньше, надо использовать сообщения, относящиеся к одному окну редактирования. Это сообщения EN_SETFOCUS и EN_KILLFOCUS, передаваемые, естественно, через сообщение WM_COMMAND. Ниже представлена программа, демонстрирующая этот механизм, и комментарий к ней. Сообщение EN_SETFOCUS говорит о том, что окно редактирования приобрело фокус (стало активным), а сообщение EN_KILLFOCUS - что окно редактирования потеряло фокус.
// файл dial1.rc // определение констант // стили окна #define WS_SYSMENU 0x00080000L #define WS_MINIMIZEBOX 0x00020000L #define WS_MAXIMIZEBOX 0x00010000L
// текст в окне редактирования прижат к левому краю #define ES_LEFT 0x0000L // стиль всех элементов на окне #define WS_CHILD 0x40000000L // элементы на окне должны быть изначально видимы #define WS_VISIBLE 0x10000000L // бордюр вокруг элемента #define WS_BORDER 0x00800000L // при помощи TAB можно по очереди активизировать элементы #define WS_TABSTOP 0x00010000L // прижать строку к левому краю отведенного поля #define SS_LEFT 0x00000000L // стиль кнопка #define BS_PUSHBUTTON 0x00000000L // центрировать текст на кнопке #define BS_CENTER 0x00000300L #define DS_LOCALEDIT 0x20L
// определение диалогового окна DIAL1 DIALOG 0, 0, 240, 120 STYLE WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX CAPTION "Пример диалогового окна" FONT 8, "Arial" { // поле редактирования, идентификатор 1 CONTROL "", 1, "edit", ES_LEFT | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 24, 20, 128, 12 // еще одно поле редактирования, идентификатор 2 CONTROL "", 2, "edit", ES_LEFT | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 24, 52, 127, 12 // текст, идентификатор 3 CONTROL "Строка 1", 3, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 164, 22, 60, 8 // еще текст, идентификатор 4 CONTROL "Строка 2", 4, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 163, 54, 60, 8 // кнопка, идентификатор 5 CONTROL "Выход", 5, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISlBLE | WS_TABSTOP, 180, 76, 50, 14 }
;файл dial1.inc ; константы ; сообщение приходит при закрытии окна WM_CLOSE equ 10h WM_INITDIALOG equ 110h WM_COMMAND equ 111h WM_SETTEXT equ 0Ch WM_HOTKEY equ 312h EN_SETFOCUS equ 100h EN_KILLFOCUS equ 200h
; прототипы внешних процедур EXTERN UnregisterHotKey@8:NEAR EXTERN RegisterHotKey@16:NEAR EXTERN MessageBoxA@16:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetModuleHandleA@4:NEAR EXTERN DialogBoxParamA@20:NEAR EXTERN EndDialog@8:NEAR EXTERN SendMessageA@16:NEAR EXTERN GetDlgItem@8:NEAR EXTERN MessageBoxA@16:NEAR
; структуры ; структура сообщения MSGSTRUCT STRUC MSHWND DWORD ? MSMESSAGE DWORD ? MSWPARAM DWORD ? MSLPARAM DWORD ? MSTIME DWORD ? MSPT DWORD ? MSGSTRUCT ENDS
;файл dial.asm .386P ; плоская модель .MODEL FLAT, stdcall include dial1.inc ; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ; ------------------------------------------------------------ ; сегмент данных DATA SEGMENT DWORD PUBLIC USE32 'DATA' MSG MSGSTRUCT <?> HINST DD 0 ; дескриптор приложения PA DB "DIAL1",0 STR1 DB "Неправильный символ !",0 STR2 DB "Ошибка !",0 ; таблица для создания горячих клавиш TAB DB 32,33,34,35,36,37,38,39,40 DB 41,42,43,44,45,46,47,58,59,60 DB 61,62,63,64,65,66,67,68,69,70 DB 71,72,73,74,75,76,77,78,79,80 DB 81,82,83,84,85,86,87,88,89,90 DB 91,92,93,94,95,96,97,98,99,100 DB 101,102,103,104,105,106,107,108,109,110 DB 111,112,113,114,115,116,117,118,119,120 DB 121,122,123,124,125,126,127,128,129,130 DB 131,132,133,134,135,136,137,138,139,140 DB 141,142,143,144,145,146,147,148,149,150 DB 151,152,153,154,155,156,157,158,159,160 DB 161,162,163,164,165,166,167,168,169,170 DB 171,172,173,174,175,176,177,178,179,180 DB 181,182,183,184,185,186,187,188,189,190 DB 191,192,193,194,195,196,197,198,199,200 DB 201,202,203,204,205,206,207,208,209,210 DB 211,212,213,214,215,216,217,218,219,220 DB 221,222,223,224,225,226,227,228,229,230 DB 231,232,233,234,235,236,237,238,239,240 DB 241,242,243,244,245,246,247,248,249,250 DB 251,252,253,254,255 _DATA ENDS
; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA@4 MOV [HINST], EAX ;---------------------------------------- PUSH 0 PUSH OFFSET WNDPROC PUSH 0 PUSH OFFSET PA PUSH [HINST] CALL DialogBoxParamA@20 CMP EAX,-1 JNE KOL KOL: ;---------------------------------------- PUSH 0 CALL ExitProcess@4 ;---------------------------------------- ; процедура окна ; расположение параметров в стеке ; [EBP+014Н] ; LPARAM ; [EBP+10H] ; WAPARAM ; [EBP+0CH] ; MES ; [EBP+8] ; HWND WNDPROC PROC PUSH EBP MOV EBP,ESP PUSH EBX PUSH ESI PUSH EDI ;---------------------------------------- CMP DWORD PTR [EBP+0CH], WM_CLOSE JNE L1 PUSH 0 PUSH DWORD PTR [EBP+08H] CALL EndDialog@8 MOV EAX, 1 JMP FIN L1: CMP DWORD PTR [EBP+0CH],WM_INITDIALOG JNE L2 ; здесь заполнить окна редактирования, если надо ; ; MOV EAX, 1 JMP FIN L2: CMP DWORD PTR [EBP+0CH],WM_COMMAND JNE L5 ; кнопка выхода ? CMP WORD PTR [EBP+10H], 5 JNE L3 PUSH 0 PUSH DWORD PTR [EBP+08H] CALL EndDialog@8 MOV EAX, 1 JMP FIN L3: CMP WORD PTR [EBP+10H], 1 JNE FINISH ; блок обработки сообщений первого окна редактирования CMP WORD PTR [EBP+12H], EN_KILLFOCUS JNE L4 ; окно редактирования с идентификатором 1 теряет фокус MOV EBX, 0 ; снимаем все горячие клавиши L33: MOVZX EAX,BYTE PTR [ТАВ+EBX] PUSH EAX PUSH DWORD PTR [EBP+08Н] CALL UnregisterHotKey@8 INC EBX CMP EBX, 214 JNE L33 MOV EAX, 1 JMP FIN L4: CMP WORD PTR [EBP+12H],EN_SETFOCUS JNE FINISH ; окно редактирования с идентификатором 1 получает фокус MOV EBX, 0 ; устанавливаем горячие клавиши L44: MOVZX EAX,BYTE PTR [ТАВ+EBX] PUSH EAX PUSH 0 PUSH EAX PUSH DWORD PTR [EBP+08Н] CALL RegisterHotKey@16 INC EBX CMP EBX, 214 JNE L44 MOV EAX, 1 JMP FIN L5: CMP DWORD PTR [EBP+0CH],WM_HOTKEY JNE FINISH ; здесь реакция на неправильно введенный символ PUSH 0 ; МВ_ОК PUSH OFFSET STR2 PUSH OFFSET STR1 PUSH DWORD PTR [EBP+08Н] ; дескриптор окна CALL MessageBoxA@16 FINISH: MOV EAX, 0 FIN: POP EDI POP ESI POP EBX POP EBP RET 16 WNDPROC ENDP _TEXT ENDS END START
Этот раздел главы посвящается графике
III
Этот раздел главы посвящается графике. Впрочем, основы графики в Windows в принципе достаточно тривиальны, поэтому мы рассмотрим один простой пример - на вывод графических образов. Но в начале я изложу некоторые опорные моменты:
1. Система координат для вывода графических образов такая же, как и для ввода текстовой информации. Координаты измеряются в логических единицах, которые по умолчанию совпадают с пикселями. При желании эту пропорцию можно изменить.
2. Цвет рисования образуется тремя способами. При использовании функции SetPixel задается цвет данной точки. Для линий необходимо задать цвет пера. Для задания цвета замкнутых графических объектов следует задать цвет кисти.
3. Перо создается при помощи функции CreatePen, кисть - при помощи CreateSolidBrush (мы ее уже использовали). Для создания разноцветной картинки можно заранее создать несколько кистей и перьев, а затем в нужный момент выбирать при помощи функции SelectObject (мы также уже использовали эту функцию).
4. Для рисования можно использовать следующие функции API:
SetPixel - установить заданный цвет пикселя. LineTo - провести линию от текущей точки до точки с заданными координатами, которая в свою очередь становится текущей. MoveToEx - сменить текущую точку. Arc - рисование дуги. Rectangle - нарисовать прямоугольник. RoundRect - нарисовать прямоугольник с округленными углами. Ellipse, Pie - рисование эллипсов и секторов эллипсов.
5. Если при рисовании замкнутой фигуры был установлен цвет кисти, отличный от цвета основного фона, то замкнутая фигура окрашивается этим цветом.
6. Для установки соотношения между логическими единицами и пикселями используется функция SetMapMode.
7. Можно установить область вывода при помощи функции SetViewportExtEx. С помощью функции SetViewportOrgEx можно задать начало области ввода.
После всего сказанного пора продемонстрировать программу. Программа достаточно проста, но в ней заложены основы работы с графикой. По щелчку левой кнопки мыши сначала появляется горизонтальная линия, по второму щелчку - наклонная линия, по третьему щелчку - заполненный прямоугольник. Программа показана на Рисунок 2.1.6, результат ее работы - на Рисунок 2.1.7.
; файл graph1.inc ; константы ; сообщение приходит при закрытии окна WM_DESTROY equ 2 ; сообщение приходит при создании окна WM_CREATE equ 1 ; сообщение при щелчке левой кнопкой мыши в области окна WM_LBUTTONDOWN equ 201h ; сообщение приходит при перерисовке окна WM_PAINT equ 0FH ; свойства окна CS_VREDRAW equ 1h CS_HREDRAW equ 2h CS_GLOBALCLASS equ 4000h WS_OVERLAPPEDWINDOW equ 000CF0000H stylcl equ CS_HREDRAW + CS_VREDRAW + CS_GLOBALCLASS DX0 equ 600 DY0 equ 400 ; компоненты цветов RGBW equ (50 or (50 shl 8)) or (255 shl 16) ; цвет окна RGBR equ 150 ; цвет региона RGBL equ 0 ; цвет линии RGBP equ 255 or (100 shl 8) ; цвет точки ; идентификатор стандартной иконки IDI_APPLICATION equ 32512 ; идентификатор курсора IDC_CROSS equ 32515 ; режим показа окна - нормальный SW_SHOWNORMAL equ 1 ; прототипы внешних процедур EXTERN CreateWindowExA@48:NEAR EXTERN DefWindowProcA@16:NEAR EXTERN DispatchMessageA@4:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetMessageA@16:NEAR EXTERN GetModuleHandleA@4:NEAR EXTERN LoadCursorA@8:NEAR EXTERN LoadIconA@8:NEAR EXTERN PostQuitMessage@4:NEAR EXTERN RegisterClassA@4:NEAR EXTERN ShowWindow@8:NEAR EXTERN TranslateMessage@4:NEAR EXTERN UpdateWindow@4:NEAR EXTERN BeginPaint@8:NEAR EXTERN EndPaint@8:NEAR EXTERN GetStockObject@4:NEAR EXTERN CreateSolidBrush@4:NEAR EXTERN GetSystemMetrics@4:NEAR EXTERN GetDC@4:NEAR EXTERN CreateCompatibleDC@4:NEAR EXTERN SelectObject@8:NEAR EXTERN CreateCompatibleBitmap@12:NEAR EXTERN PatBlt@24:NEAR EXTERN BitBlt@36:NEAR EXTERN ReleaseDC@8:NEAR EXTERN DeleteObject@4:NEAR EXTERN InvalidateRect@12:NEAR EXTERN GetStockObject@4:NEAR EXTERN DeleteDC@4:NEAR EXTERN CreatePen@12:NEAR EXTERN SetPixel@16:NEAR EXTERN LineTo@12:NEAR EXTERN MoveToEx@16:NEAR EXTERN Rectangle@20:NEAR
; структуры ; структура сообщения MSGSTRUCT STRUC MSHWND DD ? ; идентификатор окна, получающего сообщение MSMESSAGE DD ? ; идентификатор сообщения MSWPARAM DD ? ; доп. информация о сообщении MSLPARAM DD ? ; доп. информация о сообщении MSTIME DD ? ; время посылки сообщения MSPT DD ? ; положение курсора во время ; посылки сообщения MSGSTRUCT ENDS
WNDCLASS STRUC CLSSTYLE DD ? ; стиль окна CLSLPFNWNDPROC DD ? ; указатель на процедуру окна CLSCBCLSEXTRA DD ? ; информация о доп. байтах для ; данной структуры
CLSCBWNDEXTRA DD ? ; информация о доп. байтах для окна CLSHINSTANCE DD ? ; дескриптор приложения CLSHICON DD ? ; идентификатор иконы окна CLSHCURSOR DD ? ; идентификатор курсора окна CLSHBRBACKGROUND DD ? ; идентификатор кисти окна MENNAME DD ? ; имя-идентификатор меню CLSNAME DD ? ; специфицирует имя класса окон WNDCLASS ENDS
PAINTSTR STRUC hdc DD 0 fErase DD 0 left DD 0 top DD 0 right DD 0 bottom DD 0 fRes DD 0 fIncUp DD 0 Reserv DB 32 dup(0) PAINTSTR ENDS
RECT STRUC L DD ? ; Х-левого верхнего угла T DD ? ; Y-левого верхнего угла R DD ? ; Х- правого нижнего угла B DD ? ; Y- правого нижнего угла RECT ENDS
; файл graph.asm .386P ; плоская модель .MODEL FLAT, stdcall ;------------------------------------------------------------------ include graph1.inc ; подключения библиотек includelib c:\masm32\iib\user32.lib includelib c:\masm32\lib\kernel32.lib includelib c:\masm32\lib\gdi32.lib ;------------------------------------------------------------------ ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' NEWHWND DWORD 0 MSG MSGSTRUCT <?> WC WNDCLASS <?> PNT PAINTSTR <?> HINST DWORD 0 TITLENAME BYTE 'Графика в окне',0 NAM BYTE 'CLASS32',0 XT DWORD 30 YT DWORD 30 XM DWORD ? YM DWORD ? HDC DWORD ? MEMDC DWORD ? HPEN DWORD ? HBRUSH DWORD ? P DWORD 0 ; признак вывода XP DWORD ? YP DWORD ? _DATA ENDS
; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA@4 MOV [HINST], EAX REG_CLASS: ; заполнить структуру окна ; стиль MOV [WC.CLSSTYLE],stylcl ; процедура обработки сообщений MOV [WC.CLSLPFNWNDPROC], OFFSET WNDPROC MOV [WC.CLSCBCLSEXTRA],0 MOV [WC.CLSCBWNDEXTRA], 0 MOV EAX, [HINST] MOV [WC.CLSHINSTANCE], EAX ; ----------иконка окна PUSH IDI_APPLICATION PUSH 0 CALL LoadIconA@8 MOV [WC.CLSHICON], EAX ; ----------курсор окна PUSH IDC_CROSS PUSH 0 CALL LoadCursorA@8 MOV [WC.CLSHCURSOR], EAX ;----------- PUSH RGBW ; цвет кисти CALL CreateSolidBrush@4 ; создать кисть MOV [WC.CLSHBRBACKGROUND], EAX MOV DWORD PTR [WC.MENNAME],0 MOV DWORD PTR [WC.CLSNAME], OFFSET NAM PUSH OFFSET WC CALL RegisterClassA@4 ; создать окно зарегистрированного класса PUSH 0 PUSH [HINST] PUSH 0 PUSH 0 PUSH DY0 ; DYO - высота окна PUSH DX0 ; DXO - ширина окна PUSH 100 ; координата Y PUSH 100 ; координата X PUSH WS_OVERLAPPEDWINDOW PUSH OFFSET TITLENAME ; имя окна PUSH OFFSET NAM ; имя класса PUSH 0 CALL CreateWindowExA@48 ; проверка на ошибку CMP EAX, 0 JZ _ERR MOV [NEWHWND], EAX ; дескриптор окна ;------------------------------------------------------------ PUSH SW_SHOWNORMAL PUSH [NEWHWND] CALL ShowWindow@8 ; показать созданное окно ;------------------------------------------------------------ PUSH [NEWHWND] CALL UpdateWindow@4 ; перерисовать видимую часть окна ; петля обработки сообщений MSG_LOOP: PUSH 0 PUSH 0 PUSH 0 PUSH OFFSET MSG CALL GetMessageA@16 CMP AX, 0 JE END_LOOP PUSH OFFSET MSG CALL TranslateMessage@4 PUSH OFFSET MSG CALL DispatchMessageA@4 JMP MSG_LOOP END_LOOP: ; выход из программы (закрыть процесс) PUSH [MSG.MSWPARAM] CALL ExitProcess@4 _ERR: JMP END_LOOP ;------------------------------------------------------------ ; процедура окна ; расположение параметров в стеке ; [EBP+014Н] ; LPARAM ; [EBP+10H] ; WAPARAM ; [EBP+0CH] ; MES ; [EBP+8] ; HWND WNDPROC PROC PUSH EBP MOV EBP,ESP PUSH EBX PUSH ESI PUSH EDI CMP DWORD PTR [EBP+0CH],WM_DESTROY JE WMDESTROY CMP DWORD PTR [EBP+0CH],WM_CREATE JE WMCREATE CMP DWORD PTR [EBP+0CH],WM_PAINT JE WMPAINT CMP DWORD PTR [EBP+0CH],WM_LBUTTONDOWN JE LBUTTON JMP DEFWNDPROC LBUTTON: CMP P,0 JNE F1 ; линия точками (горизонтальная) MOV YP,50 ; Y MOV XP,10 ; X MOV ECX,200 LL: PUSH ECX PUSH RGBP PUSH YP PUSH XP PUSH MEMDC CALL SetPixel@16 INC XP POP ECX LOOP LL INC P JMP F3 F1: CMP P,1 JNE F2 ; вначале установим текущие координаты на конец ; предыдущей линии PUSH 0 PUSH YP PUSH XP PUSH MEMDC CALL MoveToEx@16 ; линия пером PUSH 300 PUSH 550 PUSH MEMDC CALL LineTo@12 INC P JMP F3 F2: CMP P,2 JNE FIN ; замкнутая фигура - прямоугольник ; вначале выбрать кисть для заполнения области PUSH HBRUSH PUSH MEMDC CALL SelectObject@8 ; теперь рисуем заполненный прямоугольник если не выбирать ; кисть, то будет нарисован незаполненный прямоугольник PUSH 350 PUSH 400 PUSH 200 PUSH 200 PUSH MEMDC CALL Rectangle@20 INC P F3: ; дать команду перерисовать окно PUSH 0 PUSH OFFSET RECT PUSH DWORD PTR [EBP+08H] CALL InvalidateRect@12 FIN: MOV EAX, 0 JMP FINISH WMPAINT: PUSH OFFSET PNT PUSH DWORD PTR [EBP+08H] CALL BeginPaint@8 MOV HDC,EAX ; сохранить контекст (дескриптор) ; скопировать виртуальное окно на реальное PUSH 0CC0020h ; SRCCOPY=изображение как есть PUSH 0 ; у-источника PUSH 0 ; х-источника PUSH MEMDC ; контекст источника PUSH YM ; высота - куда PUSH XM ; ширина - куда PUSH 0 ; у - куда PUSH 0 ; х-куда PUSH HDC ; контекст - куда CALL BitBlt@36 ;---------------- закрыть контекст окна PUSH OFFSET PNT PUSH DWORD PTR [EBP+08H] CALL EndPaint@8 MOV EAX, 0 JMP FINISH WMCREATE: ; размеры экрана PUSH 0 ; X CALL GetSystemMetrics@4 MOV XM, EAX PUSH 1 ; Y CALL GetSystemMetrics@4 MOV YM, EAX ; открыть контекст окна PUSH DWORD PTR [EBP+08H] CALL GetDC@4 MOV HDC,EAX ; создать совместимый с данным окном контекст PUSH EAX CALL CreateCompatibleDC@4 MOV MEMDC, EAX ; создать в памяти растровое изображение, совместимое с hdc PUSH YM PUSH XM PUSH HDC CALL CreateCompatibleBitmap@12 ; выбрать растровое изображение в данном контексте PUSH EAX PUSH MEMDC CALL SelectObject@8 ; цвет кисти PUSH RGBW CALL CreateSolidBrush@4 ; создать кисть ; выбрать кисть в данном контексте PUSH EAX PUSH MEMDC CALL SelectObject@8 ; заполнить данную прямоугольную область PUSH 0F00021h ; РАТСОРУ=заполнить данным цветом PUSH YM PUSH XM PUSH 0 PUSH 0 PUSH MEMDC CALL PatBlt@24 ; создать кисть и перо для рисования ; цвет кисти PUSH RGBR CALL CreateSolidBrush@4 ; создать кисть MOV HBRUSH,EAX ; задать перо PUSH RGBR ; цвет PUSH 0 ; толщина=1 PUSH 0 ; сплошная линия CALL CreatePen@12 MOV HPEN, EAX ; удалить контекст PUSH HDC PUSH DWORD PTR [EBP+08H] CALL ReleaseDC@8 MOV EAX, 0 JMP FINISH DEFWNDPROC: PUSH DWORD PTR [EBP+14H] PUSH DWORD PTR [EBP+10H] PUSH DWORD PTR [EBP+0CH] PUSH DWORD PTR [EBP+08H] CALL DefWindowProcA@16 JMP FINISH WMDESTROY: ; удалить перо PUSH HPEN CALL DeleteDC@4 ; удалить кисть PUSH HBRUSH CALL DeleteDC@4 ; удалить виртуальное окно PUSH MEMDC CALL DeleteDC@4 ; выход PUSH 0 CALL PostQuitMessage@4 ; WM_QUIT MOV EAX, 0 FINISH: POP EDI POP ESI POP EBX POP EBP RET 16 WNDPROC ENDP _TEXT ENDS END START
Конструкции времени исполнения программы
Конструкции времени исполнения программы
1. Условные конструкции.
а) .IF условие ... .ENDIF б) .IF условие ... .ELSE ... .ENDIF в) .IF условие 1 ... .ELSEIF условие 2 ... .ELSEIF условие 3 ... ... .ELSE ... .ENDIF
Рассмотрим следующий фрагмент, содержащий условную конструкцию и соответствующий ей ассемблерный код.
.IF EAX==12H MOV EAX,10H .ELSE MOV EAX,15Н .ENDIF
эквивалентен следующему ассемблерному коду:
CMP EAX,12Н JNE NO_EQ MOV EAX,10H JMP EX_BLOK NO_EQ: MOV EAX,15Н EX_BLOK:
Весьма удобная штука, но не увлекайтесь: на мой взгляд, это сильно расслабляет.
2. Цикл "пока".
.WHILE условие ... .ENDW
Пример.
WHILE EAX<64H ADD EAX,10 ENDW
Для MASM:
JMP L2 L1: ADD EAX,10Н L2: CMP EAX,64Н JB L1
Для TASM:
L1: CМР EAX,64Н JNB EXI ADD EAX,10Н JMP L1 EXI:
Есть некоторое отличие в том, как два ассемблера транслируют директивы .IF и .WHILE. Транслятор TASM32 производит автоматически оптимизацию на предмет выравнивания по границе учетверенного слова, добавляя дополнительно команды NOP. Это несколько ускоряет выполнение программы, но увеличивает ее объем. Мне ближе позиция MASM.
Кроме указанных характеристик
Кроме указанных характеристик,
файл, разумеется, имеет имя. При этом мы будем различать длинное и короткое имя. Точно также будем различать полный путь (со всеми длинными именами) и укороченный путь (все длинные имена заменены укороченными). Необходимость использования укороченного имени и пути диктуется, прежде всего, тем, что некоторые программы получают путь или имя на стандартный вход и трактуют пробелы как разделители для параметров. Преобразование длинного имени в короткое можно осуществить функцией GetShortPathName, которая работает и для имени, и для пути. Обратное преобразование можно осуществить функиией GetFullPathName.
В данной книге мы не рассматриваем вопроса о прямом доступе к диску. Но вопрос о структуре записей каталога может у читателя все же возникнуть. Это и понятно, ведь с переходом к FAT3233, во-первых, появилась возможность хранения файлов с длинным именем, во-вторых, у файла, кроме времени и даты модификации, появились еще время и дата создания и доступа. Где же все это хранится?
Для того чтобы ответить на поставленный вопрос, вспомним, что каталог в файловых системах FAT34 делится на записи длиной 32 байта. Ниже приводится структура записи для FAT32. Пустыми записями считаются записи, содержащие нулевые байты, либо начинающиеся с кода E5H (для удаленных записей). На файл с обычным именем (восемь байт на имя и 3-на расширение) отводится 32 байта. В байте со смещением +11 содержится атрибут файла. Если атрибут файла равен 0FH, то система считает, что здесь содержится длинное имя. Длинное имя кодируется в UNICODE и записывается в обратном порядке. За одной или несколькими записями с длинным именем должна следовать запись с обычным именем, содержащим знак "~" - тильда. Здесь содержится также остальная информация о файле. Как видите, алгоритм просмотра каталога с выявлением информации о файле весьма прост. Обратимся теперь к структуре записи каталога. В старой операционной системе MS DOS байты с 12 по 21 никак не использовались системой (см. [1]). Новой системе они пригодились. Ниже в таблице дана новая структура записи каталога.
СмещениеРазмерСодержимое
(+0) | 8 | Имя файла или каталога, выровненное на левую границу и дополненное пробелами. |
(+8) | 3 | Расширение имени файла, выровненное на левую границу и дополненное пробелами. |
(+11) | 1 | Атрибут файла. |
(+12) | 2 | Время доступа. |
(+14) | 2 | Время создания. |
(+16) | 2 | Дата создания. |
(+18) | 2 | Дата доступа. |
(+20) | 2 | Два старших байта номера первого кластера файла. |
(+22) | 2 | Время модификации файла. |
(+24) | 2 | Дата модификации файла. |
(+26) | 2 | Два младших байта номера первого кластера файла. |
(+28) | 4 | Размер файла в байтах. |
Как видите, все байты 32-байтной записи каталога теперь заняты. Лишний раз убеждаешься в первоначальной непродуманности файловой системы MS DOS, Это касается, в частности, длины файла. Как можно заметить, на длину файла отводится всего 4 байта. А как найти длину файла, если на нее требуется более 4 байт? Разумеется, в этом случае следует считать, что в каталоге хранятся младшие байты длины, а полную длину легко определить, обратившись к таблице размещения файлов. Но, согласитесь, что это уже явная недоработка. Странно также выглядит функция GetFileSize, которая возвращает четыре младших байта длины файла, старшие же байты возвращаются во втором параметре функции.
Иное дело в файловой системе NTFS, поддерживаемой Windows NT, изначально планируемой для работы с файлами больших размеров. Здесь для индексации кластеров используются 64-битные поля.
33 В начале Windows 95 работала с 16-битной FAT, но длинные имена уже поддерживала.
34
FAT (File Allocation Table) - один из элементов, на котором базируются файловые системы MS DOS и Windows 9х. По этой причине часто такие файловые системы называют FAT системами.
Макроопределения
Макроопределения
Общий вид макроопределения.
Имя MACRO параетры ... ENDM
Определив блок один раз, можно использовать его в программе многократно. Причем в зависимости от значений параметров заменяемый участок может иметь разные значения. Если заданный участок предполагается многократно использовать, например в цикле, макроопределение имеет несомненные преимущества перед процедурой, т.к. несколько убыстряет выполнение кода. Пример:
ЕХС MACRO par1,par2 PUSH par1 POP par2 ENDM
Данное макроопределение приводит к обмену содержимым между параметрами.
ЕХС EAX,EBX
эквивалентно PUSH EAX\POP EAX; ЕХС MEM1,ESI - PUSH MEM1\POP ESI
и т. д. Заметим, что если первый параметр будет непосредственно числом, то это приведет к загрузке данного числа во второй операнд.
Важным вопросом в связи с макроопределениями является проблема меток. Действительно, если мы будем применять в макроопределении обычные метки, то при использовании его более чем один раз возникнет коллизия. Коллизия эта разрешается при помощи объявления локальных меток. Для этого используется ключевое слово LOCAL. Например,
ЕХС MACRO par1,par2 LOCAL EXI CMP par1,par2 JE EXI PUSH par1 POP par2 EXI: ENDM
Данное макроопределение можно использовать сколь угодно много раз - при каждой подстановке ассемблер будет генерировать уникальную метку. Для выхода из макроопределения (т.е. для прекращения генерации макроопределения) применяется директива EXITM. Она может понадобиться, если в макроопределении Вы используете условные конструкции типа IF..ENDIF.
Макроповторения
Макроповторения
1. Повторение, заданное опеделенное число раз. Используется макродиректива REPT. Например:
A EQU 10 REPT 100 DB A ENDM
Будет сгенерировано 100 директив DB 10. С этой директивой удобно использовать оператор "=", который позволяет изменять значение переменной многократно, т.е. использовать выражение типа А = А + 5.
2. Директива IRP.
IRP параметр,<список> ... ENDM
Блок будет вызываться столько раз, сколько параметров в списке. Например:
IRP REG, <EAX,EBX,ECX,EDX,ESI,EDI> PUSH REG ENDM
Приведет к генерации следующих строк:
PUSH EAX PUSH EBX PUSH ECX PUSH EDX PUSH ESI PUSH EDI
3. Директива IRPC.
IRPC параметр, строка Операторы ENDM
Пример:
IRPC CHAR,azklg CMP AL,'&CHAR&' JZ EndC ENDM EndC:
Данный фрагмент эквивалентен следующей последовательности:
CMP AL,'a' JZ EndC CMP AL,'z' JZ EndC CMP AL,'k' JZ EndC CMP AL,'l' JZ EndC CMP AL,'g' JZ EndC EndC:
Амперсант (&) в последнем примере используется для того, чтобы задать вычисление параметра блока повторения даже внутри кавычек. Амперсант - это макрооперация, которая работает в блоке повторения, поскольку блоки повторения представляют собой один из типов макрокоманды.
Меню
II
6. Меню. Меню также может быть задано в файле ресурсов. Как и диалоговое окно, в программе оно определяется по имени (строке). Меню можно задать и в обычном окне, и в диалоговом окне. Для обычного окна при регистрации класса следует просто заменить строку
MOV DWORD PTR [WC.CLMENNAME],0
на
MOV DWORD PTR [WC.CLMENNAME], OFFSET MENS
Здесь MENS - имя, под которым меню располагается в файле ресурсов. Меню на диалоговое окно устанавливается другим способом, который, разумеется, подходит и для обычного окна. В начале меню загружается при помощи функции LoadMenu, а затем устанавливается функцией SetMenu.
А теперь обо всем подробнее. Рассмотрим структуру файла ресурсов, содержащего определение меню. Ниже представлен текст файла, содержащего определение меню.
Далее представлена программа, демонстрирующая меню на диалоговом окне.
MENUP MENU { POPUP "&Первый пункт" { MENUITEM "&Первый",1 MENUITEM "В&торой",2 POPUP "Подмен&ю" { MENUITEM "Десятый пунк&т",6 } } POPUP "&Второй пункт" { MENUITEM "Трети&й",3 MENUITEM "Четверт&ый",4 } MENUITEM "Вы&ход",5 }
Внимательно рассмотрите текст меню. Как видите, пункты меню имеют идентификаторы, по которым в программе можно определить, какой пункт меню выбран. Можно заметить, что выпадающее меню может содержать еще и подменю.
//файл menu.rc //определение констант #define WS_SYSMENU 0x00080000L #define WS_MINIMIZEBOX 0x00020000L #define WS_MAXIMIZEBOX 0x00010000L #define WS_POPUP 0x80000000L #define WS_CAPTION 0x00C00000L
MENUP MENU { POPUP "&Первый пункт" { MENUITEM "&Первый",1 MENUITEM "В&торой",2 } POPUP "&Второй пункт" { MENUITEM "Трети&й",3 MENUITEM "Четверт&ый",4
POPUP "Еще подмен&ю" {
MENUITEM "Десятый пунк&т", 6 } } MENUITEM "Вы&ход", 5 }
// идентификаторы #define IDI_ICON1 100
; определили иконку IDI_ICON1 ICON "ico1.ico"
// определение диалогового окна DIAL1 DIALOG 0, 0, 240, 120
STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX
CAPTION "Пример диалогового окна" FONT 8, "Arial" { }
; файл menu.inc ; константы ; сообщение приходит при закрытии окна
WM_CLOSE equ 10h WM_INITDIALOG equ 110h WM_SETICON equ 80h WM_COMMAND equ 111h
; прототипы внешних процедур EXTERN MessageBoxA@16:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetModuleHandleA@4:NEAR EXTERN DialogBoxParamA@20:NEAR EXTERN EndDialog@8:NEAR EXTERN LoadStringA@16:NEAR EXTERN LoadIconA@8:NEAR EXTERN LoadMenuA@8:NEAR EXTERN SendMessageA@16:NEAR EXTERN SetMenu@8:NEAR
; структуры ; структура сообщения MSGSTRUCT STRUC MSHWND DD ? MSMESSAGE DD ? MSWPARAM DD ? MSLPARAM DD ? MSTIME DD ? MSPT DD ? MSGSTRUCT ENDS
; файл menu.asm .386P
; плоская модель .MODEL FLAT, stdcall
include menu. inc
; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ;------------------------------------------------------------
; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' MSG MSGSTRUCT <?> HINST DD 0 ; дескриптор приложения PA DB "DIAL1",0 PMENU DB "MENUP",0 STR1 DB "Выход из программы",0 STR2 DB "Сообщение",0 _DATA ENDS
; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA@4 MOV [HINST], EAX ;--------------------------------- PUSH 0 PUSH OFFSET WNDPROC PUSH 0 PUSH OFFSET PA PUSH [HINST] CALL DialogBoxParamA@20 CMP EAX,-1 JNE KOL KOL: PUSH 0 CALL ExitProcess@4
; процедура окна ; расположение параметров в стеке
; [EBP+014Н] LPARAM ; [EBP+10H] WAPARAM ; [EBP+0CH] MES ; [EBP+8] HWND WNDPROC PROC PUSH EBP MOV EBP,ESP PUSH EBX PUSH ESI PUSH EDI ;----------------- CMP DWORD PTR [EBP+0CH],WM_CLOSE JNE L1 ; закрыть диалоговое окно PUSH 0 PUSH DWORD PTR [EBP+08H] CALL EndDialog@8 JMP FINISH L1: CMP DWORD PTR [EBP+0CH],WM_INITDIALOG JNE L2 ; загрузить иконку PUSH 100 ; идентификатор иконки PUSH [HINST] ; идентификатор процесса CALL LoadIconA@8 ; установить иконку PUSH EAX PUSH 0 ; тип иконки (маленькая) PUSH WM_SETICON PUSH DWORD PTR [EBP+08H] CALL SendMessageA@16 ; загрузить меню PUSH OFFSET PMENU PUSH [HINST] CALL LoadMenuA@8 ; установить меню PUSH EAX PUSH DWORD PTR [EBP+08H] CALL SetMenu@8 JMP FINISH L2: ; проверяем, не случилось ли чего с управляющими элементами ; на диалоговом окне, в нашем случае имеется единственный ; управляющий элемент - это меню CMP DWORD PTR [EBP+0CH], WM_COMMAND JNE FINISH ; здесь определяем идентификатор, в данном случае ; это идентификатор пункта меню CMP WORD PTR [EBP+10Н],5 JNE FINISH ; сообщение PUSH 0 ; МВ_ОК PUSH OFFSET STR2 PUSH OFFSET STR1 PUSH 0 CALL MessageBoxA@16 ; закрыть диалоговое окно PUSH 0 PUSH DWORD PTR [EBP+08H] CALL EndDialog@8 FINISH: MOV EAX, 0 POP EDI POP ESI POP EBX POP EBP RET 16 WNDPROC ENDP _TEXT ENDS END START
Рисунок 2.3.3. Пример программы с меню.
Дадим небольшой комментарий к программе на Рисунок 2.3.3. Прежде всего обращаю ваше внимание на довольно прозрачную аналогию между диалоговым окном и меню. Действительно, и в том и в другом случае ресурс определяется не по идентификатору, а по имени. Далее, и диалоговое окно, и меню содержат в себе элементы, определяющиеся по их идентификаторам, которые мы задали в файле ресурсов и которые помещаются в младшее слово параметра WPARAM. В программе на Рисунок 2.3.3 мы программно загружаем меню. Можно поступить и по другому: указать меню в опциях определения диалогового окна следующим образом.
//определение диалогового окна DIAL1 DIALOG 0, 0, 240, 120
STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX MENU MENUP
CAPTION "Пример диалогового окна" FONT 8, "Arial" { }
Этого достаточно, чтобы меню загрузилось и отобразилось автоматически.
Хочу напомнить читателю одну вещь. В главе 1.3 приводился пример кнопки, которая создавалась как дочернее окно. То, что нажата имение эта кнопка, мы определяли по содержимому LPARAM, который содержал дескриптор кнопки.
Как видите, идентифицировать элемент, расположенный на диалоговом окне, можно и по дескриптору, и по идентификатору ресурса.
Вернемся к меню. Пункты меню могут содержать дополнительные параметры, которые определяют дополнительные свойства этих пунктов.
Вот эти свойства, понимаемые компилятором ресурсов:
CHECKED - пункт отмечен "птичкой".
GRAYED - элемент недоступен (имеет серый цвет).
HELP - элемент может быть связан с помощью. Редакторы ресурсов дополнительно создают ресурс - строку. При этом идентификатор строки совпадает с идентификатором пункта меню.
MENUBARBREAK - для горизонтального пункта это означает, что начиная с него горизонтальные пункты располагаются в новой строке. Для вертикального пункта - то, что начиная с него пункты расположены в новом столбце. При этом проводится разделительная линия.
MENUBREAK - аналогично предыдущему, но разделительная линия не проводится.
INACTIVE - пункт не срабатывает.
SEPARATOR - создает в меню разделитель. При этом идентификатор не ставится.
Заканчивая рассуждение о меню, замечу, что у Windows есть обширнейший набор функций, с помощью которых можно менять свойства меню (удалять и добавлять пункты меню, менять их свойства). В следующей главе приводятся примеры некоторых из этой группы.
Метки
Метки
1. Метка с двоеточием после имени определяет адрес следующей за меткой команды.
2. Директива LABEL позволяет определить явно тип метки. Значение же определенной таким образом метки равно адресу команды или данных, стоящих далее. Например,
LABEL L1 DWORD.
3. Выражение ИМЯ PROC определяет метку, переход на которую обычно происходит по команде CALL. Блок кода, начинающийся с такой метки, называют процедурой. Впрочем, переход на такую метку можно осуществлять и с помощью JMP, как, впрочем, и команду CALL можно использовать для перехода на обычную метку. В этом, несомненно, состоит сила и гибкость ассемблера.
4. В строке за меткой может стоять директива резервирования данных, например: ERR DB 'Ошибка' или NUM DWORD 0. С точки зрения языка высокого уровня таким образом мы определяем глобальную переменную. С точки же зрения ассемблера нет никакой разницы между командой и данными, поэтому между меткой, определяющей команду, и меткой, определяющей данные, нет никакой разницы. Раз уж речь пошла о данных, перечислю их типы:
BYTE (DB) - байт,
WORD (DW) - 2 байта,
DWORD (DD) - 4 байта,
FWORD (DF) - 6 байт,
QWORD (DQ) - 8 байт,
TBYTE (DT) - 10 байт.
5. С помощью директивы EQU в терминах языков высокого уровня определяются константы. Например - MES EQU "ERROR!", LAB EQU 145Н. С помощью EQU значение данной метке может быть присвоено только один раз. С правой стороны от EQU может стоять выражение с использованием арифметических, логических и битовых операций. Вот эти операции: "+", "-", "*", "/", "MOD"-ocтaток от деления, "AND", "OR", "NOT", "XOR", "SHR", "SHL". Используются также операции сравнения: EQ, GE, GT, LE, LT, NE. Выражение с операцией сравнения считается логическим и принимает значение 0, если условие не выполняется, и 1 - если выполняется. С помощью директивы "=" можно присваивать только целые значения, но зато производить переприсваивание. Заметим, что выражение может являться операндом команды: MOV EAX,16*16-1.
6. Метка "$" всегда определяет текущий адрес.
7. В MASM метки, стоящие в процедуре, автоматически считаются локальными и, следовательно, имена меток в процедурах могут дублироваться. В TASM все метки по умолчанию считаются глобальными. Чтобы сделать метки, стоящие в процедуре локальными, они должны иметь префикс @@, а в начале программы следует указать директиву LOCALS (см. предыдущую главу).
Некоторые другие директивы транслятора ассемблера
Некоторые другие директивы транслятора ассемблера
1. Кроме объявлений с использованием директив PUBLIC и EXTERN, возможно объявление при помощи директивы GLOBAL, которая действует, как PUBLIC и EXTERN одновременно.
2. PURGE имя макроса. Отменяет загрузку макроса. Используется при работе с библиотекой макросов, чтобы не перегружать память38.
3. LENGTHOF - определяет число элементов данных. SIZEOF - определяет размер данных (отсутствуют в TASM).
4. Директивы задания набора команд.
.8086 - разрешены только команды микропроцессора 8086. Данная директива работает по умолчанию.
.186 - разрешены команды 186.
.286 и .286Р - разрешены команды 286-ого микропроцессора. Добавка "P" здесь и далее означает разрешение команд защищенного режима.
.386 и .386P - разрешение команд 386-ого микропроцессора.
.486 и .486Р - разрешение команд 486-ого процессора.
.586 и .586Р - разрешены команды Р5 (Pentium).
.686 и .686Р - разрешены команды Pб (Pentium Pro, Pentium II).
.8087 - разрешены команды арифметического сопроцессора 8087.
.287 - разрешены команды арифметического сопроцессора 287.
.387 - разрешены команды арифметического сопроцессора 387.
.MMX - разрешены команды расширения ММХ.
5. Директивы управления листингом.
NAME - задать имя модуля.
TITLE - определяет заголовок листинга.
По умолчанию и имя модуля, и заголовок листинга совпадают с именем файла.
SUBTTL - определяет подзаголовок листинга.
PAGE - определяет размеры страницы листинга: длина, ширина. Директива PAGE без аргументов начинает новую страницу листинга.
.LIST - выдавать листинг.
.XLIST - запретить выдачу листинга.
.SALL - подавить печать макроопределений.
.SFCOND - подвить печать условных блоков с ложными условиями.
.LFCOND - печатать условные блоки с ложными условиями.
.CREF - разрешить листинг перекрестных ссылок.
.XCREF - запретить листинг перекрестных ссылок.
38 В операционной системе MS DOS это было существенно.
Поиск файлов
Поиск файлов.
Для поиска файлов в Windows существуют две функции FindFirstFile и FindNextFile, очень похожие на аналогичные функции MS DOS и, как и там, работающие в паре. При успешном поиске первая функция возвращает некое число или идентификатор, который затем используется второй функцией для продолжения поиска.
Первым параметром функции FindFirstFile является указатель на строку для поиска файлов, второй параметр - указатель на структуру, которая получает информацию о найденных файлах. Функция FindNextFile первым своим параметром имеет идентификатор, полученный первой функцией, а вторым параметром - указатель на структуру. Эту структуру можно изучить по программе на Рисунок 2.5.1.
Основным отличием этих функций от соответствующих функций MS DOS является то, что поиск ограничивается только маской поиска (*.*, *.ЕХЕ и т.п.). Если файл найден, то тогда по возвращаемой структуре, где содержится вся информация о нем, Вы уже можете решать, подходит файл или нет.
На Рисунок 2.5.1 представлена программа, осуществляющая поиск файлов в указанном каталоге. Программа может иметь один или два параметра, или не иметь их вовсе. Если имеются два параметра, то первый параметр трактуется как каталог для поиска, причем программа учитывает, есть ли на конце косая черта или нет (допустимо c:, c:\, c:\windows\, c:\windows\system и т.п.). Второй параметр (в программе он третий, так как первым считается командная строка), если он есть, представляет собой маску поиска. Если его нет, то маска поиска берется в виде "*.*". Наконец, если параметров нет вообще, то поиск осуществляется в текущем каталоге по маске "*.*". Эту программу легко развить и сделать из нее полезную утилиту. Предоставляю это Вам, дорогой читатель. Ниже будет дан комментарий к означенной программе.
; файл FILES.ASM .386P ; плоская модель .MODEL FLAT, stdcall ; константы STD_OUTPUT_HANDLE equ -11 STD_INPUT_HANDLE equ -10 ; прототипы внешних процедур EXTERN wsprintfA:NEAR EXTERN CharToOemA@8:NEAR EXTERN GetStdHandle@4:NEAR EXTERN WriteConsoleA@20:NEAR EXTERN ReadConsoleA@20:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetCommandLineA@0:NEAR EXTERN lstrcatA@8:NEAR EXTERN FindFirstFileA@8:NEAR EXTERN FindNextFileA@8:NEAR EXTERN FindClose@4:NEAR ;----------------------------- ; структура, используемая для поиска файла ; при помощи функций FindFirstFile и FindNextFile _FIND STRUC ; атрибут файла ATR DWORD ? ; время создания файла CRTIME DWORD ? DWORD ? ; время доступа к файлу ACTIME DWORD ? DWORD ? ; время модификации файла WRTIME DWORD ? DWORD ? ; размер файла SIZEH DWORD ? SIZEL DWORD ? ; резерв DWORD ? DWORD ? ; длинное имя файла NAM DB 260 DUP (0) ; короткое имя файла ANAM DB 14 DUP(0) _FIND ENDS ;------------------------------------------------- ; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ;-------------------------------------------------
; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' BUF DB 0 DB 100 dup(0) LENS DWORD ? ; количество выведенных символов HANDL DWORD ? HANDL1 DWORD ? MASKA DB "*.*",0 АР DB "\",0 FIN _FIND <0> TEXT DB "Для продолжения нажмите клавишу ENTER",13,10,0 BUFIN DB 10 DUP(0) FINDH DWORD ? NUM DB 0 NUMF DWORD 0 ; счетчик файлов NUMD DWORD 0 ; счетчик каталогов FORM DB "Число найденных файлов: %lu",0 FORM1 DB "Число найденных каталогов: %lu",0 BUFER DB 100 DUP (?) DIR DB "<DIR>",0 PAR DB 0 ; количество параметров _DATA ENDS
; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить HANDLE вывода PUSH STD_OUTPUT_HANDLE CALL GetStdHandle@4 MOV HANDL,EAX ; получить HANDL1 ввода PUSH STD_INPUT_HANDLE CALL GetStdHandle@4 MOV HANDL1,EAX ; преобразовать строки для вывода PUSH OFFSET TEXT PUSH OFFSET TEXT CALL CharToOemA@8 PUSH OFFSET FORM PUSH OFFSET FORM CALL CharToOemA@8 PUSH OFFSET FORM1 PUSH OFFSET FORM1 CALL CharToOemA@8 ; получить количество параметров CALL NUMPAR MOV PAR,EAX ; если параметр один, то искать в текущем каталоге CMP EAX, 1 JE NO_PAR ;------------------------------------------------- ; получить параметр номером EDI MOV EDI, 2 LEA EBX,BUF CALL GETPAR PUSH OFFSET BUF CALL LENSTR ; если в конце нет "\" - добавим CMP BYTE PTR [BUF+EBX-1],"\" JE NO_PAR PUSH OFFSET AP PUSH OFFSET BUF CALL lstrcatA@8 ; нет ли еще параметра, где задана маска поиска CMP PAR,3 JB NO_PAR ; получить параметр - маску поиска MOV EDI,3 LEA EBX,MASKA CALL GETPAR NO_PAR: ;------------------------------------------------- CALL FIND ; вывести количество файлов PUSH NUMF PUSH OFFSET FORM PUSH OFFSET BUFER CALL wsprintfA LEA EAX, BUFER MOV EDX,1 CALL WRITE ; вывести количество каталогов PUSH NUMD PUSH OFFSET FORM1 PUSH OFFSET BUFER CALL wsprintfA LEA EAX, BUFER MOV EDX, 1 CALL WRITE _END: PUSH 0 CALL ExitProcess@4 ;************************ ; область процедур ;************************ ; вывести строку (в конце перевод строки) ; EAX - на начало строки ; EDX - с переводом строки или без WRITE PROC ; получить длину параметра PUSH EAX CALL LENSTR MOV ESI,EAX CMP EDX,1 JNE NO_ENT ; в конце - перевод строки MOV BYTE PTR [EBX+ESI],13 MOV BYTE PTR [EBX+ESI+1],10 MOV BYTE PTR [EBX+ESI+2],0 ADD EBX,2 NO_ENT: ; вывод строки PUSH 0 PUSH OFFSET LENS PUSH EBX PUSH EAX PUSH HANDL CALL WriteConsoleA@20 RET WRITE ENDP
; процедура определения длины строки ; строка - [EBP+08Н] ; длина в EBX LENSTR PROC PUSH EBP MOV EBP,ESP PUSH EAX ;---------------------- CLD MOV EDI, DWORD PTR [EBP+08Н] MOV EBX, EDI MOV ECX,100 ; ограничить длину строки XOR AL,AL REPNE SCASB ; найти символ 0 SUB EDI, EBX ;-длина строки, включая 0 MOV EBX, EDI DEC EBX ;---------------------- POP EAX POP EBP RET 4 LENSTR ENDP
; процедура определения количества параметров в строке ; определить количество параметров (->EAX) NUMPAR PROC CALL GetCommandLineA@0 MOV ESI,EAX ; указатель на строку XOR ECX,ECX ; счетчик MOV EDX,1 ; признак L1: CMP BYTE PTR [ESI],0 JE L4 CMP BYTE PTR [ESI],32 JE L3 ADD ECX,EDX ; номер параметра MOV EDX, 0 JMP L2 L3: OR EDX,1 L2: INC ESI JMP L1 L4: MOV EAX,ECX RET NUMPAR ENDP
; получить параметр из командной строки ; EBX - указывает на буфер, куда будет помещен параметр ; в буфер помещается строка с нулем на конце ; EDI - номер параметра GETPAR PROC CALL GetCommandLineA@0 MOV ESI, EAX ; указатель на строку XOR ECX, ECX ; счетчик MOV EDX, 1 ; признак L1: CMP BYTE PTR [ESI], 0 JE L4 CMP BYTE PTR [ESI], 32 JE L3 ADD ECX,EDX ; номер параметра MOV EDX,0 JMP L2 L3: OR EDX,1 L2: CMP ECX,EDI JNE L5 MOV AL,BYTE PTR [ESI] MOV BYTE PTR [EBX],AL INC EBX L5: INC ESI JMP L1 L4: MOV BYTE PTR [EBX], 0 RET GETPAR ENDP
; поиск в каталоге файлов и их вывод ; имя каталога в BUF FIND PROC ; путь с маской PUSH OFFSET MASKA PUSH OFFSET BUF CALL lstrcatA@8 ; здесь начало поиска PUSH OFFSET FIN PUSH OFFSET BUF CALL FindFirstFileA@8 CMP EAX,-1 JE _ERR ; сохранить дескриптор поиска MOV FINDH,EAX LF: ; исключить "файлы" "." и ".." CMP BYTE PTR FIN.NAM,"." JE _NO ; не каталог ли? TEST BYTE PTR FIN.ATR,10H JE NO_DIR PUSH OFFSET DIR PUSH OFFSET FIN.NAM CALL lstrcatA@8 INC NUMD DEC NUMF NO_DIR: ; преобразовать строку PUSH OFFSET FIN.NAM PUSH OFFSET FIN.NAM CALL CharToOemA@8 ; здесь вывод результата LEA EAX, FIN.NAM MOV EDX,1 CALL WRITE ; увеличить счетчики INC NUMF INC NUM ; конец страницы? CMP NUM, 22 JNE _NO MOV NUM, 0 ; ждать ввод строки MOV EDX,0 LEA EAX, TEXT CALL WRITE PUSH 0 PUSH OFFSET LENS PUSH 10 PUSH OFFSET BUFIN PUSH HANDL1 CALL ReadConsoleA@20 _NO: ; продолжение поиска PUSH OFFSET FIN PUSH FINDH CALL FindNextFileA@8 CMP EAX,0 JNE LF ; закрыть поиск PUSH FINDH CALL FindClose@4 _ERR: RET FIND ENDP _TEXT ENDS END START
Приемы работы с двоичными файлами*
Приемы работы с двоичными файлами*.
Манипуляция внешними файлами36 основывается на нескольких функциях API, главной и наиболее сложной из которых является функция CreateFile.
В связи с ограниченностью объема книги, мы не можем подробно остановиться на свойствах функции CreateFile. Однако замечу, что с помощью этой функции можно не только создавать или открывать файл, но и такие объекты как каналы (PIPE), консоли, устройства жесткого диска (disk device), коммуникационный ресурс и др. Функция различает устройство по структуре имени. К примеру, "C:\config.sys" определяет файл, a "CONOUT$" - буфер вывода текущей консоли.
Сейчас я представлю две простые, но весьма важные программы (
Пример использования горячих клавиш с диалоговым окном
Рисунок 2.4.2. Пример использования горячих клавиш с диалоговым окном.
Комментарий к программе на Рисунок 2.4.2.
1. Самое главное: разберитесь с тем, как мы определяем, когда первое окно редактирования теряет, когда приобретает фокус. В начале определяется, что сообщение пришло от окна редактирования с идентификатором 1, а затем - какое сообщение пришло: EN_SETFOCUS или EN_KILLFOCUS. В первом случае мы устанавливаем горячие клавиши, а во втором снимаем горячие клавиши.
2. В области данных задаем таблицу горячих клавиш. Функция RegisterHotKey имеет следующие параметры:
1-й параметр - идентификатор окна. 2-й параметр - идентификатор горячей клавиши. 3-й параметр - флаг нажатия управляющих клавиш. 4-й параметр - виртуальный код клавиши.
В нашем случае виртуальный код клавиши и идентификатор горячей клавиши совпадают. Конечно, здесь есть поле для дальнейшего усовершенствования. Скажем, исключить из обработки клавиши управления курсором. Я думаю, читатель справится с этим самостоятельно.
Пример использования условного ассемблирования для написания совместимой программы
Рисунок 2.6.1. Пример использования условного ассемблирования для написания совместимой программы.
Трансляция в MASM:
ML /с /coff /DMASM PROG.ASM LINK /SUBSYSTEM:WINDOWS PROG.OBJ
Трансляция в TASM:
TASM32 /ml PROG.ASM TLINK32 -aa PROG.OBJ
Как видите, все сводится к проверке, определена символьная константа MASM или нет (ключ /DMASM). Еще одна сложность - добавка в конце имени @N. Эту проблему мы обходим, используя оператор "=", с помощью которого переопределяем имена (см. секцию "работаем в TASM").
Пример манипуляции с меню
Рисунок 2.4.1. Пример манипуляции с меню.
Программа на Рисунок 2.4.1 имеет ряд механизмов, к обсуждению которых я намерен сейчас приступить. Для начала замечу, что в программе используются три ресурса: два меню и таблица акселераторов (см. файл ресурсов).
1. Первое, на что хочу обратить Ваше внимание, - это переменная PRIZN. В ней хранится состояние меню: 2 - загружено меню MENUP, 1 - меню отсутствует, 0 - загружено меню MENUC. Начальное состояние обеспечивается заданием меню при регистрации класса окна:
MOV DWORD PTR [WC.CLMENNAME], OFFSET MEN
2. Второе - это кнопка. Механизм распознавания нажатия кнопки мы уже разбирали, так что больше на этом останавливаться не будем. Одно из событий, которое может произойти при нажатии кнопки - это удаление меню. Удаляется меню при помощи функции DestroyMenu. После удаления необходимо обновить содержимое окна. Это достигается последовательностью двух команд ShowWindow.
3. Еще одно событие, которое происходит при нажатии кнопки, это смена меню. Интересно, что смена меню происходит автоматически, если мы загрузим и установим новое меню.
4. Выбор одного из пунктов меню MENUP также приводит к смене меню. Здесь должно быть все понятно, поскольку обращение идет к тому же участку программы, что и в случае нажатия кнопки.
5. Интересная ситуация возникает с акселератором. Акселераторная клавиша у нас F5. При ее нажатии генерируется такое же сообщение, как при выборе пункта "Четвертый" меню MENUP. Важно то, что такое же сообщение будет генерироваться и тогда, когда загружается меню MENUC и когда меню не будет. А поскольку наша процедура обрабатывает сообщение в любом случае, клавиша F5 будет срабатывать всегда.
6. Рассмотрим теперь то, как производится определение названия выбранного пункта меню. Центральную роль в этом механизме играет сообщение WM_MENUSELECT. Это сообщение приходит всегда, когда выбирается пункт меню. Тут важно отметить, что когда мы активизируем меню, то в начале приходит сообщение WM_MENUSELECT со значением LPARAM, которое определяет идентификатор меню равным нулю. Этим целям служат строки:
CMP WORD PTR [EBP+14H], 0 JE FINISH
7. По получении сообщения WM_MENUSELECT в младшем слове параметра WPARAM может содержаться либо идентификатор пункта меню, либо номер заголовка выпадающего меню. Это ключевой момент. Нам важно это знать, так как строка заголовка выпадающего меню и строка пункта меню получаются по-разному. Определить, что выбрано, можно по старшему слову WPARAM. Мы используем для этого константу MF_POPUP:
TEST WORD PTR [EBP+12H], MF_POPUP.
Обратите внимание, как удобна и как кстати здесь команда SETNE.
8. Далее, для получения строки-названия используется функция GetMenuItemInfo. Третьим параметром этой функции как раз и может быть либо ноль, либо единица. Если ноль, то второй параметр - это идентификатор пункта меню, если единица, то второй параметр - номер заголовка выпадающего меню. Четвертым параметром является указатель на структуру, которая и будет заполняться в результате выполнения функции. Некоторые поля этой структуры должны быть, однако, заполнены заранее. Обращаю внимание на поле dwTypeData, которое должно содержать указатель на буфер, получающий необходимую нам строку. При этом поле cch должно содержать длину этого буфера. Но для того чтобы поля dwTypeData и cch трактовались функцией именно как указатель на буфер и его длину, поля fMask и fType должны быть правильно заполнены (см. программу). Наконец, поле cbSize должно содержать длину всей структуры.
9. После получения нужной информации, т.е. строки-названия пункта меню, при помощи функции SendMessage мы посылаем сообщение WM_SETTEXT, которое дает команду установить заголовок окна.
Пример обработки текстового файла
Рисунок 2.5.4. Пример обработки текстового файла.
Программа на Рисунок 2.5.4 демонстрирует один из возможных алгоритмов обработки текстового файла - построчное чтение текстового файла. Часть программы, занимающаяся чтением и анализом текстового файла, сосредоточена между метками L00 и CONT. Детально разберитесь в алгоритме и проникнитесь тем, что язык высокого уровня никогда не будет стимулировать написание таких алгоритмов, а значит, язык ассемблера делает нас интеллектуально богаче.
Пример программы которая осуществляет рекурсивный поиск по дереву каталогов
Рисунок 2.5.2. Пример программы, которая осуществляет рекурсивный поиск по дереву каталогов.
Разберем ту роль, которую играют локальные переменные в процедуре FIND. Переменная FINDH - здесь хранится дескриптор поиска в данном каталоге. Рекурсивный вызов процедуры FIND может происходить и тогда, когда поиск в текущем каталоге еще не закончился. Следовательно, после возврата из рекурсии поиск должен быть продолжен. Это можно обеспечить только старым значением дескриптора. Локальная переменная обеспечивает такую возможность, поскольку она разрушается только при переходе на более низкий уровень (к родительскому каталогу).
Аналогичную роль играет переменная DIRSS. В ней хранится текущий каталог. Это важно, т.к. с помощью этой переменной формируется полное имя файла.
Переменные DIRS и DIRV играют вспомогательную роль. В принципе, вместо них можно было бы использовать и глобальные переменные. Хотя, с точки зрения эффективности рекурсивных алгоритмов, чем меньше объем локальных переменных - тем лучше.
Еще один вопрос я хочу здесь обсудить. Для передачи имени каталога при вызове процедуры используется переменная DIRV. Почему же для этой цели нельзя использовать переменную DIRSS? Причина вот в чем. В процедуру передается не само значение, а указатель (адрес). Следовательно, любые изменения с параметром DIR приведет к аналогичным изменениям с переменной DIRSS на нижнем уровне рекурсии, В чем мы, разумеется, не заинтересованы.
Пример программы с двумя списками
Рисунок 2.4.3. Пример программы с двумя списками. Перебросить запись из левого списка в правый можно двойным щелчком мыши или клавишей INSERT.
Комментарий к программе на Рисунок 2.4.3.
В первую очередь обратите внимание на функцию SendDIgItemMessage. Для посылки сообщения элементам диалогового окна эта функция более удобна, чем SendMessage, так как элемент в ней идентифицируется не дескриптором (который еще надо узнать), а номером, определенным в файле ресурсов. Взглянув на файл ресурсов. Вы увидите, что второму (правому) списку присвоено свойство LBS_SORT. Если такое свойство присвоено списку, то при добавлении в него элемента (сообщение LB_ADDSTRING) этот элемент помещается в список так, что список остается упорядоченным. Свойство LBS_SORT стоит системе Windows довольно большой работы. Посредством сообщения WM_COMPAREITEM она определяет нужное положение нового элемента в списке, а затем вставляет его при помощи сообщения LB_INSERTSTRING. Хотелось бы также обратить внимание на цикл заполнения левого списка. Нам приходится хранить регистр ECX в стеке. Вы скажете - дело обыкновенное при организации цикла при помощи команды LOOP. А я Вам скажу, что это совсем не очевидно. К сожалению, в документации по функциям API и сообщениям не указывается, какие регистры микропроцессора сохраняются, а какие нет. Все это придется устанавливать экспериментально. Что и было сделано мною в данном примере. Сообщение WM_VKEYTOITEM приходит при нажатии какой-либо клавиши, при наличии фокуса на списке. При этом список должен иметь свойство LBS_WANTKEYBOARDINPUT. Именно потому, что данное свойство установлено только у левого списка, у нас нет необходимости проверять, от какого списка пришло сообщение.
Пример простейшей программы с текстом
Рисунок 2.1.1. Пример простейшей программы с текстом.
Еще один пример с выводом текста в окно. Теперь мы усложняем свою задачу. Зададимся целью, чтобы текстовая строка все время, чтобы ни случилось с окном, была бы в его середине. Для этого необходимо знать длину строки в пикселях и размеры окна. Длина строки в пикселях определяется с помощью функции GetTextExtentPoint32, а размеры окна - с помощью функции GetWindowRect. При этом нам понадобятся структуры типа SIZET и RECT. Надеюсь, читатель понимает, как определить положение строки, если известна ее длина и размеры окна, добавлю только, что необходимо учесть высоту заголовка окна.
; файл text2.inc ; константы ; сообщение приходит при закрытии окна WM_DESTROY equ 2 ; сообщение приходит при создании окна WM_CREATE equ 1 ; сообщение приходит при перерисовке окна WM_PAINT equ 0FH ; свойства окна CS_VREDRAW equ 1h CS_HREDRAW equ 2h CS_GLOBALCLASS equ 4000h WS_OVERLAPPEDWINDOW equ 000CF0000H stylcl equ CS_HREDRAW + CS_VREDRAW + CS_GLOBALCLASS DX0 equ 300 DY0 equ 200 ; компоненты цветов RED equ 80 GREEN equ 80 BLUE equ 255 RGBW equ (RED or (GREEN shl 8)) or (BLUE shl 16) RGBT equ 00FF00H ; зеленый ; идентификатор стандартной иконки IDI_APPLICATION equ 32512 ; идентификатор курсора IDC_CROSS equ 32515 ; режим показа окна - нормальный SW_SHOWNORMAL equ 1 ; прототипы внешних процедур EXTERN CreateWindowExA@48:NEAR EXTERN DefWindowProcA@16:NEAR EXTERN DispatchMessageA@4:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetMessageA@16:NEAR EXTERN GetModuleHandleA@4:NEAR EXTERN LoadCursorA@8:NEAR EXTERN LoadIconA@8:NEAR EXTERN PostQuitMessage@4:NEAR EXTERN RegisterClassA@4:NEAR EXTERN ShowWindow@8:NEAR EXTERN TranslateMessage@4:NEAR EXTERN UpdateWindow@4:NEAR EXTERN BeginPaint@8:NEAR EXTERN EndPaint@8:NEAR EXTERN TextOutA@20:NEAR EXTERN GetStockObject@4:NEAR EXTERN CreateSolidBrush@4:NEAR EXTERN SetBkColor@8:NEAR EXTERN SetTextColor@8:NEAR EXTERN GetTextExtentPoint32A@16:NEAR EXTERN GetWindowRect@8:NEAR
; структуры ; структура сообщения MSGSTRUCT STRUC MSHWND DD ? ; идентификатор окна, получающего сообщение MSMESSAGE DD ? ; идентификатор сообщения MSWPARAM DD ? ; доп. информация о сообщении MSLPARAM DD ? ; доп. информация о сообщении MSTIME DD ? ; время посылки сообщения MSPT DD ? ; положение курсора, во время ; посылки сообщения MSGSTRUCT ENDS
WNDCLASS STRUC CLSSTYLE DD ? ; стиль окна CLSLPFNWNDPROC DD ? ; указатель на процедуру окна CLSCBCLSEXTRA DD ? ; информация о доп. байтах для данной структуры CLSCBWNDEXTRA DD ? ; информация о доп. байтах для окна CLSHINSTANCE DD ? ; дескриптор приложения CLSHICON DD ? ; идентификатор иконы окна CLSHCURSOR DD ? ; идентификатор курсора окна CLSHBRBACKGROUND DD ? ; идентификатор кисти окна MENNAME DD ? ; имя-идентификатор меню CLSNAME DD ? ; специфицирует имя класса окон WNDCLASS ENDS
PAINTSTR STRUC hdc DWORD 0 fErase DWORD 0 left DWORD 0 top DWORD 0 right DWORD 0 bottom DWORD 0 fRes DWORD 0 fIncUp DWORD 0 Reserv DB 32 dup (0) PAINTSTR ENDS
SIZET STRUC X1 DWORD ? Y1 DWORD ? SIZET ENDS
RECT STRUC L DWORD ? ; X - левого верхнего угла T DWORD ? ; Y - левого верхнего угла R DWORD ? ; X - правого нижнего угла B DWORD ? ; Y - правого нижнего угла RECT ENDS
; файл text2.asm .386P ; плоская модель .MODEL FLAT, stdcall ;------------------------------------------------------------ include text2.inc ; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib includelib c:\masm32\lib\gdi32.lib
; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' NEWHWND DD 0 MSG MSGSTRUCT <?> WC WNDCLASS <?> PNT PAINTSTR <?> SZT SIZET <?> RCT RECT <?> HINST DD 0 TITLENAME DB 'Текст в окне',0 NAM DB 'CLASS32',0 XT DWORD ? YT DWORD ? TEXT DB 'Текст в окне зеленый',0 CONT DWORD ? _DATA ENDS
; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA@4 MOV [HINST], EAX REG_CLASS: ; заполнить структуру окна стиль MOV [WC.CLSSTYLE],stylcl ; процедура обработки сообщений MOV [WC.CLSLPFNWNDPROC], OFFSET WNDPROC MOV [WC.CLSCBCLSEXTRA], 0 MOV [WC.CLSCBWNDEXTRA], 0 MOV EAX, [HINST] MOV [WC.CLSHINSTANCE], EAX ; ---------- иконка окна PUSH IDI_APPLICATION PUSH 0 CALL LoadIconA@8 MOV [WC.CLSHICON], EAX ;---------- курсор окна PUSH IDC_CROSS PUSH 0 CALL LoadCursorA@8 MOV [WC.CLSHCURSOR], EAX PUSH RGBW ; цвет кисти CALL CreateSolidBrush@4 ; создать кисть MOV [WC.CLSHBRBACKGROUND], EAX MOV DWORD PTR [WC.MENNAME],0 MOV DWORD PTR [WC.CLSNAME], OFFSET NAM PUSH OFFSET WC CALL RegisterClassA@4 ; создать окно зарегистрированного класса PUSH 0 PUSH [HINST] PUSH 0 PUSH 0 PUSH DY0 ; DY0 - высота окна PUSH DX0 ; DX0 - ширина окна PUSH 100 ; координата Y PUSH 100 ; координата X PUSH WS_OVERLAPPEDWINDOW PUSH OFFSET TITLENAME ; имя окна PUSH OFFSET NAM ; имя класса PUSH 0 CALL CreateWindowExA@48 ; проверка на ошибку CMP EAX, 0 JZ _ERR MOV [NEWHWND], EAX ; дескриптор окна PUSH SW_SHOWNORMAL PUSH [NEWHWND] CALL ShowWindow@8 ; показать созданное окно PUSH [NEWHWND] CALL UpdateWindow@4 ; перерисовать видимую часть окна ; петля обработки сообщений MSG_LOOP: PUSH 0 PUSH 0 PUSH 0 PUSH OFFSET MSG CALL GetMessageA@16 CMP AX, 0 JE END_LOOP PUSH OFFSET MSG CALL TranslateMessage@4 PUSH OFFSET MSG CALL DispatchMessageA@4 JMP MSG_LOOP END_LOOP: ; выход из программы (закрыть процесс) PUSH [MSG.MSWPARAM] CALL ExitProcess@4 _ERR: JMP END_LOOP ; процедура окна ; расположение параметров в стеке ; [EBP+014Н] LPARAM ; [EBP+10H] WAPARAM ; [EBP+0CH] MES ; [EBP+8] HWND WNDPROC PROC PUSH EBP MOV EBP,ESP PUSH EBX PUSH ESI PUSH EDI CMP DWORD PTR [EBP+0CH],WM_DESTROY JE WMDESTROY CMP DWORD PTR [EBP+0CH],WM_CREATE JE WMCREATE CMP DWORD PTR [EBP+0CH],WM_PAINT JE WMPAINT JMP DEFWNDPROC WMPAINT: PUSH OFFSET PNT PUSH DWORD PTR [EBP+08H] CALL BeginPaint@8 MOV CONT,EAX ; сохранить контекст (дескриптор) ;---------------- цвет фона = цвет окна PUSH RGBW PUSH EAX CALL SetBkColor@8 ;---------------- цвет текста (красный) PUSH RGBT PUSH CONT CALL SetTextColor@8 ;- вычислить длину текста в пикселях текста PUSH OFFSET TEXT CALL LENSTR PUSH EBX ; сохраним длину строки PUSH OFFSET SZT PUSH EBX PUSH OFFSET TEXT PUSH CONT CALL GetTextExtentPoint32A@16 ;---------------- размер окна PUSH OFFSET RCT PUSH DWORD PTR [EBP+8] CALL GetWindowRect@8 ;---------------- здесь вычисления координат MOV EAX, RCT.R SUB EAX, RCT.L SUB EAX, SZT.X1 SHR EAX, 1 ; текст посередине MOV XT, EAX MOV EAX, RCT.B SUB EAX, RCT.T SHR EAX, 1 SUB EAX, 25 ; учтем заголовочную часть окна MOV YT,EAX ; ---------------- вывести текст ; длина строки уже в стеке PUSH OFFSET TEXT PUSH YT PUSH XT PUSH CONT CALL TextOutA@20 ;---------------- закрыть контекст PUSH OFFSET PNT PUSH DWORD PTR [EBP+08H] CALL EndPaint@8 MOV EAX, 0 JMP FINISH WMCREATE: MOV EAX, 0 JMP FINISH DEFWNDPROC: PUSH DWORD PTR [EBP+14H] PUSH DWORD PTR [EBP+10H] PUSH DWORD PTR [EBP+0CH] PUSH DWORD PTR [EBP+08H] CALL DefWindowProcA@16 JMP FINISH WMDESTROY: PUSH 0 CALL PostQuitMessage@4 ; WM_QUIT MOV EAX, 0 FINISH: POP EDI POP ESI POP EBX POP EBP RET 16 WNDPROC ENDP
; длина строки ; указатель на строку в - [EBP+08H] LENSTR PROC PUSH EBP MOV EBP,ESP PUSH ESI MOV ESI, DWORD PTR [EBP+8] XOR EBX, EBX LBL1: CMP BYTE PTR [ESI],0 JZ LBL2 INC EBX INC ESI JMP LBL1 LBL2: POP ESI POP EBP RET 4 LENSTR ENDP _TEXT ENDS END START
Пример простейших манипуляций с меню
I
Читатель, наверное, обращал внимание, что во многих программах меню может динамически меняться во время работы: исчезают и добавляются некоторые пункты, одно меню встраивается в другое. Пример простейших манипуляций с меню приведен на Рисунок 2.4.1.
Программа открывает окно с кнопкой и меню. При нажатии кнопки текущее меню заменяется другим. Если нажать еще раз, то меню исчезает. Следующее нажатие приводит к появлению первого меню и так далее, по кругу. Кроме того, в первом меню имеется пункт, который приводит к такому же результату, что и нажатие кнопки. Наконец, для этого пункта установлена акселераторная клавиша - F5. При передвижении по меню название пунктов меню и заголовков выпадающих (POPUP) подменю отображается в заголовке окна. Вот, вкратце, как работает программа. Механизмы работы программы будут подробно разобраны ниже.
// файл menu2.rc
// виртуальная клавиша F5 #define VK_F5 0х74
// ************** MENUP **************
MENUP MENU { POPUP "&Первый пункт" { MENUITEM "&Первый",1 MENUITEM "В&торой",2 }
POPUP "&Второй пункт" { MENUITEM "Трети&й",3 MENUITEM "Четверт&ый\tF5",4 MENUITEM SEPARATOR POPUP "Еще подмен&ю" { MENUITEM "Дополнительный пу&нкт",6 }
}
MENUITEM "Вы&ход",5 }
//**************** MENUC ******************** MENUC MENU { POPUP "Набор первый" { MENUITEM "Белый",101 MENUITEM "Серый",102 MENUITEM "Черный",103 } POPUP "Набор второй" { MENUITEM "Красный",104 MENUITEM "Синий",105 MENUITEM "Зеленый",106 } }
// таблица акселераторов // определен один акселератор для вызова // пункта из меню MENUP MENUP ACCELERATORS { VK_F5, 4, VIRTKEY, NOINVERT }
; файл menu2.inc ; константы ; сообщение приходит при закрытии окна WM_DESTROY equ 2 ; сообщение приходит при создании окна WM_CREATE equ 1 ; сообщение при щелчке левой кнопкой мыши в области окна WM_COMMAND equ 111h WM_MENUSELECT equ 11Fh WM_SETTEXT equ 0Ch MIIM_TYPE equ 10h MF_STRING equ 0h MF_POPUP equ 10h
; свойства окна CS_VREDRAW equ 1h CS_HREDRAW equ 2h CS_GLOBALCLASS equ 4000h WS_OVERLAPPEDWINDOW equ 000CF0000H STYLE equ CS_HREDRAW+CS_VREDRAW+CS_GLOBALCLASS BS_DEFPUSHBUTTON equ 1h WS_VISIBLE equ 10000000h WS_CHILD equ 40000000h STYLBTN equ WS_CHILD+BS_DEFPUSHBUTTON+WS_VISIBLE ; идентификатор стандартной иконки IDI_APPLICATION equ 32512 ; идентификатор курсора IDC_ARROW equ 32512 ; режим показа окна - нормальный SW_SHOWNORMAL equ 1 SW_HIDE equ 0 SW_SHOWMINIMIZED equ 2
; прототипы внешних процедур EXTERN wsprintfA:NEAR EXTERN GetMenuItemInfoA@16:NEAR EXTERN LoadMenuA@8:NEAR EXTERN SendMessageA@16:NEAR EXTERN MessageBoxA@16:NEAR EXTERN CreateWindowExA@48:NEAR EXTERN DefWindowProcA@16:NEAR EXTERN DispatchMessageA@4:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetMessageA@16:NEAR EXTERN GetModuleHandleA@4:NEAR EXTERN LoadCursorA@8:NEAR EXTERN LoadIconA@8:NEAR EXTERN PostQuitMessage@4:NEAR EXTERN RegisterClassA@4:NEAR EXTERN ShowWindow@8:NEAR EXTERN TranslateMessage@4:NEAR EXTERN UpdateWindow@4:NEAR EXTERN TranslateAcceleratorA@12:NEAR EXTERN LoadAcceleratorsA@8:NEAR EXTERN GetMenu@4:NEAR EXTERN DestroyMenu@4:NEAR EXTERN SetMenu@8:NEAR
; структуры ; структура сообщения MSGSTRUCT STRUC MSHWND DD ? MSMESSAGE DD ? MSWPARAM DD ? MSLPARAM DD ? MSTIME DD ? MSPT DD ? MSGSTRUCT ENDS
;----структура класса окон WNDCLASS STRUC CLSSTYLE DD ? CLWNDPROC DD ? CLSCBCLSEX DD ? CLSCBWNDEX DD ? CLSHINST DD ? CLSHICON DD ? CLSHCURSOR DD ? CLBKGROUND DD ? CLMENNAME DD ? CLNAME DD ? WNDCLASS ENDS
MENINFO STRUCT cbSize DD ? fMask DD ? fType DD ? fState DD ? wID DD ? hSubMenu DD ? hbmpChecked DD ? hbmpUnchecked DD ? dwItemData DD ? dwTypeData DD ? cch DD ? MENINFO ENDS
; файл menu2.asm .386P ; плоская модель .MODEL FLAT, stdcall include menu2.inc ; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ; ------------------------------------------------------------
; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' SPACE DB 30 dup(32),0 MENI MENINFO <0> NEWHWND DD 0 MSG MSGSTRUCT <?> WC WNDCLASS <?> HINST DD 0 ; дескриптор приложения CLASSNAME DB 'CLASS32',0 CPBUT DB 'Кнопка',0 ; выход CLSBUTN DB 'BUTTON',0 HWNDBTN DD 0 CAP DB 'Сообщение',0 MES DB 'Конец работы программы',0 MEN DB 'MENUP',0 MENC DB 'MENUC',0 ACC DD ? HMENU DD ? PRIZN DD ? BUFER DB 100 DUP(0) _DATA ENDS
; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; инициализировать счетчик MOV PRIZN, 2 ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA@4 MOV [HINST], EAX REG_CLASS: ; заполнить структуру окна ; стиль MOV [WC.CLSSTYLE], STYLE ; процедура обработки сообщений MOV [WC.CLWNDPROC], OFFSET WNDPROC MOV [WC.CLSCBCLSEX], 0 MOV [WC.CLSCBWNDEX], 0 MOV EAX, [HINST] MOV [WC.CLSHINST], EAX ; ----------иконка окна PUSH IDI_APPLICATION PUSH 0 CALL LoadIconA@8 MOV [WC.CLSHICON], EAX ; ----------курсор окна PUSH IDC_ARROW PUSH 0 CALL LoadCursorA@8 MOV [WC.CLSHCURSOR], EAX ; ——————---- MOV [WC.CLBKGROUND], 17 ; цвет окна MOV DWORD PTR [WC.CLMENNAME], OFFSET MEN MOV DWORD PTR [WC.CLNAME], OFFSET CLASSNAME PUSH OFFSET WC CALL RegisterClassA@4 ; создать окно зарегистрированного класса PUSH 0 PUSH [HINST] PUSH 0 PUSH 0 PUSH 400 ; DY - высота окна PUSH 400 ; DX - ширина окна PUSH 100 ; Y PUSH 100 ; X PUSH WS_OVERLAPPEDWINDOW PUSH OFFSET SPACE ; имя окна PUSH OFFSET CLASSNAME ; имя класса PUSH 0 CALL CreateWindowExA@48 ; проверка на ошибку CMP EAX, 0 JZ _ERR MOV [NEWHWND], EAX ; дескриптор окна ; определить идентификатор меню PUSH EAX CALL GetMenu@4 MOV HMENU, EAX ; загрузить акселераторы PUSH OFFSET MEN PUSH [HINST] CALL LoadAcceleratorsA@8 MOV ACC, EAX ; --———————————————————- PUSH SW_SHOWNORMAL PUSH [NEWHWND] CALL ShowWindow@8 ; показать созданное окно ; ------------------------- PUSH [NEWHWND] CALL UpdateWindow@4 ; команда перерисовать видимую ; часть окна, сообщение WM_PAINT ; петля обработки сообщений MSG_LOOP: PUSH 0 PUSH 0 PUSH 0 PUSH OFFSET MSG CALL GetMessageA@16 CMP EAX, 0 JE END_LOOP PUSH OFFSET MSG PUSH [ACC] PUSH [NEWHWND] CALL TranslateAcceleratorA@12 CMP EAX, 0 JNE MSG_LOOP PUSH OFFSET MSG CALL TranslateMessage@4 PUSH OFFSET MSG CALL DispatchMessageA@4 JMP MSG_LOOP END_LOOP: ; выход из программы (закрыть процесс) PUSH [MSG.MSWPARAM] CALL ExitProcess@4 _ERR: JMP END_LOOP
;---------------------------------------- ; процедура окна ; расположение параметров в стеке ; [EBP+014Н] ; LPARAM ; [EBP+10H] ; WAPARAM ; [EBP+0CH] ; MES ; [EBP+8] ; HWND WNDPROC PROC PUSH EBP MOV EBP,ESP PUSH EBX PUSH ESI PUSH EDI ; сообщение WM_DESTROY - при закрытии окна CMP DWORD PTR [EBP+0CH], WM__DESTROY JE WMDESTROY ; сообщение WM CREATE - при создании окна CMP DWORD PTR [EBP+0CH], WM_CREATE JE WMCREATE ; сообщение WM COMMAND - при событиях с элементами на окне CMP DWORD PTR [EBP+0CH], WM_COMMAND JE WMCOMMND ; сообщение WM_MENUSELECT - события, связанные с меню CMP DWORD PTR [EBP+0CH], WM_MENUSELECT JE WMMENUSELECT ; остальные события возвращаем обратно JMP DEFWNDPROC WMMENUSELECT: ; пропускаем первое сообщение при обращении к меню CMP WORD PTR [EBP+14Н],0 JE FINISH ; проверяем, что активизировано - пункт меню ;или заголовок выпадающего меню MOV EDX, 0 TEST WORD PTR [EBP+12H],MF_POPUP SETNE DL ; заполнение структуры для вызова функции ; GetMenuItemInfo MOVZX EAX,WORD PTR [EBP+10H] ; идентификатор MOV MENI.cbSize,48 MOV MENI.fMask, MIIM_TYPE MOV MENI.fType, MF_STRING MOV EBX, DWORD PTR [EBP+14H] MOV MENI.hSubMenu, EBX MOV MENI.dwTypeData, OFFSET BUFER MOV MENI.cch, 100 ; получить информацию о выбранном пункте меню PUSH OFFSET MENI PUSH EDX PUSH EAX PUSH DWORD PTR [EBP+14H] CALL GetMenuItemInfoA@16 ; проверить результат выполнения функции CMP EAX, 0 JE FINISH ; вывести название пункта меню PUSH MENI.dwTypeData PUSH 0 PUSH WM_SETTEXT PUSH DWORD PTR [EBP+0BH] CALL SendMessageA@16 MOV EAX, 0 JMP FINISH WMCOMMND: MOV EAX, HWNDBTN ; проверить, не нажата ли кнопка CMP DWORD PTR [EBP+14Н], EAX JE YES_BUT ; проверить, не выбран ли пункт меню MENUC - Выход CMP WORD PTR [EBP+10Н],5 JE WMDESTROY ; проверить, не выбран ли пункт меню с идентификатором 5 CMP WORD PTR [EBP+10Н], 4 JNE L00 JMP YES_BUT L00: MOV EAX, 0 JMP FINISH YES_BUT: ; здесь обработка нажатия кнопки ; вначале стереть надпись в заголовке PUSH OFFSET SPACE PUSH 0 PUSH WM_SETTEXT PUSH DWORD PTR [EBP+08H] CALL SendMessageA@16 ; проверить загружено или нет меню CMP PRIZN, 0 JE L1 CMP PRIZN, 1 JE L2 ; загрузить меню MENC PUSH OFFSET MENC PUSH [HINST] CALL LoadMenuA@8 ; установить меню MOV HMENU, EAX PUSH EAX PUSH DWORD PTR [EBP+08H] CALL SetMenu@8 ; установить признак MOV PRIZN,0 MOV EAX,0 JMP FINISH L2: ; загрузить меню MENUP PUSH OFFSET MEN PUSH [HINST] CALL LoadMenuA@8 ; установить меню MOV HMENU, EAX PUSH EAX PUSH DWORD PTR [EBP+08H] CALL SetMenu@8 ; установить признак MOV PRIZN, 2 MOV EAX,0 JMP FINISH L1: ; удалить меню PUSH HMENU CALL DestroyMenu@4 ; обновить содержимое окна PUSH SW_SHOWMINIMIZED PUSH DWORD PTR [EBP+08H] CALL ShowWindow@8 PUSH SW_SHOWNORMAL PUSH DWORD PTR [EBP+08H] CALL ShowWindow@8 MOV PRIZN,1 MOV EAX, 0 JMP FINISH WMCREATE: ; создать окно-кнопку PUSH 0 PUSH [HINST] PUSH 0 PUSH DWORD PTR [EBP+08H] PUSH 20 ; DY PUSH 60 ; DX PUSH 10 ; Y PUSH 10 ; X PUSH STYLBTN ; имя окна (надпись на кнопке) PUSH OFFSET CPBUT PUSH OFFSET CLSBUTN ; имя класса PUSH 0 CALL CreateWindowExA@48 MOV HWNDBTN, EAX ; запомнить дескриптор кнопки MOV EAX, 0 JMP FINISH DEFWNDPROC: PUSH DWORD PTR [EBP+14H] PUSH DWORD PTR [EBP+10H] PUSH DWORD PTR [EBP+0CH] PUSH DWORD PTR [EBP+08H] CALL DefWindowProcA@16 JMP FINISH WMDESTROY: PUSH 0 ; MB_OK PUSH OFFSET CAP PUSH OFFSET MES PUSH DWORD PTR [EBP+08H] ; дескриптор окна CALL MessageBoxA@16 PUSH 0 CALL PostQuitMessage@4 ; сообщение WM_QUIT MOV EAX, 0 FINISH: POP EDI POP ESI POP EBX POP EBP RET 16 WNDPROC ENDP _TEXT ENDS END START
Пример простой программы которая осуществляет поиск файлов и выводит их название на экран
Рисунок 2.5.1 Пример простой программы, которая осуществляет поиск файлов и выводит их название на экран.
Программа на Рисунок 2.5.1 довольно проста. Из нового здесь Вы обнаружите лишь то, как обращаться с функциями FindFirstFile и FindNextFile. Процедуры, которые используются для работы с параметрами командной строки, Вы уже встречали ранее. Вывод информации осуществляется в текущую консоль, с чем Вы тоже знакомы. Для получения дескриптора консоли используется функция GetStdHandle. Процедура WRITE позволила несколько упростить те участки программы, которые отвечают за вывод информации на экран. Ранее я обещал, что мы не обойдем вниманием строковые API-функции. В данной программе это обещание выполнено, и наряду со строковыми процедурами "собственного изготовления" используется строковая функция lstrcat, которая осуществляет сложение (конкатенацию) строк. По поводу параметра в командной строке замечу, что при наличии в имени каталога пробела Вам придется задавать имя в укороченном виде. Так, например, вместо C:\Program Files придется написать C:\Progra~1. Это должно быть понятно - пробелы отделяют параметры. Чтобы корректно решать проблему, необходимо ввести специальный разделитель для параметров, например "-" или "/".
Данная программа осуществляет поиск в указанном или текущем каталоге. Если бы программа была написана на языке высокого уровня, например Си, ее легко можно было бы видоизменить так, чтобы она осуществляла поиск по дереву каталогов. Собственно, небольшая модификация потребовалась бы только для процедуры FIND, которая должна была бы вызываться рекурсивно. Можно видеть, что эта легкость произрастает из наличия в языках высокого уровня такого элемента, как локальная переменная. Попробуем осуществить это, основываясь на материале Главы 1.2. А можно осуществить это без использования локальных переменных?
Программа на Рисунок 2.5.2 немного похожа на предыдущую программу. Но поиск она осуществляет по дереву каталогов, начиная с заданного каталога. Эта программа - одна из самых сложных в книге, поэтому советую читателю скрупулезно в ней разобраться. Может быть, Вам удастся ее усовершенствовать. Я могу дать и направление, в котором возможно такое усовершенствование. Дело в том, что вторым параметром командной строки можно указать маску поиска. Если, например, указать маску "*.ЕХЕ", по этой маске будет осуществляться поиск не только файлов, но и каталогов. Этот недостаток и следовало бы устранить в первую очередь.
Поиск по дереву каталогов оптимально производить рекурсивным образом, однако для этого необходимы локальные переменные35. Смысл использования локальной переменной в рекурсивном алгоритме заключается в том, что часть данных должна сохраняться при возврате из процедуры.
В данной программе я, ради простоты, отказался от процедуры LENSTR и использую функцию API lstrlen. Кроме того, я усовершенствовал вывод так, чтобы на экран выводилось полное имя файла.
35
Конечно, можно обойтись и без них, храня данные, например, в глобальном массиве, обращаясь к той или иной области массива в зависимости от уровня рекурсии.
; файл FILES.ASM
.386P ; плоская модель .MODEL FLAT, stdcall
; константы STD_OUTPUT_HANDLE equ -11 STD_INPUT_HANDLE equ -10
; прототипы внешних процедур EXTERN wsprintfA:NEAR EXTERN CharToOemA@8:NEAR EXTERN GetStdHandle@4:NEAR EXTERN WriteConsoleA@20:NEAR EXTERN ReadConsoleA@20:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetCommandLineA@0:NEAR EXTERN lstrcatA@8:NEAR EXTERN lstrcpyA@8:NEAR EXTERN lstrlenA@4:NEAR EXTERN FindFirstFileA@8:NEAR EXTERN FindNextFileA@8:NEAR EXTERN FindClose@4:NEAR ;----------------------------
; структура, используемая для поиска файла ; при помощи функций FindFirstFile и FindNextFile
_FIND STRUC ; атрибут файла ATR DWORD ? ; время создания файла CRTIME DWORD ? DWORD ? ; время доступа к файлу ACTIME DWORD ? DWORD ? ; время модификации файла WRTIME DWORD ? DWORD ? ; размер файла SIZEH DWORD ? SIZEL DWORD ? ; резерв DWORD ? DWORD ? ; длинное имя файла NAM DB 260 DUP (0) ; короткое имя файла ANAM DB 14 DUP (0) _FIND ENDS ;------------------------------------------------- ; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ;------------------------------------------------- ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' BUF DB 0 DB 100 dup (0) LENS DWORD ? ; количество выведенных символов HANDL DWORD ? HANDL1 DWORD ? MASKA DB "*.*" DB 50 DUP (0) AP DB "\",0 FIN _FIND <0> TEXT DB "Нажмите клавишу ENTER",13, 10, 0 BUFIN DB 10 DUP (0) ; буфер ввода NUM DB 0 NUMF DWORD 0 ; счетчик файлов NUMD DWORD 0 ; счетчик каталогов FORM DB "Число найденных файлов: %lu",0 FORM1 DB "Число найденных каталогов: %lu",0 DIRN DB " <DIR>",0 PAR DWORD 0 PRIZN DB 0 _DATA ENDS
; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить HANDLE вывода PUSH STD_OUTPUT_HANDLE CALL GetStdHandle@4 MOV HANDL,EAX ; получить HANDL1 ввода PUSH STD_INPUT_HANDLE CALL GetStdHandle@4 MOV HANDL1,EAX ; преобразовать строки для вывода PUSH OFFSET TEXT PUSH OFFSET TEXT CALL CharToOemA@8 PUSH OFFSET FORM PUSH OFFSET FORM CALL CharToOemA@8 PUSH OFFSET FORM1 PUSH OFFSET FORM1 CALL CharToOemA@8 ; получить количество параметров CALL NUMPAR MOV PAR,EAX ; если параметр один, то искать в текущем каталоге CMP EAX, 1 JE NO_PAR ;---------------------------------- ; получить параметр номером EDI MOV EDI,2 LEA EBX,BUF CALL GETPAR CMP PAR,3 JB NO_PAR ; получить параметр - маску поиска MOV EDI,3 LEA EBX, MASKA CALL GETPAR NO_PAR: ;---------------------------------- PUSH OFFSET BUF CALL FIND ; вывести количество файлов PUSH NUMF PUSH OFFSET FORM PUSH OFFSET BUF CALL wsprintfA LEA EAX, BUF MOV EDX,1 CALL WRITE ;+++++++++++++++++ ; вывести количество каталогов PUSH NUMD PUSH OFFSET FORM1 PUSH OFFSET BUF CALL wsprintfA LEA EAX, BUF MOV EDX, 1 CALL WRITE _END: PUSH 0 CALL ExitProcess@4 ; область процедур ;************************************* ; вывести строку (в конце перевод строки) ; EAX - на начало строки ; EDX - с переводом строки или без WRITE PROC ; получить длину параметра PUSH EAX PUSH EAX CALL lstrlenA@4 MOV ESI,EAX POP EBX CMP EDX, 1 JNE NO_ENT ; в конце - перевод строки MOV BYTE PTR [EBX+ESI],13 MOV BYTE PTR [EBX+ESI+1],10 MOV BYTE PTR [EBX+ESI+2],0 ADD EAX,2 NO_ENT: ; вывод строки PUSH 0 PUSH OFFSET LENS PUSH EAX PUSH EBX PUSH HANDL CALL WriteConsoleA@20 RET WRITE ENDP
; процедура определения количества параметров в строке ; определить количество параметров (->EAX) NUMPAR PROC CALL GetCommandLineA@0 MOV ESI,EAX ; указатель на строку XOR ECX,ECX ; счетчик MOV EDX,1 ; признак L1: CMP BYTE PTR [ESI],0 JE L4 CMP BYTE PTR [ESI],32 JE L3 ADD ECX,EDX ; номер параметра MOV EDX,0 JMP L2 L3: OR EDX, 1 L2: INC ESI JMP L1 L4: MOV EAX,ECX RET NUMPAR ENDP
; получить параметр из командной строки ; EBX - указывает на буфер, куда будет помещен параметр ; в буфер помещается строка с нулем на конце ; EDI - номер параметра GETPAR PROC CALL GetCommandLineA@0 MOV ESI,EAX ; указатель на строку XOR ECX,ECX ; счетчик MOV EDX,1 ; признак L1: CMP BYTE PTR [ESI],0 JE L4 CMP BYTE PTR [ESI],32 JE L3 ADD ECX,EDX ; номер параметра MOV EDX,0 JMP L2 L3: OR EDX,1 L2: CMP ECX,EDI JNE L5 MOV AL, BYTE PTR [ESI] MOV BYTE PTR [EBX], AL INC EBX L5: INC ESI JMP L1 L4: MOV BYTE PTR [EBX], 0 RET GETPAR ENDP ;----------------------------------- ; поиск в каталоге файлов и их вывод ; локальные переменные FINDH EQU [EBP-4] ; дескриптор поиска DIRS EQU [EBP-304] ; полное имя файла DIRSS EQU [EBP-604] ; для хранения каталога DIRV EQU [EBP-904] ; для временного хранения DIR EQU [EBP+8] ; параметр - имя каталога FIND PROC PUSH EBP MOV EBP,ESP SUB ESP,904 ; инициализация локальных переменных MOV ECX,300 MOV AL,0 MOV EDI,0 CLR: MOV BYTE PTR DIRS+[EDI],AL MOV BYTE PTR DIRSS+[EDI],AL MOV BYTE PTR DIRV+[EDI],AL INC EDI LOOP CLR ; определить длину пути PUSH DIR CALL lstrlenA@4 MOV EBX,EAX MOV EDI, DIR CMP BYTE PTR [EDI],0 JE _OK ;если в конце нет "\" - добавим CMP BYTE PTR [EDI+EBX-1],"\" JE _OK PUSH OFFSET AP PUSH DWORD PTR DIR CALL lstrcatA@8 _OK: ; запомним каталог PUSH DWORD PTR DIR LEA EAX,DIRSS PUSH EAX CALL lstrcpyA@8 ; путь с маской PUSH OFFSET MASKA PUSH DWORD PTR DIR CALL lstrcatA@8 ; здесь начало поиска PUSH OFFSET FIN PUSH DWORD PTR DIR CALL FindFirstFileA@8 CMP EAX,-1 JE _ERR ; сохранить дескриптор поиска MOV FINDH,EAX LF: ; исключить "файлы" "." и ".." CMP BYTE PTR FIN.NAM,"." JE _FF ;--------------------- LEA EAX,DIRSS PUSH EAX LEA EAX,DIRS PUSH EAX CALL lstrcpyA@8 ;--------------------- PUSH OFFSET FIN.NAM LEA EAX, DIRS PUSH EAX CALL lstrcatA@8 ; не каталог ли? TEST BYTE PTR FIN.ATR, 10H JE NO_DIR ; добавить в строку <DIR> PUSH OFFSET DIRN LEA EAX, DIRS PUSH EAX CALL lstrcatA@8 ; увеличим счетчики INC NUMD DEC NUMF ; установим признак каталога MOV PRIZN,1 ; вывести имя каталога LEA EAX, DIRS PUSH EAX CALL OUTF JMP _NO NO_DIR: ; вывести имя файла LEA EAX, DIRS PUSH EAX CALL OUTF ; признак файла (не каталога) MOV PRIZN,0 _NO: CMP PRIZN,0 JZ _F ; каталог, готовимся в рекурсивному вызову LEA EAX,DIRSS PUSH EAX LEA EAX, DIRV PUSH EAX CALL lstrcpyA@8 PUSH OFFSET FIN.NAM LEA EAX,DIRV PUSH EAX CALL lstrcatA@8 ; осуществляем вызов LEA EAX, DIRV PUSH EAX CALL FIND ; продолжение поиска _F: INC NUMF _FF: PUSH OFFSET FIN PUSH DWORD PTR FINDH CALL FindNextFileA@8 CMP EAX,0 JNE LF ; закрыть дескриптор поиска PUSH DWORD PTR FINDH CALL FindClose@4 _ERR: MOV ESP, EBP POP EBP RET 4 FIND ENDP ;---------------------------------- ; страничный вывод имен найденных файлов STRN EQU [EBP+8] OUTF PROC PUSH EBP MOV EBP,ESP ; преобразовать строку PUSH DWORD PTR STRN PUSH DWORD PTR STRN CALL CharToOemA@8 ; здесь вывод результата MOV EAX, STRN MOV EDX, 1 CALL WRITE INC NUM ; конец страницы? CMP NUM, 22 JNE NO MOV NUM, 0 ; ждать ввод строки MOV EDX, 0 LEA EAX, TEXT CALL WRITE PUSH 0 PUSH OFFSET LENS PUSH 10 PUSH OFFSET BUFIN PUSH HANDL1 CALL ReadConsoleA@20 NO: POP EBP RET 4 OUTF ENDP _TEXT ENDS END START
Процедура копирования одной
Рисунок 2.1.5. Вывод текста под углом 90 градусов.
В заключении раздела мы рассмотрим один очень важный вопрос. При разборе предыдущих примеров этот вопрос, скорее всего, у Вас не возникал, и вот почему. Весь вывод информации происходил в программе по получению сообщения WM_PAINT. В реальных программах вывод информации в окно может происходить по различным событиям и из различных процедур. Кроме того, если информации в окне много, то непосредственный вывод при помощи функции TextOut достаточно медленный. Чтобы воспроизводить содержимое окна, необходимо где-то запомнить это содержимое. Возникает проблема сохранения информации (и не только текстовой), находящейся в окне.
Если кто-то программировал для операционной системы MS DOS, то там подобная проблема также возникает. Решается она следующим образом: используется фоновая видеостраница, на которую выводится вся информация. Затем фоновая страница копируется на видимую страницу. При этом создается впечатление, что информация появляется на экране мгновенно. В качестве фоновой страницы используется как область ОЗУ, так и область видеопамяти.
Аналогично в операционной системе Windows образуется виртуальное окно, и весь вывод информации производится туда. Затем по приходе сообщения WM_PAINT содержимое виртуального окна копируется на реальное окно. В целом общая схема такова:
1. При создании окна:
Создается совместимый контекст устройства.
Функция CreateCompatibleDC. Полученный контекст следует запомнить.
Создается карта бит, совместимая с данным контекстом.
Функция CreateCompatibleBitmap.
Выбирается кисть цветом, совпадающим с цветом основного окна.
Создается битовый шаблон путем выполнения растровой операции с использованием выбранной кисти. Функция PatBlt.
2. Вся информация выводится в виртуальное окно и дается команда перерисовки окна. Функция InvalidateRect.
3. При получении сообщения WM_PAINT содержимое виртуального окна копируется на реальное окно. Функция BitBlt. Изложенная теория будет применена на практике в следующем разделе.
Простая программа для демонстрации графики
Рисунок 2.1.6. Простая программа для демонстрации графики.
Рассказ о графическом выводе будет неполным, если не коснуться вопроса манипулирования растровыми изображениями. Рассмотрим последовательность действий, которые необходимо выполнить для вывода растрового изображения, примеры вывода будут даны в последующих главах.
Загрузить растровое изображение и запомнить его дескриптор. Получить контекст устройства для области памяти, где будет храниться изображение. Выбрать изображение в данном контексте. Скопировать изображение на экран (BitBlt).
С растровыми изображениями удобно работать при помощи ресурсов, но их можно создавать непосредственно в программе или считывать из файла. Но об этом позднее.
Рассмотрим несколько простых консольных функций
II
Рассмотрим несколько простых консольных функций и их применение. Во-первых, работать с чужой консолью не всегда удобно. А для того чтобы создать свою консоль, используется функция AllocConsole. По завершении программы все выделенные консоли автоматически освобождаются. Однако это можно сделать и принудительно, используя функцию FreeConsole. Для того чтобы получить дескриптор консоли, используется уже знакомая Вам функция GetStdHandle, аргументом которой может являться следующая из трех констант:
STD_INPUT_HANDLE equ -10 ; для ввода
STD_OUTPUT_HANDLE equ -11 ; для вывода
STD_ERROR_HANDLE equ -12 ; для сообщения об ошибке
Следует отметить, что один процесс может иметь только одну консоль, поэтому выполнение в начале программы FreeConsole обязательно. При запуске программы в "чужой" консоли она наследует эту консоль, поэтому, пока мы не выполним функцию FreeConsole, новой консоли не создать - чужой консоли эта функция закрыть не может.
Для чтения из буфера консоли используется функция ReadConsole. Значения параметров этой функции (слева-направо)28 следующие:
1-й, дескриптор входного буфера.
2-й, адрес буфера, куда будет помещена вводимая информация.
3-й, длина этого буфера.
4-й, количество фактически прочитанных символов.
5-й, зарезервировано.
Установить позицию курсора в консоли можно при помощи функции SetConsoleCursorPosition со следующими параметрами:
1-й, дескриптор входного буфера консоли. 2-й, структура COORD:
COORD STRUC Х WORD ? Y WORD ? COORD ENDS
Хочу лишний раз подчеркнуть, что вторым параметром является не указатель на структуру (что обычно бывает), а именно структура. На самом деле для ассемблера это просто двойное слово (DWORD), у которого младшее слово - координата X, а старшее слово — координата Y.
Установить цвет выводимых букв можно с помощью функции SetConsoleTextAttribute. Первым параметром этой функции является дескриптор выходного буфера консоли, а вторым - цвет букв и фона. Цвет получается путем комбинации (сумма или операция "ИЛИ") двух или более из представленных ниже констант. Причем возможна "смесь" не только цвета и интенсивности, но и цветов (см. программа ниже).
FOREGROUND_BLUE equ 1h ; синий цвет букв
FOREGROUND_GREEN equ 2h ; зеленый цвет букв
FOREGROUND_RED equ 4h ; красный цвет букв
FOREGROUND_INTENSITY equ 8h ; повышенная интенсивность
BACKGROUND_BLUE equ 10h ; синий свет фона
BACKGROUND_GREEN equ 20h ; зеленый цвет фона
BACKGROUND_RED equ 40h ; красный цвет фона
BACKGROUND_INTENSITY equ 80h ; повышенная интенсивность
Для определения заголовка окна консоли используется функция SetConsoleTitle, единственным параметром которой является адрес строки с нулем на конце. Здесь следует оговорить следующее: если для вывода в само окно консоли требовалась DOS-кодировка, то для установки заголовка требуется Windows-кодировка. Чтобы покончить с этой проблемой раз и навсегда, посмотрим, как это можно решить средствами Windows.
Существует специальная функция CharToOem. Первым параметром этой функции является указатель на строку, которую следует перекодировать, а вторым параметром - на строку, куда следует поместить результат. Причем поместить результат можно и в строку, которую перекодируем. Вот и все, проблема перекодировки решена. В дальнейшем, в консольных приложениях, мы будем использовать данную функцию без особых оговорок.
Мы рассмотрели несколько консольных функций, всего их около пятидесяти. Нет нужды говорить обо всех этих функциях. О некоторых из них я еще скажу, но читатель, я думаю, по приведенным в книге примерам и обсуждениям сможет сам использовать в своих программах другие консольные функции. Замечу только, что для большинства консольных функций характерно то, что при правильном их завершении возвращается ненулевое значение. В случае ошибки в EAX помещается ноль.
Ну что же, пора приступать к разбору следующих примеров.
.386P ; плоская модель .MODEL FLAT, stdcall
; константы STD_OUTPUT_HANDLE equ -11 STD_INPUT_HANDLE equ -10
; атрибуты цветов FOREGROUND_BLUE equ 1h ; синий цвет букв FOREGROUND_GREEN equ 2h ; зеленый цвет букв FOREGROUND_RED equ 4h ; красный цвет букв FOREGROUND_INTENSITY equ 8h ; повышенная интенсивность BACKGROUND_BLUE equ 10h ; синий свет фона BACKGROUND_GREEN equ 20h ; зеленый цвет фона BACKGROUND_RED equ 40h ; красный цвет фона BACKGROUND_INTENSITY equ 80h ; повышенная интенсивность
COL1 = 2h+8h ; цвет выводимого текста COL2 = 1h+2h+8h ; цвет выводимого текста 2
; прототипы внешних процедур EXTERN GetStdHandle@4:NEAR EXTERN WriteConsoleA@20:NEAR EXTERN SetConsoleCursorPosition@8:NEAR EXTERN SetConsoleTitleA@4:NEAR EXTERN FreeConsole@0:NEAR EXTERN AllocConsole@0:NEAR EXTERN CharToOemA@8:NEAR EXTERN SetConsoleCursorPosition@8:NEAR EXTERN SetConsoleTextAttribute@8:NEAR EXTERN ReadConsoleA@20:NEAR EXTERN SetConsoleScreenBufferSize@8:NEAR EXTERN ExitProcess@4:NEAR
; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib
;------------------------------------------------------------ COOR STRUC X WORD ? Y WORD ? COOR ENDS
; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' HANDL DWORD ? HANDL1 DWORD ? STR1 DB "Введите строку: ",13,10,0 STR2 DB "Простой пример работы консоли",0 BUF DB 200 dup (?) LENS DWORD ? ; количество выведенных символов CRD COOR <?> _DATA ENDS
; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; перекодируем строку PUSH OFFSET STR1 PUSH OFFSET STR1 CALL CharToOemA@8 ; образовать консоль ; вначале освободить уже существующую CALL FreeConsole@0 CALL AllocConsole@0 ; получить HANDL1 ввода PUSH STD_INPUT_HANDLE CALL GetStdHandle@4 MOV HANDL1, EAX ; получить HANDL вывода PUSH STD_OUTPUT_HANDLE CALL GetStdHandle@4 MOV HANDL, EAX ; установить новый размер окна консоли MOV CRD.X, 100 MOV CRD.Y, 25 PUSH CRD PUSH EAX CALL SetConsoleScreenBufferSize@8 ; задать заголовок окна консоли PUSH OFFSET STR2 CALL SetConsoleTitleA@4 ; установить позицию курсора MOV CRD.X,0 MOV CRD.Y,10 PUSH CRD PUSH HANDL CALL SetConsoleCursorPosition@8 ; задать цветовые атрибуты выводимого текста PUSH COL1 PUSH HANDL CALL SetConsoleTextAttribute@8 ; вывести строку PUSH OFFSET STR1 CALL LENSTR ; в EBX длина строки PUSH 0 PUSH OFFSET LENS PUSH EBX PUSH OFFSET STR1 PUSH HANDL CALL WriteConsoleA@20 ; ждать ввод строки PUSH 0 PUSH OFFSET LENS PUSH 200 PUSH OFFSET BUF PUSH HANDL1 CALL ReadConsoleA@20 ; вывести полученную строку ; вначале задать цветовые атрибуты выводимого текста PUSH COL2 PUSH HANDL CALL SetConsoleTextAttribute@8 ;------------------------------------------------------------ PUSH 0 PUSH OFFSET LENS PUSH [LENS] ; длина вводимой строки PUSH OFFSET BUF PUSH HANDL CALL WriteConsoleA@20 ; небольшая задержка MOV ECX,01FFFFFFFH L1: LOOP L1 ; закрыть консоль CALL FreeConsole@0 CALL ExitProcess@4
; строка - [EBP+08H] ; длина в EBX LENSTR PROC ENTER 0,0 PUSH EAX ;-------------- CLD MOV EDI, DWORD PTR [EBP+08H] MOV EBX, EDI MOV ECX, 100 ; ограничить длину строки XOR AL,AL REPNE SCASB ; найти символ 0 SUB EDI, EBX ; длина строки, включая 0 MOV EBX, EDI DEC EBX ;-------------- POP EAX LEAVE RET 4 LENSTR ENDP _TEXT ENDS END START
Рисунок 2.2.3. Пример создания собственной консоли.
В программе на Рисунок 2.2.3, кроме уже описанных функций, появились еще две SetConsoleCursorPosition - установить позицию курсора, и здесь все довольно ясно. Функция SetConsoleScreenBufferSize менее понятна. Она устанавливает размер буфера окна консоли. Этот размер не может уменьшить уже существующий буфер (существующее окно), а может только его увеличить.
Заметим, кстати, что в функции LENSTR мы теперь используем пару команд ENTER-LEAVE (см. Гл. 1.2) вместо обычных сочетаний. Честно говоря, никаких особых преимуществ такое использование не дает. Просто пора расширять свой командный запас.
28 Вообще, как Вы понимаете, для ассемблера практически все параметры имеют тип DWORD. По смыслу же они - или адреса, или значения. Поэтому проще перечислять их, с указанием смысла, чем записывать функцию в Си-нотации.
Рассмотрим теперь вопрос о том как выводить текстовую информацию
II
Рассмотрим теперь вопрос о том, как выводить текстовую информацию с различными типами шрифтов. Удобнее всего задать параметры шрифта при помощи функции CreateFontIndirect, параметром которой является указатель на структуру LOGFONT. Хотя название функции и начинается со слова Create, речь идет не о создании, а скорее изменении существующего шрифта согласно заданным параметрам. Существует и другая функция CreateFont, которая, на мой взгляд, менее удобна при использовании на ассемблере — поработайте с ней сами, если хотите. Выбор нужного шрифта осуществляется функцией SelectObject. Начнем с того, что разберем поля этой структуры.
LOGFONT STRUC LfHeight DWORD ? LfWidth DWORD ? LfEscapement DWORD ? LfOrientation DWORD ? LfWeight DWORD ? Lfitalic DB ? LfUnderline DB ? LfStrikeOut DB ? LfCharSet DB ? LfOutPrecision DB ? LfClipPrecision DB ? LfQuality DB ? LfPitchAndFamily DB ? LfFaceName DB 32 DUP(0) LOGFONT ENDS
LfHeight - определяет высоту шрифта в логических единицах; если 0, то высота берется по умолчанию.
LfWidth - определяет ширину шрифта в логических единицах; если 0, то ширина по умолчанию.
LfEscapement - угол наклона текста в десятых долях градуса по отношению к горизонтальной оси в направлении против часовой стрелки.
LfOrientation - тоже, что и предыдущий параметр, но по отношению к отдельному символу (игнорируется в Windows 9х).
LfWeight - задает жирность шрифта (0-900).
Lfitalic - если 1, то курсив.
LfUnderline — если 1, то символы подчеркнуты.
LfStrikeOut — если 1, то символы перечеркнуты.
LfCharSet - задает множество символов шрифта, обычно определяется константой ANSI_CHARSET (=0).
LfOutPrecision - флаг точности шрифта; определяет, насколько точно созданный шрифт отвечает заданным параметрам. Возможные значения:
OUT_DEFAULT_PRECIS = 0 OUT_STRING_PRECIS = 1 OUT_CHARACTER_PRECIS = 2 OUT_STROKE_PRECIS = 3 OUT_TT_PRECIS = 4 OUT_DEVICE__PRECIS = 5 OUT_RASTER_PRECIS = 6 OUT_TT_ONLY_PRECIS = 7 OUT_OUTLINE_PRECIS = 8 OUT_SCREEN_OUTLINE_PRECIS = 9
LfClipPrecision - флаг точности прилегания шрифта; определяет, как будут отсекаться части шрифта, не попадающие в видимую область. Возможные значения:
CLIP_DEFAULT_PRECIS = 0 CLIP_CHARACTER_PRECIS = 1 CLIP_STROKE_PRECIS = 2 CLIP_MASK = 0FH CLIP_LH_ANGLES = (1 SHL 4) CLIP_TT_ALWAYS = (2 SHL 4) CLIP_EMBEDDED = (8 SHL 4)
LfQuality - флаг качества шрифта; определяет соответствие логического шрифта и шрифта, допустимого данным устройством. Возможные значения:
DEFAULT_QUALITY = 0 DRAFT_QUALITY = 1 PROOF_QUALITY = 2
LfPitchAndFamily - определяет тип и семейство шрифта. Возможные значения определяются комбинацией (ИЛИ) двух групп констант:
DEFAULT_PITCH = 0 FIXED_PITCH = 1 VARIABLE_PITCH = 2
и
FF_DONTCARE = 0 FF_ROMAN = (1 SHL 4) FF_SWISS = (2 SHL 4) FF_MODERN = (3 SHL 4) FF_SCRIPT = (4 SHL 4) FF_DECORATIVE = (5 SHL 4)
LfFaceName - содержит название шрифта. Длина имени не может превосходить 32 символа.
Обратимся к примеру задания своего шрифта (результат работы программы - на Рисунок 2.1.5). Однако поскольку большая часть программы будет совпадать с аналогичной частью предыдущих программ, я приведу здесь только необходимые фрагменты. Рассмотрим сначала фрагмент, выполняющийся при получении сообщения WM_PAINT (Рисунок 2.1.3).
WMPAINT: ;------- определить контекст PUSH OFFSET PNT PUSH DWORD PTR [EBP+08H] CALL BeginPaint@8 MOV CONT,EAX ; сохранить контекст (дескриптор) ;------- цвет фона = цвет окна PUSH RGBW PUSH EAX CALL SetBkColor@8 ;------- цвет текста (красный) PUSH RGBT PUSH CONT CALL SetTextColor@8 ;------ здесь определение координат MOV XT, 120 MOV YT, 140 ;------ задать (создать) шрифт MOV lg.LfHeight,12 ; высота фонта MOV lg.LfWidth, 9 ; ширина фонта MOV lg.LfEscapement,900 ; ориентация MOV lg.LfOrientation, 0 ; вертикальная MOV lg.LfWeight,400 ; толщина линий шрифта MOV lg.LfItalic, 0 ; курсив MOV lg.LfUnderline, 0 ; подчеркивание MOV lg.LfStrikeOut, 0 ; перечеркивание MOV lg.LfCharSet, 0 ; набор шрифтов MOV lg.LfOutPrecision, 0 MOV lg.LfClipPrecision, 0 MOV lg.LfQuality,2 MOV lg.LfPitchAndFamily,0 PUSH OFFSET lg ; задать название шрифта PUSH OFFSET NFONT PUSH OFFSET lg.LfFaceName CALL COPYSTR CALL CreateFontIndirectA@4 ;------ выбрать созданный объект PUSH EAX PUSH CONT CALL SelectObject@8 PUSH EAX ;------ вычислить длину текста в пикселях текста PUSH OFFSET TEXT CALL LENSTR ;---------- вывести текст ---------------- PUSH EBX PUSH OFFSET TEXT PUSH YT PUSH XT PUSH CONT CALL TextOutA@20 ; удалить объект "FONT" ; идентификатор уже в стеке CALL DeleteObject@4 ;---------------- закрыть контекст PUSH OFFSET PNT PUSH DWORD PTR [EBP+08H] CALL EndPaint@8 MOV EAX, 0 JMP FINISH
Результат работы программы на Рисунок
Рисунок 2.1.7. Результат работы программы на Рисунок 2.1.6.
Замечание. Специалисту в Windows-программировании, возможно, показалось странным, что мы пишем свои собственные строковые функции, вместо того чтобы воспользоваться существующими в Windows соответствующими API-функциями. Да-да, такие функции существуют, дорогой читатель. Причина моего пренебрежения этими функциями проста. Во-первых, я рассчитываю не только на "продвинутых" читателей, но и на людей, обучающихся программированию на ассемблере. Во-вторых, как я уже говорил в предисловии, данная книга - это попытка создания некоторого симбиоза ассемблера и программирования в Windows. Следуя этому принципу, мы не всегда будем решать задачи только средствами API-функций. Однако, понимая всю важность строковых API-функций, в свое время я приведу примеры их использования. Кроме того, в приложении будет дано их полное описание.
Сейчас мы рассмотрим вопрос о написании программ
II
Сейчас мы рассмотрим вопрос о написании программ, которые одинаково транслировались бы и в MASM, и в TASM. Для этого прекрасно подходит условное ассемблирование. Удобнее всего использовать IFDEF и возможности трансляторов задавать символьную константу, все равно - TASM или MASM. И в ML, и в TASM32 определен ключ /D, позволяющий задавать такую константу.
На Рисунок 2.6.1 представлена программа, транслируемая и в MASM, и TASM. Программа весьма проста, но рассмотрения ее вполне достаточно для создания более сложных подобных совместимых программ.
.386P ; плоская модель .MODEL FLAT, STDCALL ; проверить, определена символьная константа MASM или нет IFDEF MASM ; работаем в MASM EXTERN ExitProcess@4:NEAR EXTERN MessageBoxA@16:NEAR includelib с:\masm32\lib\kernel32.lib includelib c:\masm32\lib\user32.lib ELSE ; работаем в TASM EXTERN ExitProcess:NEAR EXTERN MessageBoxA:NEAR includelib c:\tasm32\lib\import32.lib ExitProcess@4 = ExitProcess MessageBoxA@16 = MessageBoxA ENDIF ;-----------------------------------------------------------
;сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' MSG DB "Простая программа",0 TIT DB "Заголовок",0 _DATA ENDS
;сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: PUSH 0 PUSH OFFSET TIT PUSH OFFSET MSG PUSH 0 ; дескриптор экрана CALL MessageBoxA@16 ;----------------------------------- PUSH 0 CALL ExitProcess@4 _TEXT ENDS END START
Структура
Структура
1. Директива STRUC позволяет объединить несколько разнородных данных в одно целое. Эти данные называются полями. Вначале при помощи STRUC определяется шаблон структуры, затем с помощью директивы < > можно определить любое количество структур. Рассмотрим пример:
STRUC COMPLEX RE DD ? IM DD ? STRUC ENDS
... ;в сегменте данных COMP1 COMPLEX <?> COMP2 COMPLEX <?>
Доступ к полям структуры осуществляется посредством точки: COMP1.RE.
2. Объединение. Объединение определяется при помощи ключевого слова UNION. От структуры объединение отличается только тем, что все поля располагаются в структуре с нулевым смещением, т.е. накладываются друг на друга.
Таймеры в консольном приложении
IV
В последнем разделе главы мы рассмотрим довольно редко освещаемый в литературе вопрос - таймеры в консольном приложении. Надо сказать, что мы несколько опережаем события и рассматриваем таймер в консольном приложении раньше, чем в приложении GUI (Graphic Universal Interface - так называются обычные оконные приложения).
Основным способом создания таймера является использование функции SetTimer. Позднее мы будем подробно о ней говорить. Таймер может быть установлен в двух режимах. Первый режим - это когда последний параметр равен нулю. В этом случае на текущее окно (его функцию) через равные промежутки времени, определяемые третьим параметром, будет приходить сообщение WM_TIMER. Во втором режиме последний параметр указывает на функцию, которая будет вызываться опять через равные промежутки времени. Однако для консольного приложения эта функция не подходит, так как сообщение WM_TIMER пересылается окну функцией DispatchMessage, которая используется в петле обработки сообщений. Но использование этой функции для консольных приложений проблематично.
Для консольных приложений следует использовать функцию timeSetEvent. Вот параметры этой функции:
1-й параметр - время задержки таймера, для нас это время совпадает со временем между двумя вызовами таймера. 2-й параметр - точность работы таймера (приоритет посылки сообщения). 3-й параметр - адрес вызываемой процедуры. 4-й параметр - параметр, посылаемый в процедуру. 5-й параметр - тип вызова - одиночный или периодический.
Если функция завершилась удачно, то в EAX возвращается идентификатор таймера.
Сама вызываемая процедура получает также 5 параметров:
1-й параметр - идентификатор таймера. 2-й параметр - не используется. 3-й параметр - параметр Dat (см. timeSetEvent). 4 и 5-й параметры - не используются.
Для удаления таймера используется функция timeKillEvent, параметром которой является идентификатор таймера.
.386P ; плоская модель .MODEL FLAT, stdcall ; константы STD_OUTPUT_HANDLE equ -11 STD_INPUT_HANDLE equ -10 TIME_PERIODIC equ 1 ; тип вызова таймера
; атрибуты цветов FOREGROUND_BLUE equ 1h ; синий цвет букв FOREGROUND_GREEN equ 2h ; зеленый цвет букв FOREGROUND_RED equ 4h ; красный цвет букв FOREGROUND_INTENSITY equ 8h ; повышенная интенсивность BACKGROUND_BLUE equ 10h ; синий свет фона BACKGROUND_GREEN equ 20h ; зеленый цвет фона BACKGROUND_RED equ 40h ; красный цвет фона BACKGROUND_INTENSITY equ 80h ; повышенная интенсивность
COL1 = 2h+8h ; цвет выводимого текста
; прототипы внешних процедур EXTERN wsprintfA:NEAR EXTERN GetStdHandle@4:NEAR EXTERN WriteConsoleA@20:NEAR EXTERN SetConsoleCursorPosition@8:NEAR EXTERN SetConsoleTitleA@4:NEAR EXTERN FreeConsole@0:NEAR EXTERN AllocConsole@0:NEAR EXTERN CharToOemA@8:NEAR EXTERN SetConsoleCursorPosition@8:NEAR EXTERN SetConsoleTextAttribute@8:NEAR EXTERN ReadConsoleA@20:NEAR EXTERN timeSetEvent@20:NEAR EXTERN timeKillEvent@4:NEAR EXTERN ExitProcess@4:NEAR
; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib includelib c:\masm32\lib\winmm.lib ;------------------------------------------------------------
COOR STRUC X WORD ? Y WORD ? COOR ENDS
; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' HANDL DWORD ? HANDL1 DWORD ? STR2 DB "Пример таймера в консольном приложении",0 STR3 DB 100 dup (0) FORM DB "Число вызовов таймера: %lu",0 BUF DB 200 dup (?) NUM DWORD 0 LENS DWORD ? ; количество выведенных символов CRD COOR <?> ID DWORD ? ; идентификатор таймера HWND DWORD ? _DATA ENDS
; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; образовать консоль ; вначале освободить уже существующую CALL FreeConsole@0 CALL AllocConsole@0 ; получить HANDL1 ввода PUSH STD_INPUT_HANDLE CALL GetStdHandle@4 MOV HANDL1,EAX ; получить HANDL вывода PUSH STD_OUTPUT_HANDLE CALL GetStdHandle@4 MOV HANDL,EAX ; задать заголовок окна консоли PUSH OFFSET STR2 CALL SetConsoleTitleA@4 ; задать цветовые атрибуты выводимого текста PUSH COL1 PUSH HANDL CALL SetConsoleTextAttribute@8 ; установить таймер PUSH TIME_PERIODIC ; периодический вызов PUSH 0 PUSH OFFSET TIME ; вызываемая таймером процедура PUSH 0 ; точность вызова таймера PUSH 1000 ; вызов через одну секунду CALL timeSetEvent@20 MOV ID, EAX ; ждать ввод строки PUSH 0 PUSH OFFSET LENS PUSH 200 PUSH OFFSET BUF PUSH HANDL1 CALL ReadConsoleA@20 ; закрыть таймер PUSH ID CALL timeKillEvent@4 ; закрыть консоль CALL FreeConsole@0 PUSH 0 CALL ExitProcess@4
; строка - [EBP+08H] ; длина в EBX LENSTR PROC ENTER 0,0 PUSH EAX ;-------------------- CLD MOV EDI, DWORD PTR [EBP+08H] MOV EBX,EDI MOV ECX,100 ; ограничить длину строки XOR AL,AL REPNE SCASB ; найти символ 0 SUB EDI,EBX ; длина строки, включая 0 MOV EBX,EDI DEC EBX ;-------------------- POP EAX LEAVE RET 4 LENSTR ENDP
; процедура вызывается таймером TIME PROC PUSHA ; сохранить все регистры ; установить позицию курсора MOV CRD.X,0 MOV CRD.Y,10 PUSH CRD PUSH HANDL CALL SetConsoleCursorPosition@8 ; заполнить строку STR3 PUSH NUM PUSH OFFSET FORM PUSH OFFSET STR3 CALL wsprintfA ADD ESP,12 ; восстановить стек ; перекодировать строку STR3 PUSH OFFSET STR3 PUSH OFFSET STR3 CALL CharToOemA@8 ; вывести строку с номером вызова таймера PUSH OFFSET STR3 CALL LENSTR PUSH 0 PUSH OFFSET LENS PUSH EBX PUSH OFFSET STR3 PUSH HANDL CALL WriteConsoleA@20 INC NUM POPA RET 20 ; выход с освобождением стека TIME ENDP _TEXT ENDS END START
Рисунок 2.2.5. Таймер в консольном режиме.
Программа на Рисунок 2.2.5 будет выводить в окно значение счетчика, которое будет каждую секунду увеличиваться на единицу.
Я начал данную главу с рассуждения о командной строке, но до сих пор не объявил, как работать с командной строкой. О, здесь все очень просто. Есть API-функция GetCommandLine, которая возвращает указатель на командную строку. Эта функция одинаково работает как для консольных приложений, так и для приложений GUI. Ниже представлена программа, печатающая параметры командной строки. Надеюсь, вы понимаете, что первым параметром является полное имя программы.
; программа вывода параметров командной строки .386P ; плоская модель .MODEL FLAT, stdcall
; константы STD_OUTPUT_HANDLE equ -11
; прототипы внешних процедур EXTERN GetStdHandle@4:NEAR EXTERN WriteConsoleA@20:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetCommandLineA@0:NEAR
; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ;------------------------------------------------------------
; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' BUF DB 100 dup (0) LENS DWORD ? ; количество выведенных символов NUM DWORD ? CNT DWORD ? HANDL DWORD ? _DATA ENDS
; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить HANDLE вывода PUSH STD_OUTPUT_HANDLE CALL GetStdHandle@4 MOV HANDL,EAX ; получить количество параметров CALL NUMPAR MOV NUM,EAX MOV CNT,0 ;------------------------------------- ; вывести параметры командной строки LL1: MOV EDI,CNT CMP NUM,EDI JE LL2 ; номер параметра INC EDI MOV CNT, EDI ; получить параметр номером EDI LEA EBX,BUF CALL GETPAR ; получить длину параметра PUSH OFFSET BUF CALL LENSTR ; в конце - перевод строки MOV BYTE PTR [BUF+EBX],13 MOV BYTE PTR [BUF+EBX+1],10 MOV BYTE PTR [BUF+EBX+2],0 ADD EBX,2 ; вывод строки PUSH 0 PUSH OFFSET LENS PUSH EBX PUSH OFFSET BUF PUSH HANDL CALL WriteConsoleA@20 JMP LL1 LL2: PUSH 0 CALL ExitProcess@4
; строка - [EBP+08H] ; длина в EBX LENSTR PROC PUSH EBP MOV EBP,ESP PUSH EAX ;-------------------- CLD MOV EDI, DWORD PTR [EBP+08H] MOV EBX,EDI MOV ECX,100 ; ограничить длину строки XOR AL,AL REPNE SCASB ; найти символ 0 SUB EDI,EBX ; длина строки, включая 0 MOV EBX,EDI DEC EBX ;-------------------- POP EAX POP EBP RET 4 LENSTR ENDP
; определить количество параметров (->EAX) NUMPAR PROC CALL GetCommandLineA@0 MOV ESI,EAX ; указатель на строку XOR ECX,ECX ; счетчик MOV EDX,1 ; признак L1: CMP BYTE PTR [ESI],0 JE L4 CMP BYTE PTR [ESI],32 JE L3 ADD ECX,EDX ; номер параметра MOV EDX, 0 JMP L2 L3: OR EDX, 1 L2: INC ESI JMP L1 L4: MOV EAX,ECX RET NUMPAR ENDP
; получить параметр ; EBX - указывает на буфер, куда будет помещен параметр ; в буфер помещается строка с нулем на конце ; EDI - номер параметра GETPAR PROC CALL GetCommandLineA@0 MOV ESI, EAX ; указатель на строку XOR ECX, ECX ; счетчик MOV EDX,1 ; признак L1: CMP BYTE PTR [ESI],0 JE L4 CMP BYTE PTR [ESI] ,32 JE L3 ADD ECX,EDX ; номер параметра MOV EDX, 0 JMP L2 L3: OR EDX, 1 L2: CMP ECX, EDI JNE L5 MOV AL, BYTE PTR [ESI] MOV BYTE PTR [EBX],AL INC EBX L5: INC ESI JMP L1 L4: MOV BYTE PTR [EBX],0 RET GETPAR ENDP _TEXT ENDS END START
Рисунок 2.2.6. Пример работы с параметрами командной строки.
Рекомендую читателю разобраться в алгоритме работы процедур NUMPAR и GETPAR.
Следует отметить, что для трансляции программы на Рисунок 2.2.6 в TASM, кроме обычных, уже известных Вам изменений, для совпадающих меток следует в начале имени поставить "@@" - признак локальности, а в начале программы поставить директиву LOCALS. Транслятор MASM метки, стоящие в процедуре, считает локальными автоматически. Подробнее о локальных метках будет сказано в Главах 2.5 и 2.6.
Текстовая строка все время в середине окна
Рисунок 2.1.2. Текстовая строка все время в середине окна.
Не могу не воспользоваться случаем и не восхититься теми возможностями, которые открывает перед программистом ассемблер. Можете передавать параметры через стек, а можете и через регистры. Хотите - сохраняйте регистры в начале процедуры, а хотите - не сохраняйте. Ассемблерный код можно совершенствовать и еще раз совершенствовать. Кстати, для любителей цепочечных (строковых) команд ниже привожу другую процедуру определения длины строки, основанную на команде микропроцессора SCAS, позволяющей осуществлять поиск нужного элемента (байта - SCASB, слова - SCASW, двойного слова - SCASD) в строке.
; длина строки - [EBP+08Н] LENSTR PROC PUSH EBP MOV EBP,ESP PUSH EAX CLD MOV EDI, DWORD PTR [EBP+08H] MOV EBX, EDI MOV ECX,100 ; ограничить длину строки XOR AL,AL REPNE SCASB ; найти символ 0 SUB EDI, EBX ; длина строки, включая 0 MOV EBX, EDI DEC EBX ; теперь здесь длина строки POP EAX POP EBP RET 4 LENSTR ENDP
Трансляция при помощи пакета TASM
Трансляция при помощи пакета TASM32
При работе с ресурсами в пакете TASM32 следует учитывать некоторые особенности. И дело здесь не только в том, что компиляторы ресурсов могут иметь свои конструкции языка, которые не понимает другой компилятор. Об этом мы уже говорили и больше касаться этого не будем. Есть и другое отличие. Пусть программа называется DIAL.ASM, а файл ресурсов DIAL.RC. Тогда полная трансляция в пакете TASM32 будет выглядеть следующим образом.
TASM32 /ml DIAL.ASM BRCC32 DIAL.RC TLINK32 DIAL.OBJ,,,,, DIAL.RES
В результате получится программа DIAL.EXE. Если программа представляет на экран диалоговое окно (именно диалоговое, а не обычное), то, скорее всего (возможно и нет). Вы обнаружите, что стиль его соответствует стилю окон Windows 3.1, чего не было в случае трансляции в MASM32. Проблема разрешится, если добавить в стиль окна константу DS_3DLOOK, равную 0x0004L. В файле помощи можно найти утверждение, что стиль DS_3DLOOK должен автоматически устанавливаться у диалоговых окон. Возможно, суть здесь заключается в особенности работы TLINK32.EXE. Других существенных отличий при работе с ресурсами в пакетах MASM32 и TASM32 я не усматриваю.
Трансляция программы в TASM
Трансляция программы в TASM.
Основная проблема при трансляции программ на Рисунок 2.5.1 и Рисунок 2.5.2 возникнет с локальными метками. Локальная метка - это метка, которая действует в пределах некоторого блока программы. В нашем случае таким блоком программы является процедура. Транслятор MASM автоматически различает метки, находящиеся в пределах процедуры, и считает их локальными. Поэтому не возникает проблемы, когда в разных процедурах встречаются метки с одинаковым именем. В TASM несколько иной подход: по умолчанию метки считаются глобальными. Локальные метки должны иметь перед именем обозначение "@@". Кроме того, в начале программы следует поставить директиву LOCALS. Сделав нужные метки локальными и поставив директиву LOCALS, Вы без труда, уже известными действиями, приведете программу к виду, приемлемому для TASM. Не забудьте о преобразовании wsprintfA -> _wsprintfA.
Условное ассемблирование
Условное ассемблирование
1. Условное ассемблирование дает возможность при трансляции обходить тот или иной участок программы. Существует три вида условного ассемблирования.
а) IF выражение ... ENDIF
б) IF выражение ... ELSE ... ENDIF в) IF выражение 1 ... ELSEIF выражение 2 ... ELSEIF выражение 3 ... ELSE ... ENDIF
Условие считается не выполненным, если выражение принимает значение 0 и выполненным, если выражение отлично от нуля.
2. Ассемблеры MASM и TASM поддерживают также несколько условных специальных директив, назовем некоторые из них.
а)
IFE выражение ... ELSEIFE ... ENDIFE
б) Операторы IF1 и IF2 проверяют первый и второй проход при ассемблировании.
в) Оператор IFDEF - проверяет, определено ли в программе символическое имя, IFDEFN - обратный оператор. И другие IF операторы. Они имеются в любом справочнике по ассемблеру.
г) Имеется целый набор директив, начинающихся с .ERR. Например, .ERRE выражение - вызовет прекращение трансляции и сообщение об ошибке, если выражение станет равным 0.
Условное ассемблирование понадобится нам в конце главы для написания программы, транслируемой как в MASM, так и TASM.
В данном разделе мы займемся изучением языка ресурсов
I
В данном разделе мы займемся изучением языка ресурсов. Зная его, можно вполне обойтись без специального редактора ресурсов. В настоящее время существует большое количество редакторов ресурсов. Видимо, поэтому книги по программированию уделяют мало внимания языку описания ресурсов. Мы, напротив, не будем касаться этих программ31, а как можно подробнее разберем структуру и язык ресурсов. Отметим также, что изложенный стандарт одинаково будет восприниматься обоими компиляторами ресурсов. Исключения будут обговорены отдельно.
Начнем с перечисления наиболее употребляемых ресурсов.
Иконки. Курсоры. Битовая картинка. Строка. Диалоговое окно. Меню. Акселераторы.
Вот наиболее распространенные ресурсы. Надо только иметь в виду, что такой ресурс, как диалоговое окно, может содержать в себе управляющие элементы, которые также должны быть описаны, но в рамках описания окна. Но об этом поговорим несколько позже.
1. Иконки. Могут быть описаны в самом файле ресурсов, либо храниться в отдельном файле *.ico. Рассмотрим последний случай. Вот файл ресурсов resu.rc:
#define IDI_ICON1 1
IDI_ICON1 ICON "Cdrom01.ico"
Как видите, файл содержит всего две значимых строки. Одна строка определяет идентификатор иконки, вторая - ассоциирует идентификатор с файлом "Cdrom01.ico". Оператор define является Си-оператором препроцессора. Как вы увидите в дальнейшем, язык ресурсов очень напоминает язык Си. Откомпилируем текстовый файл resu.rc: RC resu.rc. На диске появляется объектный файл resu.res. При компоновке укажем этот файл в командной строке:
LINK /subsystem:windows resu.obj resu.res
У читателя возникает вопрос: как использовать данный ресурс в программе? Здесь все просто: предположим, что мы хотим установить новую иконку для окна. Вот фрагмент программы, который устанавливает стандартную иконку для главного окна.
PUSH IDI_APPLICATION PUSH 0 CALL LoadIconA@8 MOV [WC.CLSHICON], EAX
А вот фрагмент программы для установки иконки, указанной в файле ресурсов:
PUSH 1 ; идентификатор иконки (см. файл resu.rc) PUSH [HINST] ; идентификатор процесса CALL LoadIconA@8 MOV [WC.CLSHICON], EAX
Компилятор ресурсов brcc32.exe ( из пакета TASM32) допускает включение иконки в текст проекта. В этом случае проект будет иметь следующий вид (Рисунок 2.3.1):
#define IDI_ICON1 1
IDI_ICON1 ICON { '00 00 01 00 02 00 20 20 10 00 00 00 00 00 E8 02' '00 00 26 00 00 00 10 10 10 00 00 00 00 00 28 01' '00 00 0E 03 00 00 28 00 00 00 20 00 00 00 40 00' '00 00 01 00 04 00 00 00 00 00 80 02 00 00 00 00' '00 00 00 00 00 00 10 00 00 00 00 00 00 00 00 00' '00 00 00 00 BF 00 00 BF 00 00 00 BF BF 00 BF 00' '00 00 BF 00 BF 00 BF BF 00 00 C0 C0 C0 00 80 80' '80 00 00 00 FF 00 00 FF 00 00 00 FF FF 00 FF 00' '00 00 FF 00 FF 00 FF FF 00 00 FF FF FF 00 00 00' '00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00' '00 00 00 00 77 78 33 AA 00 00 00 00 00 00 00 00' '00 00 07 7F 77 78 33 AA 77 80 00 00 00 00 00 00' '00 0F F7 F7 77 78 33 AA 77 C8 60 00 00 00 00 00' '00 FF FF 7F 77 78 33 AA 78 C6 66 00 00 00 00 00' '0F FF FF F7 77 78 38 A7 7C 86 66 60 00 00 00 00' '77 FF FF 7F 77 78 37 A7 8C 66 66 77 00 00 00 07' '87 7F FF F7 F7 78 37 A7 C8 66 67 77 70 00 00 08' '78 77 FF FF 77 78 3A A8 C6 66 77 77 E0 00 00 87' '87 87 7F FF F7 78 3A AC 86 67 77 EE EE 00 00 78' '78 78 77 FF 7F 78 3A 8C 66 77 EE EE BB 00 07 87' '87 87 87 7F F7 78 3A C8 67 7E EB BB BA A0 08 78' '78 78 78 77 F8 88 88 C6 7E BB BB AA AA A0 07 87' '87 87 87 87 88 00 00 88 BB BA AA A3 33 30 08 78' '78 78 78 78 80 8F F8 08 33 33 33 DD DD D0 08 88' '88 88 88 88 80 FF FF 08 5D 5D 5D 5D 5D 50 05 D5' 'D5 D5 D5 D5 80 FF FF 08 88 88 88 88 88 80 0D DD' 'DD 33 33 33 80 8F F8 08 87 87 87 87 87 80 03 33' '3A AA AB BB 88 00 00 88 78 78 78 78 78 70 0A AA' 'AA BB BB E7 6C 88 88 8F 77 87 87 87 87 80 0A AB' 'BB BE E7 76 8C A3 87 7F F7 78 78 78 78 70 00 BB' 'EE EE 77 66 C8 A3 87 F7 FF 77 87 87 87 00 00 EE' 'EE 77 76 68 CA A3 87 7F FF F7 78 78 78 00 00 0E' '77 77 66 6C 8A A3 87 77 FF FF 77 87 80 00 00 07' '77 76 66 8C 7A 73 87 7F 7F FF F7 78 70 00 00 00' '77 66 66 C8 7A 73 87 77 F7 FF FF 77 00 00 00 00' '06 66 68 C7 7A 83 87 77 7F FF FF F0 00 00 00 00' '00 66 6C 87 AA 33 87 77 F7 FF FF 00 00 00 00 00' '00 06 8C 77 AA 33 87 77 7F 7F F0 00 00 00 00 00' '00 00 08 77 AA 33 87 77 F7 70 00 00 00 00 00 00' '00 00 00 00 AA 33 87 77 00 00 00 00 00 00 00 00' '00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF F0' '0F FF FF 80 01 FF FE 00 00 7F FC 00 00 3F F8 00' '00 1F F0 00 00 0F E0 00 00 07 C0 00 00 03 C0 00' '00 03 80 00 00 01 80 00 00 01 00 00 00 00 00 00' '00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00' '00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00' '00 00 80 00 00 01 80 00 00 01 C0 00 00 03 C0 00' '00 03 E0 00 00 07 F0 00 00 0F F8 00 00 1F FC 00' '00 3F FE 00 00 7F FF 80 01 FF FF F0 0F FF 28 00' '00 00 10 00 00 00 20 00 00 00 01 00 04 00 00 00' '00 00 C0 00 00 00 00 00 00 00 00 00 00 00 00 00' '00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 80' '00 00 00 80 80 00 80 00 00 00 80 00 80 00 80 80' '00 00 80 80 80 00 C0 C0 C0 00 00 00 FF 00 00 FF' '00 00 00 FF FF 00 FF 00 00 00 FF 00 FF 00 FF FF' '00 00 FF FF FF 00 00 00 00 00 00 00 00 00 00 00' '08 87 3A 80 00 00 00 0F F8 87 32 CC 60 00 00 08' 'F8 87 32 C6 68 00 00 87 8F 87 2C 66 86 00 08 78' '78 87 2C 68 AA A0 07 87 87 70 08 2A A2 20 08 78' '78 0F F0 II 15 50 05 51 II 0F F0 87 87 80 02 2A' 'A2 80 08 78 78 70 0A AA 86 C2 78 87 87 80 00 68' '66 C2 78 F8 78 00 00 86 6C 23 78 8F 88 00 00 06' 'CC 23 78 8F F0 00 00 00 08 A3 78 80 00 00 00 00' '00 00 00 00 00 00 F8 IF 00 00 E0 07 00 00 C0 03' '00 00 80 01 00 00 80 01 00 00 00 00 00 00 00 00' '00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00' '00 00 80 01 00 00 80 01 00 00 C0 03 00 00 E0 07' '00 00 F8 1F 00 00' }
Рисунок 2.3.1. Пример файла ресурсов с включенным туда кодом иконки.
2. Курсоры. Подход здесь полностью идентичен. Привожу ниже файл ресурсов, где определен и курсор, и иконка.
#define IDI_ICON1 1 #define IDI_CUR1 2
IDI_ICON1 ICON "Cdrom01.ico" IDI_CUR1 CURSOR "4way01.cur"
А вот фрагмент программы, вызывающей иконку и курсор.
;----------иконка окна PUSH 1 ; идентификатор иконки PUSH [HINST] CALL LoadIconA@8 MOV [WC.CLSHICON], EAX ; ----------курсор окна PUSH 2 ; идентификатор курсора PUSH [HINST] CALL LoadCursorA@8 MOV [WC.CLSHCURSOR], EAX
Как и для иконки, программа brcc32.exe обрабатывает определение курсора в тексте файла ресурсов.
3. Битовые картинки (*.BMP). Здесь ситуация аналогична двум предыдущим. Вот пример файла ресурсов с битовой картинкой.
#define ВIТ1 1
BIT1 BITMAP "PIR2.BMP"
4. Строки. Чтобы задать строку или несколько строк используется ключевое слово STRINGTABLE. Ниже представлен текст ресурса, задающий две строки. Для загрузки строки в программу используется функция LoadString (см. ниже). Строки, задаваемые в файле ресурсов, могут играть роль констант.
#define STR1 1 #define STR2 2
STRINGTABLE { STR1, "Сообщение" STR2, "Версия 1.01" }
5. Диалоговые окна. Диалоговые окна являются наиболее сложными элементами ресурсов. В отличие от ресурсов, которые мы до сих пор рассматривали, для диалога не задается идентификатор. Обращение к диалогу происходит по его имени (строке).
#define WS_SYSMENU 0x00080000L #define WS_MINIMIZEBOX 0x00020000L #define WS_MAXIMIZEBOX 0x00010000L
DIAL1 DIALOG 0, 0, 240, 120 STYLE WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX CAPTION "Пример диалогового окна" FONT 8, "Arial" { }
Как видим, определение диалога начинается со строки, содержащей ключевое слово DIALOG. В этой же строке далее указывается положение и размер диалогового окна. Далее идут строки, содержащие другие свойства окна. Наконец идут фигурные скобки. В данном случае они пусты. Это означает, что на окне нет никаких управляющих элементов. Тип окна, а также других элементов определяется константами, которые мы поместили в начале файла. Эти константы стандартны, и для языка Си хранятся в файле RESOURCE.H. Мы, как и раньше, все константы будем определять непосредственно в файле ресурсов. Обращаю ваше внимание, что константы определяются согласно нотации языка Си.
Прежде чем разбирать пример на Рисунок 2.3.2, рассмотрим особенности работы с диалоговыми окнами. Диалоговое окно очень похоже на обычное окно. Так же как обычное окно, оно имеет свою процедуру. Процедура диалогового окна имеет те же параметры, что и процедура обычного окна. Сообщений, которые приходят на процедуру диалогового окна, гораздо меньше. Но те, которые у диалогового окна имеются, в основном совпадают с аналогичными сообщениями для обычного окна. Только вместо сообщения WM_CREATE приходит сообщение WM_INITDIALOG. Процедура диалогового окна может возвращать либо нулевое, либо ненулевое значение. Ненулевое значение должно возвращаться в том случае, если процедура обрабатывает (берет на себя обработку) данное сообщение, и ноль - если предоставляет обработку системе.
Отличия в поведении диалогового окна от обычного окна легко объяснить. Действительно, если Вы создаете обычное окно, то все его свойства определяются тремя факторами: свойствами класса, свойствами, определяемыми при создании окна, реакцией процедуры окна на определенные сообщения. При создании диалогового окна все свойства заданы в ресурсах. Часть этих свойств задается, когда при вызове функции создания диалогового окна (DialogBox, DialogBoxParam и др.) неявно вызывается функция CreateWindow. Остальная же часть свойств определяется поведением внутренней функции, которую создает система при создании диалогового окна. Если с диалоговым окном что-то происходит, то сообщение сначала приходит на внутреннюю процедуру, а затем вызывается процедура диалогового окна, которую мы создаем в программе. Если процедура возвращает 0, то внутренняя процедура продолжает обработку данного сообщения, если же возвращается ненулевое значение, внутренняя процедура не обрабатывает сообщение. Вот, вкратце, как работают механизмы, регулирующие работу диалогового окна. Рассмотрите теперь программу на Рисунок 2.3.2, ниже будет дано ее разъяснение.
// файл dial.rc // определение констант #define WS_SYSMENU 0x00080000L #define WS_MINIMIZEBOX 0x00020000L #define WS_MAXIMIZEBOX 0x00010000L
// идентификаторы #define STR1 1 #define STR2 2 #define IDI_ICON1 3
// определили иконку IDI_ICON1 ICON "ico1.ico"
// определение диалогового окна DIAL1 DIALOG 0, 0, 240, 120 STYLE WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX CAPTION "Пример диалогового окна" FONT 8, "Arial" { }
// определение строк STRINGTABLE { STR1, "Сообщение" STR2, "Версия программы 1.00" }
; файл dial.inc ; константы
; сообщение приходит при закрытии окна WM_CLOSE equ 10h WM_INITDIALOG equ 110h WM_SETICON equ 80h ; прототипы внешних процедур EXTERN MessageBoxA@16:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetModuleHandleA@4:NEAR EXTERN DialogBoxParamA@20:NEAR EXTERN EndDialog@8:NEAR EXTERN LoadStringA@16:NEAR EXTERN LoadIconA@8:NEAR EXTERN SendMessageA@16:NEAR ; структуры ; структура сообщения MSGSTRUCT STRUC MSHWND DD ? MSMESSAGE DD ? MSWPARAM DD ? MSLPARAM DD ? MSTIME DD ? MSPT DD ? MSGSTRUCT ENDS
;файл dial.asm .386P
; плоская модель .MODEL FLAT, stdcall include dial.inc
; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ;--------------------------------------------------
; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' MSG MSGSTRUCT <?> HINST DD 0 ; дескриптор приложения PA DB "DIAL1",0 BUF1 DB 40 dup(0) BUF2 DB 40 dup(0) _DATA ENDS
; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA@4 MOV [HINST],EAX ;------ ; загрузить строку PUSH 40 PUSH OFFSET BUF1 PUSH 1 PUSH [HINST] CALL LoadStringA@16 ; загрузить строку PUSH 40 PUSH OFFSET BUF2 PUSH 2 PUSH [HINST] CALL LoadStringA@16 ;------------------------------------------------------------ PUSH 0 ; MB_OK PUSH OFFSET BUF1 PUSH OFFSET BUF2 PUSH 0 CALL MessageBoxA@16 ; создать диалоговое окно PUSH 0 PUSH OFFSET WNDPROC ; процедура окна PUSH 0 PUSH OFFSET PA ; название ресурса (DIAL1) PUSH [HINST] CALL DialogBoxParamA@20 CMP EAX,-1 JNE KOL KOL: PUSH 0 CALL ExitProcess@4 ;--------------------------
; процедура диалогового окна ; расположение параметров в стеке ; [EBP+014Н] ; LPARAM ; [EBP+10H] ; WAPARAM ; [EBP+0CН] ; MES ; [EBP+8] ; HWND WNDPROC PROC PUSH EBP MOV EBP,ESP PUSH EBX PUSH ESI PUSH EDI ;----- CMP DWORD PTR [EBP+ОСН], WM_CLOSE JNE L1 PUSH 0 PUSH DWORD PTR [EBP+08H] CALL EndDialog@8 JMP FINISH L1: CMP DWORD PTR [EBP+ОСН], WM_INITDIALOG JNE FINISH ; загрузить иконку PUSH 3 ; идентификатор иконки PUSH [HINST] ; идентификатор процесса CALL LoadIconA@8 ; установить иконку PUSH EAX PUSH 0 ; тип иконки (маленькая) PUSH WM_SETICON PUSH DWORD PTR [EBP+08H] CALL SendMessageA@16 FINISH: POP EDI POP ESI POP EBX POP EBP MOV EAX, 0 RET 16 WNDPROC ENDP _TEXT ENDS END START
Рисунок 2.3.2. Демонстрация использования простых ресурсов.
Рассмотрим теперь, как работает эта программа.
Файл ресурсов должен быть Вам понятен, так как все используемые там ресурсы были подробно рассмотрены ранее. Замечу только, что файл ресурсов содержит сразу несколько элементов. При этом все ресурсы, кроме диалогового окна, должны иметь идентификатор. Для диалогового окна определяющим является его название, в нашем случае это DIAL1. Перед тем как вызвать диалоговое окно, демонстрируется то, как нужно работать с таким ресурсом, как строка. Как видите, это достаточно просто. При помощи функции LoadString строка загружается в буфер, после чего с ней можно работать, как с обычной строкой. Вызов диалогового окна достаточно очевиден, так что перейдем сразу к процедуре диалогового окна. Начнем с сообщения WM_INITDIALOG. Это сообщение, как и сообщение WM_CREATE для обычного окна, приходит один раз при создании окна. Это весьма удобно для проведения какой-то начальной инициализации. Мы используем это для определения иконки диалогового окна. В начале загружаем иконку, а далее посылаем сообщение установить иконку для данного окна (WM_SETICON). Вторым сообщением, которое мы обрабатываем, является WM_CLOSE. Это сообщение приходит, когда происходит щелчок мышью по крестику в правом верхнем углу экрана. По получении этого сообщения выполняется функция EndDialog, что приводит к удалению диалогового окна из памяти, выходу из функции DialogBoxParamA и в конечном итоге - к выходу из программы.
Выше было сказано, что процедура диалогового окна должна возвращать ненулевое значение, если она берет на себя обработку данного сообщения. Как видно из данного примера, в принципе в этом не всегда имеется необходимость. В дальнейшем мы акцентируем внимание на тех случаях, когда в этом есть необходимость.
31
Лично я предпочитаю использовать редактор ресурсов из пакета Borland C++ 5.00, либо простой текстовый редактор.
Вывод на консоль содержимого текстового файла Первый способ
Рисунок 2.5.3(1). Вывод на консоль содержимого текстового файла. Первый способ.
; файл FILES2.ASM .386P ; плоская модель .MODEL FLAT, stdcall
; константы STD_OUTPUT_HANDLE equ -11 GENERIC_READ equ 80000000h GENERIC_WRITE equ 40000000h GEN = GENERIC_READ or GENERIC_WRITE SHARE = 0 OPEN_EXISTING equ 3
; прототипы внешних процедур EXTERN ExitProcess@4:NEAR EXTERN GetCommandLineA@0:NEAR EXTERN CreateFileA@28:NEAR EXTERN CloseHandle@4:NEAR EXTERN ReadFile@20:NEAR EXTERN WriteFile@20:NEAR ;------------------------------------------------- ; директивы компоновщику для подключения библиотек ; includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ;-------------------------------------------------
; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' HANDL DWORD ? HFILE DWORD ? BUF DB 100 DUP (0) BUFER DB 300 DUP (0) NUMB DWORD ? NUMW DWORD ? NAMEOUT DB "CONOUT$" _DATA ENDS
; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START:
; получить HANDLE вывода (консоли) как файла PUSH 0 PUSH 0 PUSH OPEN_EXISTING PUSH 0 PUSH 0 PUSH GEN PUSH OFFSET NAMEOUT CALL CreateFileA@28 MOV HANDL,EAX ; получить количество параметров CALL NUMPAR CMP EAX, 1 JE NO_PAR ;--------------------------------------------- ; получить параметр номером EDI MOV EDI,2 LEA EBX,BUF CALL GETPAR ; открыть файл PUSH 0 PUSH 0 PUSH OPEN_EXISTING PUSH 0 PUSH 0 PUSH GEN PUSH OFFSET BUF CALL CreateFileA@28 CMP EAX,-1 JE NO_PAR MOV HFILE,EAX L00: ; прочесть в буфер PUSH 0 PUSH OFFSET NUMB PUSH 300 PUSH OFFSET BUFER PUSH HFILE CALL ReadFile@20 ; вывести на консоль как в файл PUSH 0 PUSH OFFSET NUMW PUSH NUMB PUSH OFFSET BUFER PUSH HANDL CALL WriteFile@20 CMP NUMB, 300 JE L00 ; закрыть файл PUSH HFILE CALL CloseHandle@4 ; конец работы программы NO_PAR: PUSH 0 CALL ExitProcess@4
; область процедур ; процедура определения количества параметров в строке ; определить количество параметров (->EAX) NUMPAR PROC CALL GetCommandLineA@0 MOV ESI,EAX ; указатель на строку XOR ECX,ECX ; счетчик MOV EDX,1 ; признак L1: CMP BYTE PTR [ESI],0 JE L4 CMP BYTE PTR [ESI],32 JE L3 ADD ECX,EDX ; номер параметра MOV EDX,0 JMP L2 L3: OR EDX,1 L2: INC ESI JMP L1 L4: MOV EAX,ECX RET NUMPAR ENDP
; получить параметр из командной строки ; EBX - указывает на буфер, куда будет помещен параметр ; в буфер помещается строка с нулем на конце ; EDI - номер параметра GETPAR PROC CALL GetCommandLineA@0 MOV ESI,EAX ; указатель на строку XOR ECX,ECX ; счетчик MOV EDX,1 ; признак L1: CMP BYTE PTR [ESI],0 JE L4 CMP BYTE PTR [ESI],32 JE L3 ADD ECX,EDX ; номер параметра MOV EDX,0 JMP L2 L3: OR EDX,1 L2: CMP ECX,EDI JNE L5 MOV AL,BYTE PTR [ESI] MOV BYTE PTR [EBX], AL INC EBX L5: INC ESI JMP L1 L4: MOV BYTE PTR [EBX], 0 RET GETPAR ENDP _TEXT ENDS END START
Вывод на консоль содержимого текстового фаша Второй способ
Рисунок 2.5.3(2). Вывод на консоль содержимого текстового фаша. Второй способ.
Сейчас мы поговорим более подробно о структуре текстового файла. При работе с языками высокого уровня теряются определенные алгоритмические навыки. Это касается, в частности, и работы с текстовыми файлами. Ассемблер не дает расслабиться. Рассмотрим возможные варианты работы с текстовыми файлами.
Основным признаком текстового файла является то, что он состоит из строк разной длины. Строки отделены друг от друга разделителями. Чаще всего это последовательность двух кодов - 13 и 10. Возможны и другие варианты, например, некоторые DOS-редакторы отделяли строки только одним кодом 13.
Построчное чтение текстового файла можно осуществить четырьмя наиболее очевидными способами.
Побайтное чтение из файла. Как только достигаем символа-разделителя, производим действие над считанной строкой и переходим к чтению следующей строки. При этом, разумеется, следует учесть, что на конце файла может не быть символа-разделителя. Если кто-то решит, что это слишком медленный способ, то замечу, что Windows неплохо кэширует диск, так что все выглядит не так уж плохо. Чтение в небольшой буфер, но так чтобы туда входила, по крайней мере, одна строка. Прочитав, находим в буфере конец строки и производим над ней какое-либо действие. Далее следует обратиться к файлу и передвинуть указатель так, чтобы он был в файле на начале следующей строки и, разумеется, повторить действие. Чтение в произвольный буфер. После чтения производится поиск всех строк, попавших в буфер, и совершение над ними действий. При этом с большой вероятностью должна возникнуть ситуация, когда одна строка неполностью умещается в буфере. Мы обязаны учесть такую возможность. Чтение в буфер, в который помещается весь файл. Это частный случай третьего подхода, и наиболее простой с точки зрения программирования.
В программе на Рисунок 2.5.4 реализуется третий подход.
; файл FILES2.ASM .386P ; плоская модель .MODEL FLAT, stdcall ; константы STD_OUTPUT_HANDLE equ -11 GENERIC_READ equ 80000000h GENERIC_WRITE equ 40000000h GEN = GENERIC_READ or GENERIC_WRITE SHARE = 0 OPEN_EXISTING equ 3 ; прототипы внешних процедур EXTERN ExitProcess@4:NEAR EXTERN GetCommandLineA@0:NEAR EXTERN CreateFileA@28:NEAR EXTERN CloseHandle@4:NEAR EXTERN ReadFile@20:NEAR EXTERN WriteFile@20:NEAR EXTERN CharToOemA@8:NEAR ;------------------------------------------------- ; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ;-------------------------------------------------
; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' HANDL DWORD ? ; дескриптор консоли HFILE DWORD ? ; дескриптор файла BUF DB 100 DUP (0) ; буфер для параметров BUFER DB 1000 DUP (0) ; буфер для файла NAMEOUT DB "CONOUT$" INDS DD 0 ; номер символа в строке INDB DD 0 ; номер символа в буфере NUMB DD ? NUMC DD ? PRIZN DD 0 STROKA DB 300 DUP (0) _DATA ENDS
; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить HANDLE вывода (консоли) как файла PUSH 0 PUSH 0 PUSH OPEN_EXISTING PUSH 0 PUSH 0 PUSH GEN PUSH OFFSET NAMEOUT CALL CreateFileA@28 MOV HANDL,EAX ; получить количество параметров CALL NUMPAR CMP EAX, 1 JE NO_PAR ;--------------------------------------------------- ; получить параметр номером EDI MOV EDI,2 LEA EBX,BUF CALL GET PAR ; открыть файл PUSH 0 PUSH 0 PUSH OPEN_EXISTING PUSH 0 PUSH 0 PUSH GEN PUSH OFFSET BUF CALL CreateFileA@28 CMP EAX,-1 JE NO_PAR MOV HFILE, EAX ;++++++++++++++++++++++++++++ L00: ; читать 1000 байт PUSH 0 PUSH OFFSET NUMB PUSH 1000 PUSH OFFSET BUFER PUSH HFILE CALL ReadFile@20 MOV INDB, 0 ; проверим, есть ли в буфере байты CMP NUMB, 0 JZ _CLOSE ; заполняем строку L001: MOV EDI,INDS MOV ESI,INDB MOV AL,BYTE PTR BUFER[ESI] CMP AL,13 ; проверка на конец строки JE _ENDSTR MOV BYTE PTR STROKA[EDI],AL INC ESI INC EDI MOV INDS,EDI MOV INDB,ESI CMP NUMB, ESI ; проверка на конец буфера JNBE L001 ; закончился буфер MOV INDS,EDI MOV INDB,ESI JMP L00 _ENDSTR: ; делаем что-то со строкой CALL OUTST ; обнулить строку MOV INDS,0 ; перейти к следующей строке в буфере ADD INDB,2 ; не закончился ли буфер? MOV ESI,INDB CMP NUMB, ESI JAE L001 JMP L00 ;++++++++++++++++++++++++++++++ _CLOSE: ; проверим, не пустая ли строка CMP INDS,0 JZ CONT ; делаем что-то со строкой CALL OUTST CONT: ; закрыть файлы PUSH HFILE CALL CloseHandle@4 ; конец работы программы NO_PAR: PUSH 0 CALL ExitProcess@4
; область процедур ; процедура определения количества параметров в строке ; определить количество параметров (->EAX) NUMPAR PROC CALL GetCommandLineA@0 MOV ESI,EAX ; указатель на строку XOR ECX,ECX ; счетчик MOV EDX,1 ; признак L1: CMP BYTE PTR [ESI],0 JE L4 CMP BYTE PTR [ESI],32 JE L3 ADD ECX,EDX ; номер параметра MOV EDX,0 JMP L2 L3: OR EDX,1 L2: INC ESI JMP L1 L4: MOV EAX,ECX RET NUMPAR ENDP
; получить параметр из командной строки ; EBX - указывает на буфер, куда будет помещен параметр ; в буфер помещается строка с нулем на конце ; EDI - номер параметра GETPAR PROC CALL GetCommandLineA@0 MOV ESI,EAX ; указатель на строку XOR ECX,ECX ; счетчик MOV EDX,1 ; признак L1: CMP BYTE PTR [ESI],0 JE L4 CMP BYTE PTR [ESI],32 JE L3 ADD ECX,EDX ; номер параметра MOV EDX,0 JMP L2 L3: OR EDX,1 L2: CMP ECX,EDI JNE L5 MOV AL,BYTE PTR [ESI] MOV BYTE PTR [EBX],AL INC EBX L5: INC ESI JMP L1 L4: MOV BYTE PTR [EBX],0 RET GETPAR ENDP
; вывести строку в консоль с разделителем OUTST PROC MOV EBX,INDS MOV BYTE PTR STROKA[EBX],0 PUSH OFFSET STROKA PUSH OFFSET STROKA CALL CharToOemA@8 ; в конце строки - разделитель MOV BYTE PTR STROKA[EBX],6 INC INDS ; вывести строку PUSH 0 PUSH OFFSET NUMC PUSH INDS PUSH OFFSET STROKA PUSH HANDL CALL WriteFile@20 RET OUTST ENDP _TEXT ENDS END START
Вызов процедур
Вызов процедур
1. С упрощенным вызовом процедур в MASM Вы уже познакомились. Это директива INVOKE. Процедура должна быть заранее определена с использованием ключевого слова PROTO. Например:
MessageBoxA PROTO :DWORD,:DWORD,:DWORD,:DWORD ... invoke MessageBox, h, ADDR TheMsg, ADDR TitleW, MB_OK
Здесь h - дескриптор окна, откуда вызывается сообщение, TheMsg - строка сообщения, TitleW - заголовок окна, MB_OK - тип сообщения. ADDR в данном случае синоним OFFSET.
2. Оказывается, в синтаксисе TASM тоже имеется свой упрощенный вызов.
EXTERN MESSAGEBOX:PROC ... call MessageBox PASCAL,h,ADDR TheMsg,ADDR TitleW, MB_OK
PASCAL - тип вызова, точнее порядок следования параметров. Можно поставить параметр C, тогда порядок будет обратным.