Ассемблер для Windows

         

Автоматическая компоновка



Автоматическая компоновка

. Транслятор ML.EXE обладает удобным свойством автоматического запуска компоновщика. Обычно мы игнорируем это свойство, используя ключ /c. Если не применять это ключ, то транслятор ML будет пытаться запустить программу LINK.EXE. Чтобы правильно провести всю трансляцию, необходимо еще указать опции компоновщика. Вот как будет выглядеть вся строка:

ML /coff prog.asm /LINK /subsystem:windows

He правда ли, удобно?



Часть I Основы битного программирования в Windows



Часть I. Основы 32-битного программирования в Windows



Цикл обработки очереди сообщений



Цикл обработки очереди сообщений

. Вот как выглядит этот цикл на языке Си:

while (GetMessage (&msg, NULL, 0, 0)) { // разрешить использование клавиатуры, // путем трансляции сообщений о виртуальных клавишах // в сообщения о алфавитно-цифровых клавишах TranslateMessage (&msg); // вернуть управление Windows и передать сообщение дальше // процедуре окна DispatchMessage(&msg); }

Функция GetMessage() "отлавливает" очередное сообщение из ряда сообщений данного приложения и помещает его в структуру MSG.

Что касается функции TranslateMessage, то ее компетенция касается сообщений WM_KEYDOWN и WM_KEYUP, которые транслируются в WM_CHAR и WM_DEADCHAR, а также WM_SYSKEYDOWN и WM_SYSKEYUP, преобразующиеся в WM_SYSCHAR и WM_SYSDEADCHAR. Смысл трансляции заключается не в замене, а в отправке дополнительных сообщений. Так, например, при нажатии и отпускании алфавитно-цифровой клавиши в окно сначала придет сообщение WM_KEYDOWN, затем WM_KEYUP, а затем уже WM_CHAR.

Как можно видеть, выход из цикла ожиданий имеет место только в том случае, если функция GetMessage возвращает 0. Это происходит только при получении сообщения о выходе (сообщение WM_QUIT, см. ниже). Таким образом, цикл ожидания играет двоякую роль: определенным образом преобразуются сообщения, предназначенные для какого-либо окна, и ожидается сообщение о выходе из программы.



Дизассемблеры



Дизассемблеры.

Дизассемблеры переводят исполняемый модуль в ассемблерный код. Примером простейшего дизассемблера является программа DUMPPE.EXE, работающая в строковом режиме. Пример работы программы DUMPPE.EXE представлен на Рисунок 1.1.11. Здесь дизассемблируется программа, приведенная на Рисунок 1.1.5. Ну как, узнали нашу программу? Смысл обозначений будет ясен из дальнейшего изложения.



knia.exe (hex) (dec) .EXE size (bytes) 490 1168 Minimum load size (bytes) 450 1104 Overlay number 0 0 Initial CS:IP 0000:0000 Initial SS:SP 0000:OOB8 184 Minimum allocation (para) 0 0 Maximum allocation (para) FFFF 65535 Header size (para) 4 4 Relocation table offset 40 64 Relocation entries 0 0

Portable Executable starts at a8 Signature 00004550 (РЕ) Machine 014C (Intel 386) Sections 0001 Time Date Stamp 3AE6D1B1 Wed Apr 25 19:31:29 2001

Symbol Table 00000000 Number of Symbols 00000000 Optional header size OOEO Characteristics O1OF Relocation information stripped Executable Image Line numbers stripped Local symbols stripped 32 bit word machine Magic 010B Linker Version 5.12 Size of Code 00000200 Size of Initialized Data 00000000 Size of Uninitialized Data 00000000 Address of Entry Point 00001000 Base of Code 00001000 Base of Data 00002000 Image Base 00400000 Section Alignment 00001000 File Alignment 00000200 Operating System Version 4.00 Image Version 0.00 Subsystem Version 4.00 Reserved 00000000 Image Size 00002000 Header Size 00000200 Checksum 00000000 Subsystem 0002 (Windows) DLL Characteristics 0000 Size Of Stack Reserve 00100000 Size Of Stack Commit 00001000 Size Of Heap Reserve 00100000 Size Of Heap Commit 00001000 Loader Flags 00000000 Number of Directories 00000010

Directory Name VirtAddr VirtSize ----------------------------------- -------- -------- Export 00000000 00000000 Import 00000000 00000000 Resource 00000000 00000000 Exception 00000000 00000000 Security 00000000 00000000 Base Relocation 00000000 00000000 Debug 00000000 00000000 Decription/Architecture 00000000 00000000 Machine Value (MIPS GP) 00000000 00000000 Thread Storage 00000000 00000000 Load Configuration 00000000 00000000 Bound Import 00000000 00000000 Import Address Table 00000000 00000000 Delay Import 00000000 00000000 СОМ Runtime Descriptor 00000000 00000000 (reserved) 00000000 00000000

Section Table ------------- Virtual Address 0001000 Virtual Size OOOOOE Raw Data Offset 000200 Raw Data Size 0000200 Relocation Offset 000000 Relocation Count 000 Line Number Offset 0000000 Line Number Count 000 Characteristics 0000020 Code Executable Readable

Disassembly

00401000 start: 00401000 E803000000 call fn_00401008 00401005 C3 ret 00401006 CC int 3 00401007 CC int 3 00401008 fn_00401008: 00401008 B8E8030000 mov eax,3E8h 0040100D C3 ret



Ассемблеры MASM и TASM



Глава 5. Ассемблеры MASM и TASM

В данной главе мы поговорим о двух конкурирующих продуктах MASM и TASM, их плюсах и минусах, общих чертах и различиях. Когда в конце 80-х я впервые "пересел" на "айбиэмки", первый вопрос, который я задал знающим людям, был об ассемблере. До этого я программировал на разных компьютерах, в основном имеющих весьма ограниченные ресурсы. Естественно, что основным языком на таких компьютерах был ассемблер 27. Мне дали MASM, кажется, это была вторая версия. Удивительно, но тогда на ассемблере я начал писать что-то типа баз данных. Проект мой не был закончен, но к ассемблеру я прикипел основательно. Потом мне попался Турбо Ассемблер версии 1.0. Он работал гораздо быстрее MASM. В дальнейшем мне приходилось использовать то один, то другой ассемблер. Как Вы, наверное, уже поняли, первая любовь оказалась сильнее, но теперь нам придется серьезно поговорить о том, что же все-таки предпочтительнее использовать при программировании в операционной системе Windows.

27 Одно время в образовании был широко распространен персональный компьютер "Ямаха", ОЗУ которого составляла всего 64 Кб (потом 128). Писать для такого компьютера, скажем, на языке Паскаль было, естественно, непозволительной роскошью.

Начнем со справочной информации о параметрах командной строки ML.EXE и TASM32.EXE. В начале рассмотрим транслятор ML.EXE.



Экскурс в битное программирование



Глава 4. Экскурс в 16-битное программирование

Данная глава носит уже исторический характер. Обычно при изложении исходных данных по программированию на ассемблере под Windows начинают именно с 16-битного программирования. На мой взгляд, в данный момент оно уже настолько отошло в тень, что не может служить введением в программирование под Windows25. Поскольку у Вас вряд ли возникнет потребность написания 16-битного приложения, я ограничусь одним несложным примером. Всех интересующихся отсылаю к книгам [1,7].

Начнем с того, что рассмотрим, чем отличается 16-битное программирование на ассемблере от 32-битного программирования. Вы убедитесь, что программировать стало гораздо проще.

1. В отличие от памяти в Windows 9x, модель памяти в старой системе Windows была сегментной. Соответственно, и в программе данные, стек и код относятся к разным сегментам26. Как и в операционной системе MS DOS, регистр DS будет указывать на сегмент данных, a CS на сегмент кода.

2. В 16-битной модели адресация осуществляется по схеме сегмент/смещение. Соответственно, адрес определяется двумя 16-битными компонентами. Например, чтобы поместить в стек адрес переменной М, потребуется по крайней мере две команды PUSH DS/PUSH OFFSET M. При этом сегментный адрес должен быть помещен в ячейку с большим адресом, чем смещение (стек растет в сторону меньших адресов).

3. Для создания 16-битных приложений удобнее всего пользоваться пакетом MASM версии 6.1. Поскольку данная глава — всего лишь исторический экскурс, мы не будем останавливаться на том, как делать 16-битные приложения с помощью Турбо Ассемблера. Для компоновки приложений мы будем использовать библиотеку LIBW.LIB, которая поставлялась в пакете MASM 6.1. Особенностью вызова API-функций в данном случае будет обратный порядок помещения параметров в стек, по сравнению с тем, что было до сих пор. В данной главе, и только в ней, будет действовать принцип: СЛЕВА НАПРАВО - СВЕРХУ ВНИЗ.

4. После запуска программы, должна быть выполнена начальная инициализация. Для этого требуется выполнить три функции API: INITTASK, WAITEVENT, INITAPP. Ниже дано описание этих функций.




INITTASK — инициализирует регистры, командную строку и память. Входных параметров не требует. Вызывается первой. Возвращаемые значения регистров: AX = 1 (0 — ошибка), CX — размер стека, DI — уникальный номер для данной задачи, DX — параметр NCMDSHOW (см. ниже), ES — сегментный адрес (селектор) PSP, ES:BX — адрес командной строки, SI — уникальный номер для ранее запущенного того же приложения. В Windows 3.1 при запуске приложения несколько раз каждый раз в память загружается только часть сегментов, часть сегментов является общим ресурсом. Таким методом достигалась экономия памяти. В Windows 95 от этого отказались. Каждая запущенная задача является изолированной и независимой. В Windows 95 SI всегда будет содержать 0. Кроме того, данная процедура заполняет резервный заголовок сегмента данных. WAITEVENT — проверяет наличие событий для указанного приложения. Если событие есть, то оно удаляется из очереди. Вызов:
PUSH AX ; AX — номер приложения, если 0, то текущее.

CALL WAITEVENT
INITAPP — инициализирует очередь событий для данного приложения. Вызов:
PUSH DI ; уникальный номер задачи

CALL INITAPP
В случае ошибки данная функция возвращает 0, иначе ненулевое значение.
5. Некоторые параметры API-функций 16-битного приложения имеют размер 2 байта. В частности параметры WPARAM и HWND процедуры окна также имеют размер 2 байта. С четырехбайтными же параметрами приходится работать в два приема.
6. Наконец последнее отличие: сегмент данных в начале должен содержать резервный блок размером в 16 байт.
7. Интересно, что в 16-битном приложении мы можем пользоваться обычными прерываниями MS DOS, используя INT 21H. Надо только иметь в виду, какие функции прерывания имеют смысл для Windows.
25 Как Вы убедитесь, 16-битное программирование даже несколько сложнее 32-битного.
26 Надеюсь, вы помните, что разделение данных и кода на сегменты в 32-битной модели — вещь условная.
Для иллюстрации сказанного я привожу программу из моей книги [1], которую я незначительно изменил. Трансляция программы производится средствами MASM 6.1:


ML /c prog.asm LINK prog,prog,,libw (вопрос о файле .def можно проигнорировать)
.286 .DOSSEG ; порядок сегментов согласно соглашению Microsoft DGROUP GROUP DATA, STA ASSUME CS: CODE, DS: DGROUP
;прототипы внешних процедур EXTRN INITTASK:FAR EXTRN INITAPP:FAR EXTRN WAITEVENT:FAR EXTRN DOS3CALL:FAR EXTRN REGISTERCLASS:FAR EXTRN LOADCURSOR:FAR EXTRN GETSTOCKOBJECT:FAR EXTRN GETMESSAGE:FAR EXTRN TRANSLATEMESSAGE:FAR EXTRN DISPATCHMESSAGE:FAR EXTRN CREATEWINDOW:FAR EXTRN CREATEWINDOWEX:FAR EXTRN UPDATEWINDOW:FAR EXTRN SHOWWINDOW:FAR EXTRN POSTQUITMESSAGE:FAR EXTRN DEFWINDOWPROC:FAR
; шаблоны
WNDCL STRUCT STYLE DW 0 ; стиль класса окна LPFNWNDPROC DD 0 ; указатель на процедуру обработки CBCLSEXTRA DW 0 CBWNDEXTRA DW 0 HINSTANCE DW 0 HICON DW 0 HCURSOR DW 0 HBRBACKGROUND DW 0 LPSZMENUNAME DD 0 ; указатель на строку LPSZCLASSNAME DD 0 ; указатель на строку WNDCL ENDS ; MESSA STRUCT HWND DW ? MESSAGE DW ? WPARAM DW ? LPARAM DD ? TIME DW ? X DW ? Y DW ? MESSA ENDS
; сегмент стека STA SEGMENT STACK 'STACK' DW 2000 DUP(?) STA ENDS
; сегмент данных DATA SEGMENT WORD 'DATA' ; в начале 16 байт — резерв, необходимый 16-ти битному ; приложению для правильной работы в среде Windows DWORD 0 WORD 5 WORD 5 DUP (0) HPREV DW ? HINST DW ? LPSZCMD DD ? CMDSHOW DW ? ; структура для создания класса WNDCLASS WNDCL <> ; структура сообщения MSG MESSA <> ;имя класса окна CLAS_NAME DB 'PRIVET',0 ; заголовок окна APP_NAME DB '16-битная программа',0 ; тип курсора CURSOR EQU 00007F00H ; стиль окна STYLE EQU 000CF0000H ; параметры окна XSTART DW 100 YSTART DW 100 DXCLIENT DW 300 DYCLIENT DW 200 DATA ENDS
; сегмент кода CODE SEGMENT WORD 'CODE' _BEGIN: ; I. Начальный код CALL INITTASK ; инициализировать задачу OR AX, AX ; CX - границы стека (!!! CX или AX ????) JZ _ERR MOV HPREV, SI ; номер предыдущего прил. MOV HINST, DI ; номер для новой задачи MOV WORD PTR LPSZCMD, BX ; ES:BX - адрес MOV WORD PTR LPSZCMD+2,ES ; командной строки MOV CMDSHOW, DX ; экранный параметр PUSH 0 ; текущая задача CALL WAITEVENT ; очистить очередь событий PUSH HINST CALL INITAPP ; инициализировать приложения OR AX, AX JZ _ERR CALL MAIN ; запуск основной части _TO_OS: MOV AH,4CH INT 21H ; выйти из программы _ERR: ; здесь можно поставить сообщение об ошибке JMP SHORT _TO_OS ; основная процедура ;************************************************************ MAIN PROC ; II. Регистрация класса окна ; стиль окна NULL — стандартное окно MOV WNDCLASS.STYLE, 0 ; процедура обработки LEA BX,WNDPROC MOV WORD PTR WNDCLASS.LPFNWNDPROC, BX MOV BX,CS MOV WORD PTR WNDCLASS.LPFNWNDPROC+2, BX ;------------------------------------------------------------ ; резервные байты в конце резервируемой структуры MOV WNDCLASS.CBCLSEXTRA, 0 ; резервные байты в конце структуры для каждого окна MOV WNDCLASS.CBWNDEXTRA, 0 ; иконка окна отсутствует MOV WNDCLASS.HICON, 0 ; номер запускаемой задачи MOV AX, HINST MOV WNDCLASS.HINSTANCE,AX ; определить номер стандартного курсора PUSH 0 PUSH DS PUSH CURSOR CALL LOADCURSOR MOV WNDCLASS.HCURSOR, AX ; определить номер стандартного объекта PUSH 0 ; WHITE_BRUSH CALL GETSTOCKOBJECT ; цвет фона MOV WNDCLASS.HBRBACKGROUND, AX ; имя меню из файла ресурсов (отсутствует = NULL) MOV WORD PTR WNDCLASS.LPSZMENUNAME, 0 MOV WORD PTR WNDCLASS.LPSZMENUNAME+2,0 ; указатель на строку, содержащую имя класса LEA BX,CLAS_NAME MOV WORD PTR WNDCLASS.LPSZCLASSNAME,BX MOV WORD PTR WNDCLASS.LPSZCLASSNAME+2,DS ; вызов процедуры регистрации PUSH DS ; указатель на LEA DI,WNDCLASS PUSH DI ; структуры WNDCLASS CALL REGISTERCLASS CMP AX,0 JNZ _OK1 ; ошибка регистрации RET ; ошибка при регистрации _OK1: ; III. Создание окна ; адрес строки-имени класса окна PUSH DS LEA BX,CLAS_NAME PUSH BX ; адрес строки-заголовка окна PUSH DS LEA BX,APP_NAME PUSH BX ; стиль окна MOV BX,HIGHWORD STYLE PUSH BX MOV BX,LOWWORD STYLE PUSH BX ; координата X левого верхнего угла PUSH XSTART ; координата Y левого верхнего угла PUSH YSTART ; ширина окна PUSH DXCLIENT ; высота окна PUSH DYCLIENT ; номер окна-родителя PUSH 0 ; номер (идентификатор) меню окна PUSH 0 ; NULL ; номер задачи PUSH HINST ; адрес блока параметров окна (нет) PUSH 0 PUSH 0 CALL CREATEWINDOW CMP AX,0 JNZ NO_NULL ; ошибка создания окна RET ; ошибка при создании окна ; установка для окна состояния видимости ; (окно или пиктограмма) ; согласно параметру CMDSHOW ; и его отображение NO_NULL: MOV SI,AX PUSH SI PUSH CMDSHOW CALL SHOWWINDOW ; посылка команды обновления области окна (команда WM_PAINT) ; сообщение посылается непосредственно окну PUSH SI CALL UPDATEWINDOW ; IV. Цикл ожидания LOOP1: ; извлечение сообщения из очереди PUSH DS LEA BX,MSG ; указатель на структуру PUSH BX ; сообщения PUSH 0 PUSH 0 PUSH 0 CALL GETMESSAGE ; проверка — не получено сообщение "выход" CMP AX,0 JZ NO_LOOP1 ; перевод всех пришедших сообщений к стандарту ANSI PUSH DS LEA BX,MSG PUSH BX CALL TRANSLATEMESSAGE ; указать WINDOWS ; передать данное сообщение соответствующему окну PUSH DS LEA BX,MSG PUSH BX CALL DISPATCHMESSAGE ; замкнуть цикл (петлю) JMP SHORT LOOP1 NO_LOOP1: RET MAIN ENDP


; процедура для заданного класса окон ; WINDOWS передает в эту процедуру параметры: ; HWND - дескриптор (номер) окна, тип WORD ; MES - номер сообщения, тип WORD ; WPARAM - дополнительная информация, тип WORD ; LPARAM - дополнительная информация, тип DWORD WNDPROC PROC PUSH BP MOV BP,SP MOV AX, [BP+0CH] ; MES - номер сообщения CMP AX, 2 ; не сообщение ли о закрытии WM_DESTROY JNZ NEXT ; передать сообщение о закрытии приложения, ; это сообщение будет принято в цикле ожидания, ; и т.о. приложение завершит свой путь PUSH 0 CALL POSTQUITMESSAGE JMP _QUIT NEXT: ; передать сообщение дальше WINDOWS ; своего рода правило вежливости — то, ; что не обработано процедурой обработки, ; предоставляется для обработки WINDOWS PUSH [BP+0EH] ; HWND PUSH [BP+0CH] ; MES - номер сообщения PUSH [BP+0AH] ; WPARAM PUSH [BP+8] ; HIGHWORD LPARAM PUSH [BP+6] ; LOWWORD LPARAM CALL DEFWINDOWPROC ;************************************************************ _QUIT: POP BP ; вызов процедуры окна всегда дальний, поэтому RETF RETF 10 ; освобождаем стек от параметров WNDPROC ENDP CODE ENDS END _BEGIN

Основы программирования в операционной системе Windows



Глава 2. Основы программирования в операционной системе Windows

В данной главе я намерен рассмотреть два момента, которые крайне важны для начала программирования на ассемблере в среде Windows - это вызов системных функций (API-функций) и возможные структуры программ 15 для Windows. Я полагаю, что можно выделить три типа структуры программ, которые условно можно назвать как классическая16 структура, диалоговая (основное окно — диалоговое), консольная, или безоконная17, структура. В данной главе подробно описывается первая, классическая структура.

Итак, начнем с нескольких общих положений о программировании в Windows. Те, кто уже имеет опыт программирования в среде Windows, могут на этом не останавливаться.

Программирование в Windows основывается на использовании функций API (Application Program Interface, т.е. интерфейс программного приложения). Их количество достигает двух тысяч. Ваша программа в значительной степени будет состоять из таких вызовов. Все взаимодействие с внешними устройствами и ресурсами операционной системы будет происходить посредством таких функций. Список функций API и их описание лучше всего брать из файла WIN32.HLP, который поставляется, например, с пакетом Borland C++. Главным элементом программы в среде Windows является окно. Для каждого окна определяется своя процедура18 обработки сообщений (см. ниже). Окно может содержать элементы управления: кнопки, списки, окна редактирования и др. Эти элементы, по сути, также являются окнами, но обладающими особыми свойствами. События, происходящие с этими элементами (и самим окном), приводят к приходу сообщений в процедуру окна. Операционная система Windows использует линейную модель памяти. Другими словами, всю память можно рассматривать как один сегмент. Для программиста на языке ассемблера это означает, что адрес любой ячейки памяти будет определяться содержимым одного 32-битного регистра, например EBX. Следствием пункта 5 является то, что мы фактически не ограничены в объеме данных, кода или стека (объеме локальных переменных). Выделение в тексте программы сегмента кода и сегмента данных является теперь простой формальностью, улучшающей читаемость программы. Операционная система Windows является многозадачной средой. Каждая задача имеет свое адресное пространство и свою очередь сообщений. Более того, даже в рамках одной программы может быть осуществлена многозадачность - любая процедура может быть запущена как самостоятельная задача.

Итак, после теоретических положений самое время перейти к программным примерам.

15 Не путать со структурой загружаемых модулей.

16 Классификация автора.

17 Как потом станет ясно, консольное приложение вполне может иметь диалоговое окно.

18 Исходя из терминологии, принятой в MS DOS, такую процедуру следует назвать "процедурой прерывания". Для Windows же принята другая терминология. Подобные процедуры, вызываемые самой системой, называются процедурами обратного вызова (CALLBACK).



Примеры простых программ на языке ассемблера



Глава 3. Примеры простых программ на языке ассемблера

Данная глава посвящена простым примерам, демонстрирующим технику создания окон и других оконных элементов, которая была изложена в предыдущей главе.



Int MessageBox



I

Начнем с того, как можно вызвать функции API. Обратимся к файлу помощи и выберем любую функцию API, например, MessageBox:

int MessageBox ( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType );

Данная функция выводит на экран окно с сообщением и кнопкой (или кнопками) выхода. Смысл параметров: hWnd -дескриптор окна, в котором будет появляться окно-сообщение, lpText - текст, который будет появляться в окне, lpCaption - текст в заголовке окна, uType - тип окна, в частности можно определить количество кнопок выхода. Теперь о типах параметров. Все они в действительности 32-битные целые числа: HWND — 32-битное целое, LPCTSTR — 32-битный указатель на строку, UINT — 32-битное целое. По причине, о которой будет сказано ниже, к имени функций нам придется добавлять суффикс "А", кроме того, при использовании MASM необходимо также в конце имени добавить @16. Таким образом, вызов указанной функции будет выглядеть так: CALL MessageBoxA@16. А как же быть с параметрами? Их следует аккуратно поместить в стек. Запомните правило: СЛЕВА НАПРАВО — СНИЗУ ВВЕРХ. Итак, пусть дескриптор окна расположен по адресу HW, строки — по адресам STR1 и STR2, а тип окна-сообщения — это константа. Самый простой тип имеет значение 0 и называется МВ_ОК. Имеем следующее:

МВ_ОК equ 0 . . STR1 DB "Неверный ввод! ",0 STR2 DB "Сообщение об ошибке.",0 HW DWORD ? . . PUSH МВ_ОК PUSH OFFSET STR1 PUSH OFFSET STR2 PUSH HW CALL MessageBoxA@16

Как видите, все весьма просто и ничуть не сложнее, как если бы Вы вызывали эту функцию на Си или Delphi. Результат выполнения любой функции — это, как правило, целое число, которое возвращается в регистре EAX.

Аналогичным образом в ассемблере легко воспроизвести те или иные Си-структуры. Рассмотрим, например, структуру, определяющую системное сообщение:

typedef struct tagMSG { // msg HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG;

Это сообщение будет далее подробно прокомментировано в одном из примеров. На ассемблере эта структура будет иметь вид:




MSGSTRUCT STRUC MSHWND DD ? MSMESSAGE DD ? MSWPARAM DD ? MSLPARAM DD ? MSTIME DD ? MSPT DD ? MSGSTRUCT ENDS

Как видите, на ассемблере все даже гораздо проще. Вообще трудно не отклониться от темы и еще раз не удивиться фирме Микрософт, все так запутавшей в отношении типов переменных.

Теперь обратимся к структуре всей программы. Как я уже говорил, в данной главе мы будем рассматривать классическую структуру программы под Windows. В такой программе имеется главное окно, а следовательно, и процедура главного окна. В целом, в коде программы можно выделить следующие секции:





Регистрация класса окон

Создание главного окна

Цикл обработки очереди сообщений

Процедура главного окна

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


Компиляторы ресурсов



Компиляторы ресурсов

В пакетах MASM32 и TASM32 есть компиляторы ресурсов, которые будут описаны ниже. Это программы RC.EXE и BRC32.EXE соответственно.



Краткий обзор ряда других программ



VI

В заключительной части главы я хотел дать краткий обзор ряда других программ, которые часто используются при программировании на ассемблере. С некоторыми из перечисленных программ мы познакомимся в дальнейшем более подробно, а некоторые более упоминаться не будут.



Модуль использующий переменную ALT определенную в другом модуле (PROG ASM)



Рисунок 1.1.9. Модуль, использующий переменную ALT, определенную в другом модуле (PROG2.ASM).

Заметим, что в отличие от внешних процедур, внешняя переменная не требует добавки @N, поскольку размер переменной известен.

9 Термин "внешняя переменная" используется нами по аналогии с термином "внешняя процедура".



Модуль PROG ASM процедура которого PROC будет вызываться из основного модуля



Рисунок 1.1.4. Модуль PROG2.ASM, процедура которого PROC1 будет вызываться из основного модуля.

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

Второе, на что я хотел бы обратить Ваше внимание, это то, что процедура, которая будет вызываться, должна быть объявлена как PUBLIC. В этом случае это имя будет сохранено в объектном модуле и далее может быть связано с вызовами из других модулей.

Итак, выполняем команду ML /coff /c PROG1.ASM. В результате на диске появляется объектный модуль PROG2.OBJ.

А теперь проведем маленькое исследование. Просмотрим объектный модуль с помощью какого-нибудь простого Viewer'a, например того, что есть у программы Far. И что же мы обнаружим: вместо имени PROC1 мы увидим имя _PROC1@0. Это особый разговор — будьте сейчас внимательны! Во-первых, подчеркивание спереди отражает стандарт ANSI, предписывающий всем внешним именам (доступным нескольким модулям) автоматически добавлять символ подчеркивания. Здесь ассемблер будет действовать автоматически, и у нас по этому поводу не будет никаких забот.

Сложнее с припиской @0. Что она значит? На самом деле все просто: цифра после знака @ означает количество байт, которые необходимо передать в стек в виде параметров при вызове процедуры. В данном случае ассемблер понял так, что наша процедура параметров не требует. Сделано это для удобства использования директивы INVOKE. Но о ней речь пойдет ниже, а пока попытаемся сконструировать основной модуль PROG1.ASM.

.386P ; плоская модель .MODEL FLAT, STDCALL ;-------------------------------------------------- ; прототип внешней процедуры EXTERN PROC1@0:NEAR

; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' _DATA ENDS ; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: CALL PROC1@0 RET ; выход _TEXT ENDS END START



Модуль PROG ASM с вызовам процедуры из модуля PROG ASM



Рисунок 1.1.5. Модуль PROG1.ASM с вызовам процедуры из модуля PROG2.ASM.

Как Вы понимаете, процедура, вызываемая из другого модуля, объявляется как EXTERN. Далее, вместо имени PROC1 нам приходится использовать имя PROC1@0. Здесь пока ничего нельзя сделать. Может возникнуть вопрос о типе NEAR. Дело в том, что в операционной системе MS DOS тип NEAR означал, что вызов процедуры (или безусловный переход) будет происходить в пределах одного сегмента. Тип FAR означал, что процедура (или переход) будет вызываться из другого сегмента. В операционной системе Windows реализована так называемая плоская модель, когда всю память можно рассматривать как один большой сегмент. И здесь логично использовать тип NEAR.

Выполним команду ML /coff /c PROG1.ASM, в результате получим объектный модуль PROG1.OBJ. Теперь можно объединить модули и получить загружаемую программу PROG1.EXE:

LINK /SUBSYSTEM:WINDOWS PROG1.OBJ PROG2.OBJ

При объединении нескольких модулей первым должен идти главный, а остальные — в произвольном порядке.



Модуль содержащий переменную ALT которая используется в другом модуле (PROG ASM)



Рисунок 1.1.8. Модуль, содержащий переменную ALT, которая используется в другом модуле (PROG1.ASM).

.386P

; модуль PROG1.ASM ; плоская модель .MODEL FLAT, STDCALL ;----------------------------------- ; прототип внешней процедуры EXTERN PROC1@0:NEAR ; внешняя переменная EXTERN ALT: DWORD ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' _DATA ENDS ; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: MOV ALT, 10 CALL PROC1@0 MOV EAX, ALT RET ; выход _TEXT ENDS END START



"Ничего не делающая" программа



Рисунок 1.1.2. "Ничего не делающая" программа.

На Рисунок 1.1.2 представлена "ничего не делающая" программа. Назовем ее PROG1. Сразу отмечу на будущее: команды микропроцессора и директивы макроассемблера будем писать заглавными буквами.

Итак, чтобы получить загружаемый модуль, выполним следующие команды:

ML /c /coff PROG1.ASM LINK /SUBSYSTEM:WINDOWS PROG1

или для Турбо Ассемблера

TASM32 /ml PROG1.ASM TLINK32 -аа PROG1.OBJ

Примем пока параметры трансляции программ как некую данность и продолжим наши изыскания.

Часто удобно разбить текст программы на несколько частей и объединять эти части еще на 1-й стадии трансляции. Это достигается посредством директивы INCLUDE. Например, один файл будет содержать код программы, а константы, данные (определение переменных) и прототипы внешних процедур помещаются в отдельные файлы. Часто такие файлы записывают с расширением .INC.

Именно такая разбивка демонстрируется в следующей программе (Рисунок 1.1.3).

;файл CONS.INC CONS1 EQU 1000 CONS2 EQU 2000 CONS3 EQU 3000 CONS4 EQU 4000 CONS5 EQU 5000 CONS6 EQU 6000 CONS7 EQU 7000 CONS8 EQU 8000 CONS9 EQU 9000 CONS10 EQU 10000 CONS11 EQU 11000 CONS12 EQU 12000

;файл DAT.INC DAT1 DWORD 0 DAT2 DWORD 0 DAT3 DWORD 0 DAT4 DWORD 0 DAT5 DWORD 0 DAT6 DWORD 0 DAT7 DWORD 0 DAT8 DWORD 0 DAT9 DWORD 0 DAT10 DWORD 0 DAT11 DWORD 0 DAT12 DWORD 0

;файл PROG1.ASM .386P ;плоская модель .MODEL FLAT, STDCALL ;подключить файл констант INCLUDE CONS.INC ;-------------------------------------------------- ;сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' ;подключить файл данных INCLUDE DAT.INC _DATA ENDS ;сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: MOV EAX,CONS1 SHL EAX,1 ; умножение на 2 MOV DAT1,EAX ;----------------------------------- MOV EAX,CONS2 SHL EAX,2 ; умножение на 4 MOV DAT2,EAX ;----------------------------------- MOV EAX, CONS3 ADD EAX, 1000 ; прибавим 1000 MOV DAT3, EAX ;----------------------------------- MOV EAX, CONS4 ADD EAX, 2000 ; прибавим 2000 MOV DAT4, EAX ;----------------------------------- MOV EAX, CONS5 SUB EAX, 3000 ; вычесть 3000 MOV DAT5, EAX ;----------------------------------- MOV EAX, CONS6 SUB EAX, 4000 ; вычесть 4000 MOV DAT6, EAX ;----------------------------------- MOV EAX, CONS7 MOV EDX, 3 IMUL EDX ; умножение на 3 MOV DAT7, EAX ;----------------------------------- MOV EAX, CONS8 MOV EDX, 7 ; умножение на 7 IMUL EDX MOV DAT8, EAX ;----------------------------------- MOV EAX, CONS9 MOV EBX, 3 ; деление на 3 MOV EDX, 0 IDIV EBX MOV DAT9, EAX ;----------------------------------- MOV EAX, CONS10 MOV EBX, 7 ; деление на 7 MOV EDX, 0 IDIV EBX MOV DAT10, EAX ;----------------------------------- MOV EAX, CONS11 SHR EAX, 1 ; деление на 2 MOV DAT11, EAX ;----------------------------------- MOV EAX, CONS12 SHR EAX, 2 ; деление на 4 MOV DAT12, EAX ;----------------------------------- RET ; выход _TEXT ENDS END START



Объединение модулей



V

А теперь проверим все представленные в данной главе программы на предмет их трансляции средствами пакета TASM.

С программами на Рисунок 1.2-1.3 дело обстоит просто. Для их трансляции достаточно выполнить команды:

TASM32 /ml PROG1.ASM TLINK32 -аа PROG1.OBJ

Обратимся теперь к модулям PROG2.ASM и PROG1.ASM, приведенным на Рисунок 1.4 и 1.5 соответственно.

Получение объектных модулей происходит без каких-либо трудностей. Просматривая модуль PROG2.OBJ, мы увидим, что внешняя процедура представлена просто именем PROC1.

Следовательно, единственное, что нам следует сделать, это заменить в модуле PROC1.ASM имя PROC1@0 на PROC1.

Объединение модулей далее производится элементарно:

TLINK32 -аа PROG1.OBJ PROG2.OBJ

Для работы с библиотекой в пакете TASM имеется программа — библиотекарь TLIB.ЕХЕ. Создание библиотеки, состоящей из модуля PROG2.OBJ, производится по команде

TLIB LIB1.LIB+PROG2.OBJ

В результате на диске появится библиотека LIB1.LIB. Далее компонуем модуль PROG1.OBJ с этой библиотекой:

TLINK32 -аа PROG1,PROG1,PROG1,LIB1

В результате получается загружаемый модуль PROG1.EXE.

Вообще, стоит разобраться с командной строкой TLINK32 более подробно. В расширенном виде11 эта строка выглядит следующим образом:

TLINK32 -аа OBJFILES, EXEFILE, MAPFILE, LIBFILES

OBJFILES - один или несколько объектных файлов (через пробел). Первый главный модуль.

EXEFILE - исполняемый модуль.

MAPFILE - МАР-файл, содержащий информацию о структуре модуля.

LIBFILES - одна или несколько библиотек (через пробел).

В TASM отсутствует директива INVOKE, поэтому в дальнейшем я буду избегать ее использования12.

В начале книги я объявил о своем намерении примирить два ассемблера. Поскольку различие между ними заключается в директивах и макрокомандах (см. Гл.1.5), то напрашивается вывод, что совместимости можно добиться, избегая таких директив и макрокоманд. Основой программы для Windows является вызов API-функций (см. Гл. 1.2), а мы знаем, что различие в вызове внешней процедуры заключается в том, что в именах для MASM в конце есть добавка @N. И здесь не обойтись без макроопределений, и начинается самое интересное. Но об этом, дорогой читатель, Вы узнаете в свое время.




И MASM и TASM поддерживают так называемую упрощенную сегментацию. Я являюсь приверженцем классической структуры ассемблерной программы и должен признаться, что упрощенная сегментация довольно удобная штука, особенно при программировании под Windows. Суть такой сегментации в следующем: начало сегмента определяется директивой .CODE, а сегмента данных - .DATA13. Причем обе директивы могут появляться в тексте программы несколько раз. Транслятор затем собирает код и данные вместе, как положено. Основной целью такого подхода, по-видимому, является возможность приблизить в тексте программы данные к тем строкам, где они используются. Такая возможность, как известно, в свое время была реализована в C++. На мой взгляд, она приводит к определенному неудобству при чтении текста программы. Кроме того, не сочтите меня за эстета, но когда я вижу данные, перемешанные в тексте программы с кодом, у меня возникает чувство дискомфорта.

Ниже представлена программа, демонстрирующая упрощенный режим сегментации.

.386P ; плоская модель .MODEL FLAT, STDCALL ;-------------------------------------------------- ; сегмент данных .DATA SUM DWORD 0 ; сегмент кода .CODE START: ; сегмент данных .DATA A DWORD 100 ; сегмент кода .CODE MOV EAX,A ; сегмент данных .DATA B DWORD 200 ; сегмент кода .CODE ADD EAX,B MOV SUM,EAX RET ; выход END START


Обратимся теперь к директиве INVOKE



III

Обратимся теперь к директиве INVOKE. Это довольно удобная команда, правда, по некоторым причинам (которые станут понятными позже) я почти не буду употреблять ее в своих программах.

Удобство ее заключается, во-первых, в том, что мы сможем забыть о добавке @N. Во-вторых, эта команда сама заботится о помещении передаваемых параметров в стек. Последовательность команд

PUSH par1 PUSH par2 PUSH par3 PUSH par4 CALL NAME_PROC@N ; N-количество отправляемых в стек байт

заменяется на

INVOKE NAME_PROC, par4, par3, par2, par1

Причем параметрами могут являться регистр, непосредственно значение или адрес. Кроме того, для адреса может использоваться как оператор OFFSET, так и оператор ADDR. Видоизменим теперь модуль PROG1.ASM (модуль PROG2.ASM изменять не придется).

.386P ; плоская модель .MODEL FLAT, STDCALL ;----------------------------------- ; прототип внешней процедуры PROC1 PROTO ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' _DATA ENDS ; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: INVOKE PROC1 RET ; выход _TEXT ENDS END START



Окно TD EXE с отлаживаемой программой



Рисунок 1.5.1. Окно TD32.EXE с отлаживаемой программой.




Отладчики



Отладчики.

Отладчики позволяют исполнять программу в пошаговом режиме. В IV части книги мы более подробно будем рассматривать отладчики и дизассемблеры. Приведу несколько наиболее известных отладчиков14: CodeView (Микрософт), Turbo Debugger (Borland), Ice.



Перейдем теперь к вопросу о подсоединении других объектных модулей



II

Перейдем теперь к вопросу о подсоединении других объектных модулей и библиотек во второй стадии трансляции. Прежде всего замечу, что, сколько бы ни подсоединялось объектных модулей, один объектный модуль является главным. Смысл этого весьма прост: именно с этого модуля начинается исполнение программы. На этом различие между модулями заканчивается. Условимся далее, что главный модуль всегда в начале сегмента кода будет содержать метку START, ее мы указываем после директивы END - транслятор должен знать точку входа программы, чтобы указать ее в заголовке загружаемого модуля (см. Гл.5.1).

Обычно во второстепенные модули помещаются процедуры, которые будут вызываться из основного и других модулей. Рассмотрим такой модуль. Этот модуль Вы можете видеть на Рисунок 1.4.

.386P ;модуль PROG2.ASM ;плоская модель .MODEL FLAT, STDCALL PUBLIC PROC1 _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' PROC1 PROC MOV EAX, 1000 RET PROC1 ENDP _TEXT ENDS END



Получение консольных и GUI приложений



Получение консольных и GUI приложений

. О консольных приложениях речь еще впереди, здесь же я буду краток. Консольные приложения - это приложения, работающие с текстовым экраном, при этом они являются полнокровными 32- битными программами. О структуре консольных программ речь пойдет ниже, сейчас же заметим, что для получения консольного приложения с помощью TLINK32.EXE вместо ключа -aa следует использовать -ар. Что касается компоновщика из пакета MASM32, то здесь следует использовать ключ /subsystem:console вместо /subsystem:windows.



После разбора программы



III

После разбора программы на Рисунок 1.2.2 возникает вопрос по поводу ее реализации на ассемблере TASM. В действительности здесь требуются минимальные изменения: вместо библиотек user32.lib и kernel32.lib надо подключить библиотеку import32.lib, удалить во всех именах библиотечных процедур @N и далее выполнить команды tasm32 /ml prog.asm и tlink32 -aa prog.obj.

.386P ; плоская модель MODEL FLAT, stdcall ; константы ; сообщение приходит при закрытии окна WM_DESTROY equ 2 ; сообщение приходит при создании окна WM_CREATE equ 1 ; сообщение при щелчке левой кнопкой мыши в области окна WM_LBUTTONDOWN equ 201h ; сообщение при щелчке правой кнопкой мыши в области окна WM_RBUTTONDOWN equ 204h ; свойства окна CS_VREDRAW equ 1h CS_HREDRAW equ 2h CS_GLOBALCLASS equ 4000h WS_OVERLAPPEDWINDOW equ 000CF0000H style equ CS_HREDRAW+CS_VREDRAW+CS_GLOBALCLASS ; идентификатор стандартной иконки IDI_APPLICATION equ 32512 ; идентификатор курсора IDC_CROSS equ 32515 ; режим показа окна — нормальный SW_SHOWNORMAL equ 1 ; прототипы внешних процедур EXTERN MessageBoxA:NEAR EXTERN CreateWindowExA:NEAR EXTERN DefWindowProcA:NEAR EXTERN DispatchMessageA:NEAR EXTERN ExitProcess:NEAR EXTERN GetMessageA:NEAR EXTERN GetModuleHandleA:NEAR EXTERN LoadCursorA:NEAR EXTERN LoadIconA:NEAR EXTERN PostQuitMessage:NEAR EXTERN RegisterClassA:NEAR EXTERN ShowWindow:NEAR EXTERN TranslateMessage:NEAR EXTERN UpdateWindow:NEAR ; директивы компоновщику для подключения библиотек includelib c:\tasm32\lib\import32.lib ;-------------------------------------------------- ; структуры ; структура сообщения MSGSTRUCT STRUC MSHWND DD ? ; идентификатор окна, ; получающего сообщение MSMESSAGE DD ? ; идентификатор сообщения MSWPARAM DD ? ; доп. информация о сообщении MSLPARAM DD ? ; доп. информация о сообщении MSTIME DD ? ; время посылки сообщения MSPT DD ? ; положение курсора, во время посылки сообщения MSGSTRUCT ENDS ; --------- WNDCLASS STRUC CLSSTYLE DD ? ; стиль окна CLWNDPROC DD ? ; указатель на процедуру окна CLSCEXTRA DD ? ; информация о доп. байтах для ; данной структуры CLWNDEXTRA DD ? ; информация о доп. байтах для окна CLSHINSTANCE DD ? ; дескриптор приложения CLSHICON DD ? ; идентификатор иконы окна CLSHCURSOR DD ? ; идентификатор курсора окна CLBKGROUND DD ? ; идентификатор кисти окна CLMENUNAME DD ? ; имя-идентификатор меню CLNAME DD ? ; специфицирует имя класса окон WNDCLASS ENDS




; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' NEWHWND DD 0 MSG MSGSTRUCT <?> WC WNDCLASS <?> HINST DD 0 ; здесь хранится дескриптор приложения TITLENAME DB 'Простой пример 32-битного приложения',0 CLASSNAME DB 'CLASS32',0 CAP DB 'Сообщение',0 MES1 DB 'Вы нажали левую кнопку мыши',0 MES2 DB 'Выход из программы. Пока!',0 _DATA ENDS

; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA MOV [HINST], EAX REG_CLASS: ; заполнить структуру окна стиль MOV [WC.CLSSTYLE], style ; процедура обработки сообщений MOV [WC.CLWNDPROC], OFFSET WNDPROC MOV [WC.CLSCSEXTRA], 0 MOV [WC.CLWNDEXTRA], 0 MOV EAX, [HINST] MOV [WC.CLSHINSTANCE], EAX ; ---------- иконка окна PUSH IDI_APPLICATION PUSH 0 CALL LoadIconA MOV [WC.CLSHICON], EAX ;----------- курсор окна PUSH IDC_CROSS PUSH 0 CALL LoadCursorA MOV [WC.CLSHCURSOR], EAX ; ---------- MOV [WC.CLBKGROUND], 17 ; цвет окна MOV DWORD PTR [WC.CLMENUNAME], 0 MOV DWORD PTR [WC.CLNAME], OFFSET CLASSNAME PUSH OFFSET WC CALL RegisterClassA ; создать окно зарегистрированного класса 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 TITLENAME ; имя окна PUSH OFFSET CLASSNAME ; имя класса PUSH 0 CALL CreateWindowExA ; проверка на ошибку CMP EAX, 0 JZ _ERR MOV [NEWHWND], EAX PUSH SW_SHOWNORMAL PUSH [NEWHWND] CALL ShowWindow ; показать созданное окно PUSH [NEWHWND] CALL UpdateWindow ; команда перерисовать видимую ; часть окна, сообщение WM_PAINT

; петля обработки сообщений MSG_LOOP: PUSH 0 PUSH 0 PUSH 0 PUSH OFFSET MSG CALL GetMessageA CMP EAX, 0 JE END_LOOP PUSH OFFSET MSG CALL TranslateMessage PUSH OFFSET MSG CALL DispatchMessageA JMP MSG_LOOP END_LOOP:

; выход из программы (закрыть процесс) PUSH [MSG.MSWPARAM] CALL ExitProcess _ERR: JMP END_LOOP ;-------------------------------------------------- ; процедура окна ; расположение параметров в стеке ; [EBP+014Н] LPARAM ; [EBP+10H] WAPARAM ; [EBP+0СН] 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_LBUTTONDOWN ; левая кнопка JE LBUTTON CMP DWORD PTR [EBP+0CH], WM_RBUTTONDOWN ; правая кнопка JE RBUTTON JMP DEFWNDPROC ; нажатие правой кнопки приводит к закрытию окна RBUTTON: JMP WMDESTROY ; нажатие левой кнопки мыши LBUTTON: ; выводим сообщение PUSH 0 ; МВ_ОК PUSH OFFSET CAP PUSH OFFSET MES1 PUSH DWORD PTR [EBP+08H] CALL MessageBoxA 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 JMP FINISH WMDESTROY: PUSH 0 ; MB_OK PUSH OFFSET CAP PUSH OFFSET MES2 PUSH DWORD PTR [EBP+08H] ; дескриптор окна CALL MessageBoxA PUSH 0 CALL PostQuitMessage ; сообщение WM_QUIT MOV EAX, 0 FINISH: POP EDI POP ESI POP EBX POP EBP RET 16 WNDPROC ENDP _TEXT ENDS END START


Пример битного приложения



Рисунок 1.4.1. Пример 16-битного приложения.

Вот и все. Сделаю еще замечание по поводу директивы .DOSSEG. Эта директива задает определенный порядок сегментов в ЕХЕ-файле. Этот порядок таков: в начале идет сегмент класса CODE, далее идут сегменты, отличные от класса CODE и не входящие в группу сегментов (GROUP), и наконец — сегменты, сгруппированные при помощи директивы GROUP. Причем в начале идут сегменты, отличные от класса BSS и класса STACK, далее — сегменты класса BSS (если есть) и последний сегмент класса STACK.

Попрощаемся с 16-битным программированием. Более в книге мы с ним не встретимся. Согласитесь теперь, что с точки зрения программирования, Windows 95 была несомненным шагом вперед. Программировать на ассемблере стало еще проще.



Пример дизассемблированш программы



Нех-редакторы

Нех-редакторы позволяют просматривать и редактировать загружаемые модули в шестнадцатеричном виде. Их великое множество, к тому же отладчики и дизассемблеры, как правило, имеют встроенные НЕХ-редакторы. Отмечу только, весьма популярную в хакерских кругах программу HIEW.EXE. Эта программа позволяет просматривать загружаемые модули как в шестнадцатеричном виде, так и в виде ассемблерного кода. И не только просматривать, но и редактировать.



Пример использование директивы INCLUDE



Рисунок 1.1.3. Пример использование директивы INCLUDE.

Программа на Рисунок 1.1.3 также достаточно бессмысленна (как и все программы данной главы), но зато демонстрирует удобства использования директивы INCLUDE. Напомню, что мы не останавливаемся в книге на очевидных командах микропроцессора. Замечу только по поводу команды IDIV. В данном случае команда IDIV осуществляет операцию деления над операндом, находящемся в паре регистров EDX:EAX. Обнуляя EDX, мы указываем, что операнд целиком находится в регистре EAX.

Трансляция программы осуществляется так, как это было указано ранее для ассемблеров MASM и TASM.

Замечание о типах данных.


Замечание о типах данных.

В данной книге Вы встретитесь в основном с тремя типами данных (простых): байт, слово, двойное слово. При этом используются следующие стандартные обозначения. Байт - BYTE или DB, слово - WORD или DW, двойное слово - DWORD или DD. Выбор, скажем, в одном случае DB, а в другом BYTE, продиктован лишь желанием автора несколько разнообразить изложение.

7 Программу LINK.EXE называют также компоновщиком или просто линковщиком.

8 Правда, весьма ограниченно, т.к. согласование системных вызовов в разных операционных системах может весьма сильно различаться.



Пример использования оператора INVOKE



Рисунок 1.1.6. Пример использования оператора INVOKE.

Как видите, внешняя процедура объявляется теперь при помощи директивы PROTO. Данная директива позволяет при необходимости указывать и наличие параметров. Например, строка

PROC1 PROTO :DWORD, :WORD

будет означать, что процедура требует два параметра длиной в четыре и два байта (всего 6, т.е. @6).

Как уже говорилось, я буду редко использовать оператор INVOKE. Теперь я назову первую причину такого пренебрежения к данной возможности. Дело в том, что я сторонник чистоты языка ассемблера и любое использование макросредств вызывает у меня чувство несовершенства. На мой взгляд, и начинающим программистам не стоит увлекаться макросредствами, иначе не чувствуется вся красота этого языка. О второй причине Вы узнаете ниже.

На нашей схеме, на Рисунок 1.1, говорится не только о возможности подсоединения объектных модулей, но и библиотек. Собственно, если объектных модулей несколько, то это по понятным причинам вызовет неудобства. Поэтому объектные модули объединяются в библиотеки. Для подсоединения библиотеки в MASM удобнее всего использовать директиву INCLUDELIB, которая сохраняется в объектном коде и используется программой LINK.EXE.

Но как создать библиотеку из объектных модулей? Для этого имеется специальная программа, называемая библиотекарем. Предположим, мы хотим создать библиотеку LIB1.LIB, состоящую из одного модуля - PROG2.OBJ. Выполним для этого следующую команду: LIB /OUT:LIB1.LIB PROG2.OBJ.

Если необходимо добавить в библиотеку еще один модуль (MODUL.OBJ), то достаточно выполнить команду: LIB LIB1.LIB MODUL.OBJ.

Вот еще два полезных примера использования библиотекаря:

LIB /LIST LIB1.LIB - выдает список модулей библиотеки.

LIB /REMOVE:MODUL.OBJ LIB1.LIB - удаляет из библиотеки модуль MODUL.OBJ.

Вернемся теперь к нашему примеру. Вместо объектного модуля мы используем теперь библиотеку LIB1.LIB. Видоизмененный текст программы PROG1.ASM представлен на Рисунок 1.7.

.386P ; плоская модель .MODEL FLAT, STDCALL ;-------------------------------------------------- ; прототип внешней процедуры EXTERN PROC1@0:NEAR ;-------------------------------------------------- INCLUDELIB LIB1.LIB ;-------------------------------------------------- ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' _DATA ENDS ; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: CALL PROC1@0 RET ; выход _TEXT ENDS END START



Пример окна со строкой редактирования



Рисунок 1.3.2. Пример окна со строкой редактирования.



Пример программы использующей упрощенную сегментацию



Рисунок 1.1.10. Пример программы, использующей упрощенную сегментацию.

11 Все же в несколько упрощенном виде.

12 Честно говоря, не знаю ничего лучшего, чем собственноручно отправлять параметры в стек.

13 Разумеется, есть директива и для стека — это .STACK, но мы ее почти не будем использовать.



Приступаем к разбору следующего примера



II

Приступаем к разбору следующего примера. При запуске этой программы на экране появляется окно с кнопкой "Выход". Нажатие этой кнопки, как Вы понимаете, должно привести к выходу из программы.

Самое важное здесь, как мы узнаем, что кнопка нажата. Все просто: в начале проверка сообщения WM_COMMAND, а затем проверяем LPARAM — здесь хранится дескриптор (уникальный номер) окна (кнопка создается как окно). В данном случае для кнопки этого уже достаточно, чтобы определить событие24.

Да, и обратите внимание на свойства кнопки, которую мы создаем как окно, — это наиболее типичное сочетание свойств, но не единственное. Например, если Вы хотите, чтобы кнопка содержала иконку, то необходимым условием для этого будет свойство BS_ICON (или BS_BITMAP).

24 Честно говоря, здесь я, читатель, немного грешен. Убедившись, что событие произошло именно с кнопкой, нам бы следовало определить, какое событие произошло, проверив старшее слово параметра WPARAM (событие BN_CLICKED=0). Не вдаваясь в подробности, замечу, что в большинстве примеров, которые мы разбираем, для кнопки этого делать не обязательно.

; файл button.inc ; константы ; сообщение приходит при закрытии окна WM_DESTROY equ 2 ; сообщение приходит при создании окна WM_CREATE equ 1 ; сообщение при щелчке левой кнопкой мыши в области окна WM_LBUTTONDOWN equ 201h WM_COMMAND equ 111h ; свойства окна 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 ; прототипы внешних процедур 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 ; структуры ; структура сообщения 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

; файл button.asm .386P ; плоская модель .MODEL FLAT, stdcall include button.inc ; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ;------------------------------------------------------------ ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' NEWHWND DD 0 MSG MSGSTRUCT <?> WC WNDCLASS <?> HINST DD 0 ; дескриптор приложения TITLENAME DB 'Пример - кнопка выхода',0 CLASSNAME DB 'CLASS32',0 CPBUT DB 'Выход',0 ; выход CLSBUTN DB 'BUTTON',0 HWNDBTN DWORD 0 CAP DB 'Сообщение',0 MES DB 'Конец работы Программы',0 _DATA ENDS

; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения 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],0 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 TITLENAME ; имя окна PUSH OFFSET CLASSNAME ; имя класса 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 ; команда перерисовать видимую ; часть окна, сообщение 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 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_COMMAND JE WMCOMMND JMP DEFWNDPROC WMCOMMND: MOV EAX, HWNDBTN CMP DWORD PTR [EBP+14H], EAX ; не кнопка ли нажата? JE WMDESTROY 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


Приступим теперь к разбору конкретных примеров



II

Приступим теперь к разбору конкретных примеров. На Рисунок 1.2.2 представлена простая программа. Разберите ее внимательно — она будет фундаментом, на котором мы будем строить дальнейшее рассмотрение.

Прежде всего обратите внимание на директивы INCLUDELIB. В пакете MASM32 довольно много разных библиотек. Для данного примера нам понадобились две: user32.lib и kernel32.lib.

Сначала мы определяем константы и внешние библиотечные процедуры. В действительности все эти определения можно найти в include-файлах, прилагаемых к пакету MASM32. Мы не будем использовать стандартные include-файлы по двум причинам: во-первых, так удобнее понять технологию программирования, во-вторых — так легче перейти от MASM к TASM.

Необходимо четко понимать способ функционирования процедуры окна, так как именно это определяет суть программирования под Windows. Задача данной процедуры — правильная реакция на все приходящие сообщения. Давайте детально разберемся в работе нашего приложения. Сначала обратим внимание на то, что необработанные сообщения должны возвращаться в систему при помощи функции DefWindowProcA.

Мы отслеживаем четыре сообщения: WM_CREATE, WM_DESTROY, WM_LBUTTONDOWN, WM_RBUTTONDOWN. Сообщения WM_CREATE и WM_DESTROY в терминах объектного программирования играют роль конструктора и деструктора: они приходят в функцию окна при создании окна и при уничтожении окна. Если щелкнуть по крестику в правом углу окна, то в функцию окна придет сообщение WM_DESTROY. Далее будет выполнена функция PostQuitMessage и приложению будет послано сообщение WM_QUIT, которое вызовет выход из цикла ожидания и выполнение функции ExitProcess, что в свою очередь приведет к удалению приложения из памяти.

Обращаю Ваше внимание на метку _ERR — переход на нее происходит при возникновении ошибки, и здесь можно поместить соответствующее сообщение.

.386P ; плоская модель .MODEL FLAT, stdcall ; константы ; сообщение приходит при закрытии окна WM_DESTROY equ 2 ; сообщение приходит при создании окна WM_CREATE equ 1 ; сообщение при щелчке левой кнопкой мыши в области окна WM_LBUTTONDOWN equ 201h ; сообщение при щелчке правой кнопкой мыши в области окна WM_RBUTTONDOWN equ 204h ; свойства окна CS_VREDRAW equ 1h CS_HREDRAW equ 2h CS_GLOBALCLASS equ 4000h WS_OVERLAPPEDWINDOW equ 000CF0000H style equ CS_HREDRAW+CS_VREDRAW+CS_GLOBALCLASS ; идентификатор стандартной иконки IDI_APPLICATION equ 32512 ; идентификатор курсора IDC_CROSS equ 32515 ; режим показа окна - нормальный SW_SHOWNORMAL equ 1 ; прототипы внешних процедур 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 ; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ;-------------------------------------------------- ; структуры ; структура сообщения MSGSTRUCT STRUC MSHWND DD ? ; идентификатор окна, ; получающего сообщение MSMESSAGE DD ? ; идентификатор сообщения MSWPARAM DD ? ; доп. информация о сообщении MSLPARAM DD ? ; доп. информация о сообщении MSTIME DD ? ; время посылки сообщения MSPT DD ? ; положение курсора, во время посылки ; сообщения MSGSTRUCT ENDS ;--------- WNDCLASS STRUC CLSSTYLE DD ? ; стиль окна CLWNDPROC DD ? ; указатель на процедуру окна CLSCSEXTRA DD ? ; информация о доп. байтах для ; данной структуры CLWNDEXTRA DD ? ; информация о доп. байтах для окна CLSHINSTANCE DD ? ; дескриптор приложения CLSHICON DD ? ; идентификатор иконы окна CLSHCURSOR DD ? ; идентификатор курсора окна CLBKGROUND DD ? ; идентификатор кисти окна CLMENUNAME DD ? ; имя-идентификатор меню CLNAME DD ? ; специфицирует имя класса окон WNDCLASS ENDS




; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' NEWHWND DD 0 MSG MSGSTRUCT <?> WC WNDCLASS <?> HINST DD 0 ; здесь хранится дескриптор приложения TITLENAME DB 'Простой пример 32-битного приложения',0 CLASSNAME DB 'CLASS32',0 CAP DB 'Сообщение',0 MES1 DB 'Вы нажали левую кнопку мыши',0 MES2 DB 'Выход из программы. Пока!',0 _DATA ENDS

; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA@4 MOV [HINST], EAX REG_CLASS: ; заполнить структуру окна стиль MOV [WC.CLSSTYLE], style ; процедура обработки сообщений MOV [WC.CLWNDPROC], OFFSET WNDPROC MOV [WC.CLSCEXTRA], 0 MOV [WC.CLWNDEXTRA], 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 ;--------- MOV [WC.CLBKGROUND], 17 ; цвет окна MOV DWORD PTR [WC.CLMENUNAME], 0 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 TITLENAME ; имя окна PUSH OFFSET CLASSNAME ; имя класса 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 ; команда перерисовать видимую ; часть окна, сообщение 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 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+014H] 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_LBUTTONDOWN ;левая кнопка JE LBUTTON CMP DWORD PTR [EBP+0CH] ,WM_RBUTTONDOWN ;правая кнопка JE RBUTTON JMP DEFWNDPROC ; нажатие правой кнопки приводит к закрытию окна RBUTTON: JMP WMDESTROY ; нажатие левой кнопки мыши LBUTTON: ; выводим сообщение PUSH 0 ; МВ_ОК PUSH OFFSET CAP PUSH OFFSET MES1 PUSH DWORD PTR [EBP+08H] CALL MessageBoxA@16 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 ; МВ_ОК PUSH OFFSET CAP PUSH OFFSET MES2 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


Процедура главного окна



Процедура главного окна

.

Вот прототип функции19 окна на языке С:

LRESULT CALLBACK WindowFunc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

Оставив в стороне тип возвращаемого функцией значения20, обратите внимание на передаваемые параметры. Вот смысл этих параметров: hwnd — идентификатор окна, message — идентификатор сообщения, wParam и lParam — параметры, уточняющие смысл сообщения (для каждого сообщения могут играть разные роли или не играть никаких). Все четыре параметра, как вы, наверное, уже догадались, имеют тип DWORD.

А теперь рассмотрим "скелет" этой функции на языке ассемблера.

WNDPROC PROC PUSH EBP MOV EBP, ESP ; теперь EBP указывает на вершину стека PUSH EBX PUSH ESI PUSH EDI PUSH DWORD PTR [EBP+14H]; LPARAM (lParam) PUSH DWORD PTR [EBP+10H]; WPARAM (wParam) PUSH DWORD PTR [EBP+0CH]; MES (message) PUSH DWORD PTR [EBP+08H]; HWND (hwnd) CALL DefWindowProcA@16 POP EDI POP ESI POP EBX POP EBP RET 16 WNDPROC ENDP



Программа LINK EXE( bit)



Программа LINK.EXE(32 bit).

В этой таблице объяснение опции помещено под строкой, содержащей эту опцию.

/ALIGN:number

Определяет выравнивание секций в линейной модели. По умолчанию 4096.

/BASE:{address|@filename,key}

Определяет базовый адрес (адрес загрузки). По умолчанию для ЕХЕ-программы адрес 0х400000, для DLL — 0х10000000.

/COMMENT:["]comment["]

Определяет комментарий, помещаемый в заголовок ЕХЕ- и DLL-файлов.

/DEBUG

Создает отладочную информацию для ЕХЕ- и DLL-файлов. Отладочная информация помещается в pdb-файл.

/DEBUGTYPE:{CV|COFF|BOTH}

CV — отладочная информация в формате Microsoft, COFF — отладочная информация в формате COFF (Common Object File Format), BOTH — создаются оба вида отладочной информации.

/DEF:filename

Определяет DEF-файл.

/DEFAULTLIB:library

Добавляет одну библиотеку к списку используемых библиотек.

/DLL

Создать DLL-файл.

/DRIVER[:{UPONLY|WDM}]

Используется для создания NT-драйвера (Kernel Mode Driver).

/ENTRY:symbol

Определяет стартовый адрес для ЕХЕ- и DLL-файлов.

/EXETYPE:DYNAMIC

Данная опция используется при создании VxD-драйвера.

/EXPORT:entryname[=internalname][,@ordinal[,NONAME]][,DATA]

Данная опция позволяет экспортировать функцию из вашей программы так, чтобы она была доступна для других программ. При этом создается import-библиотека.

/FIXED[:NO]

Данная опция фиксирует базовый адрес, определенный в опции /BASE.

/FORCE[:{MULTIPLE|UNRESOLVED}]

Позволяет создавать исполняемый файл, даже если не найдено внешнее имя или имеется несколько разных определений.

/GPSIZE:number

Определяет размер общих переменных для MIPS и Alpha платформ.

/HEAP:reserve[,commit]

Определяет размер кучи (HEAP) в байтах. По умолчанию этот размер равен одному мегабайту.

/IMPLIB:filename

Определяет имя import-библиотеки, если она создается.

/INCLUDE:symbol

Добавляет имя к таблице имен.

/INCREMENTAL:{YES|NO}

Если установлена опция /INCREMENTAL:YES, то в ЕХЕ добавляется дополнительная информация, позволяющая быстрее перекомпилировать этот файл. По умолчанию это информация не добавляется.




/LARGEADDRESSAWARE[:NO]

Указывает, что приложение оперирует адресами, большими 2 Гб.

/LIBPATH:dir

Определяет библиотеку, которая в первую очередь разыскивается компоновщиком.

/MACHINE: {ALPHA|ARM|IX86|MIPS|MIPS16|MIPSR41XX|PPC|SH3|SH4}

Определяет платформу. В большинстве случаев это делать не приходится.

/MAP[:filename]

Дает команду создания МАР-файла.

/MAPINFO:{EXPORTS|FIXUPS|LINES}

Указывает компоновщику включить соответствующую информацию в МАР-файл.

/MERGE:from=to

Объединить секцию "from" с секцией "to" и присвоить имя "to".

/NODEFAULTLIB[:library]

Игнорирует все или конкретную библиотеку.

/NOENTRY

Необходимо для создания DLL-файла.

/NOLOGO

Не выводить начальное сообщение компоновщика.

/OPT:{ICF[,iterations]|NOICF|NOREF|NOWIN98|REF|WIN98}

Определяет способ оптимизации, которую выполняет компоновщик.

/ORDER:@filename

Оптимизация программы путем вставки определенных инициализированных данных (COMDAT).

/OUT:filename

Определяет выходной файл.

/PDB: {filename|NONE}

Определить имя файла, содержащего информацию для отладки.

/PDBTYPE:{CON[SOLIDATE]|SEPT[YPES]}

Определяет тип РDВ-файла.

/PROFILE

Используется для работы с профайлером (анализатором работы программы).

/RELEASE

Помещает контрольную сумму в выходной файл.

/SECTION:name,[E][R][W][S][D][K][L][P][X]

Данная опция позволяет изменить атрибут секции.

/STACK:reserve[,commit]

Определяет размер выделяемого стека. Commit — определяет размер памяти, интерпретируемый операционной системой.

/STUB:filename

Определяет STUB-файл, запускающийся в системе MS DOS.

/SUBSYSTEM:{NATIVE|WINDOWS|CONSOLE|WINDOWSCE|POSIX}[,#[.##]]

Определяет, как запускать ЕХЕ-файл. CONSOLE — консольное приложение, WINDOWS — обычные WINDOWS-приложения, NATIVE — приложение для Windows NT, POSIX — создает приложение в POSIX-подсистеме WINDOWS NT.

/SWAPRUN:{CD|NET}

Сообщает операционной системе скопировать выходной файл в swap-файл (WINDOWS NT).

/VERBOSE[:LIB]

Заставляет выводить информацию о процессе компоновки.

/VERSION:#[.#]

Помещает информацию о версии в ЕХЕ-заголовок.

/VXD

Создать VXD-драйвер.

/WARN[:warninglevel]

Определяет количество возможных предупреждений, выдаваемых компоновщиком.

/WS:AGGRESSIVE

Несколько уменьшает скорость выполнения приложения (Windows NT). Операционная система удаляет данное приложение из памяти в случае его простоя.


Программа ML EXE



Программа ML.EXE

Параметр

Комментарий




Программа TASM32.EXE

/?Вывод помощи.
/ATСоздать файл в формате .СОМ. Для программирования в Windows этот ключ, естественно, бесполезен.
/Bl<linker> Использовать альтернативный компоновщик. Предполагается автоматический запуск компоновщика.
/c Компиляция без компоновки.
/СрСохранение регистров пользовательских идентификаторов. Может использоваться для дополнительного контроля.
/CuПриведение всех пользовательских идентификаторов к верхнему регистру.
/СхСохранение регистров пользовательских идентификаторов, объявленных PUBLIC и EXTERNAL.
/coffСоздание объектных файлов в стандарте coff. Применение обязательно.
/D<name>=[строка]Задание текстового макроса. Очень удобен для отладки с использованием условной компиляции.
/ЕРЛистинг: текст программы с включаемыми файлами.
/F <hex>Размер стека в байтах. Размер стека по умолчанию равен 1 Мб.
/Fe<file>Имя исполняемого файла. Имеет смысл без параметра /с.
/Fl<file>Создать файл листинга.
/Fm<file>Создать map-файл. Имеет смысл без опции /с.
/Fo<file>Задать имя объектного файла.
/FpiВключение кода эмулятора сопроцессора. Начиная с 486-ого микропроцессора, данный параметр потерял актуальность.
/Fr<file>Включить ограниченную информацию браузера.
/FR<file>Включить полную информацию браузера.
/G<c|d|z>Использовать соглашение вызова Паскаль, Си, stdcall.
/H<number>Установить максимальную длину внешних имен.
/I<name>Добавить путь для inc-файлов. Допускается до 10 опций /I.
/link <opt>Опции командной строки компоновщика. Имеет смысл без опции /с.
/nologoНе показывать заголовочный текст компилятора.
/SaЛистинг максимального формата.
/ScВключить в листинг синхронизацию.
/SfЛистинг первого прохода.
/Sl<number>Длина строки листинга.
/SnНе включать в листинг таблицу символов.
/Sp<number>Высота страницы листинга.
/Ss<string>Текст подзаголовка листинга.
/St<string>Текст заголовка листинга.
/SxВключить в листинг фрагменты условной компиляции.
/Ta<file> Для компилирования файлов, расширение которых не .asm.
/W<number>Устанавливает перечень событий компиляции, трактуемые как предупреждения.
/WXТрактовать предупреждения как ошибки.
/wТоже что /W0 /WX.
/XИгнорировать путь, установленный переменной окружения INCLUDE.
/ZdОтладочная информация состоит только из номеров строк.
/ZfОбъявить все имена PUBLIC.
/ZiВключить полную отладочную информацию.
/ZmВключить совместимость с MASM 5.01.
/Zp<n>Установить выравнивание структур.
/ZsВыполнять только проверку синтаксиса.
ПараметрКомментарий

/? или /hВывод помощи.
/aСегменты в объектном файле располагаются в алфавитном порядке.
/sСегменты в объектном файле расположены в порядке их описания.
/d<name>=[string]Задание текстового макроса. Очень удобен для отладки с использованием условной компиляции.
/eВключение кода эмуляции сопроцессора.
/rРазрешение инструкций сопроцессора.
/i<string>Добавить путь для inc-файлов. Синтаксис такой же, как у команды РАТН.
/j<dir>Определяет директиву, которая будет транслироваться перед трансляцией.
/kh<number>Задается максимальное количество идентификаторов. По умолчанию 16384.
/lСоздавать файл листинга.
/laПоказать в листинге код, вставляемый транслятором для организации интерфейса с языками высокого уровня.
/mlРазличать прописные и строчные буквы в идентификаторах.
/mxРазличать прописные и строчные буквы в идентификаторах PUBLIC и EXTERNAL.
/muСчитать все символы в идентификаторах как прописные.
/mv<number>Установить максимальную длину идентификатора.
/m<number>Установка количества проходов транслятора. По умолчанию это число равно 1.
/nНе выдавать в файле листинга таблицы идентификаторов.
/os, /o, /op, /oiТип объектного кода: стандартный, оверлейный, Phar Lap, IBM.
/pПроверять наличие кода с побочными эффектами при работе в защищенном режиме.
/qУдаление из объектного кода лишней информации.
/tПодавление вывода всех сообщений при условном ассемблировании.
/w0, /w1, /w2Уровень полноты сообщений: сообщения не генерируются, сообщения генерируются.
/w-<xxx> /w+<xxx>Генереция (+) или ее отсутствие (-) сообщений класса xxx.
/xВключить в листинг блоки условного ассемблирования.
/zВыводить не только сообщения об ошибках, но строку с ошибкой.
/ziВключить в объектный код информацию для отладки.
/zdПоместить в объектный код номера строк.
/znНе помещать в объектный код отладочной информации.

Программа TLINK EXE



Программа TLINK32.EXE.

В пакетах Borland С, начиная с 1997 года, сосуществовали две программы для компоновки объектных файлов: tlink32.exe и ilink32.exe. Опции этих программ практически совпадали, ilink32.exe - инкрементальный, или пошаговый, компоновщик. Он хранит информацию о предыдущих компоновках, что позволяет значительно ускорить весь процесс повторных трансляций. В последнее время программа tlink32.exe вообще исчезла из поставки. В дальнейшем мы не будем делать различий между этими двумя программами. Опции, помеченные (+), появились в новых версиях ilink32.exe, а опции со знаком (-), наоборот, исчезли. В новых версиях для выделения опции используется "/" вместо тире.



-mСоздать МАР-файл с информацией о сегментах и два листинга с PUBLIC-именами (в алфавитном и адресном порядке).
-sДетальная информация о сегментах в МАР-файле.
-MПоместить в МАР-файл сокращенные имена идентификаторов.
-cРазличать прописные и заглавные буквы в PUBLIC и EXTERNAL именах.
-EnnЗадает максимальное количество ошибок, приводящее к остановке компоновки.
-P-Запретить паковать сегменты. Имеет смысл для 16-битных приложений (-P — разрешить).
-b:ххххЗадает базовый адрес. По умолчанию базовый адрес равен 400000Н. (+)
-B:ххххАналогично опции -b, но не создает таблицу настройки. Ключи —b и -B могут несколько ускорить работу программы.
-wxxxВозможные предупреждения. Например, -w-stk игнорировать отсутствие стекового сегмента.
-Txx-Tpx PE image(x: е=ЕХЕ, d=DLL) Тип выходного файла. Tpe - создать ЕХЕ-файл. Tpd - создать DLL-файл. Tpp - создать пакетный файл. (+)
-ax -ap -aa-ар — создать консольное приложение, -аа — создать обычное Windows-приложение (GUI), -ad — создать 32-битный драйвер для Windows.(+)
-rЗаставляет компоновщик выдавать информацию о процессе компоновки.
-Vd.dПомещает в ЕХЕ-заголовок ожидаемую версию Windows.
-Ud.dПоместить в заголовок ЕХЕ-файла номер версии программы. (+)
-oИмпорт по номеру функции. (-)
-Ao:nnnnОпределяет величину выравнивания (кратно 2, минимально 16).
-Af:nnnnФайл выравнивания.
-Sc:xxxxОпределить размер стека.
-S:xxxx Определить размер резервного стека. Минимально 4К.
-Hc:ххххОпределить размер специальной "кучи".
-H:ххххОпределить размер резервной "кучи".
-nНет библиотеки "по умолчанию". (-)
-vПоместить в исполняемый файл полную отладочную информацию. Можно использовать -v+ и -v- для селективного помещения отладочной информации в отдельные файлы.
-jОпределить путь поиска OBJ-файлов.
-LОпределить путь к LIB-библиотеке.
-xНе создавать МАР-файл.
-RrПозволяет заменять или добавлять ресурсы. (+)
-dУстановить задержку загрузки библиотеки DLL. Она будет загружаться лишь при вызове входной процедуры. (+)
-DxxxxПоместить в PE-заголовок специальный дескриптор.
-GCПоместить в заголовок РЕ строку (или строки). Например -GC"Hellow!". (+)
-GDГенерировать Delphi-совместимый файл ресурсов. (+)
-GFУстановить флаг для загрузочного модуля: SWAPNET - указание для операционной системы поместить загрузочный модуль в swap-файл и загружать его оттуда, если он расположен на сетевом устройстве. SWAPCD - аналогично предыдущему флагу, но для съемных устройств. UNIPROCESSOR - приложение не должно запускаться в мультипроцессорной системе. LARGEADDRESSAWARE - приложение использует адреса больше 4 Gb. AGGRESSIVE - операционная система удаляет приложение из памяти в случае его простоя.(+)
-GkЗаставляет компоновщик оставлять в случае ошибки те файлы, которые обычно в этом случае уничтожались.(+)
-GlГенерировать LIB-файл.(+)
-GprСоздавать пакет "времени исполнения".(+)
-GpdСоздать пакет "времени создания".(+)
-GnЗапретить пошаговую компиляцию.(+)
-GSstring-GS:string=[ECIRWSDKP] Добавляет флаги к уже существующим флагам секций.(+)
-GzПомещает в РЕ-заголовок контрольную сумму загрузочного модуля.
На первый взгляд трудно выявить предпочтение между MASM32 и TASM32. Но все же:

MASM32 несколько более богат возможностями. Я имею в виду опции командной строки. TASM32 перестал поддерживаться как отдельный продукт. В связи с этим MASM обошел конкурента по количеству содержащихся в пакете примеров, документации, библиотек и т.д. TASM32 осуществляет более сложный алгоритм вызова API-функций, а это приводит к тому, что исполняемые модули в TASM32 получаются несколько большими, чем в MASM32.

Завершая главу, приведу несколько простых примеров.


Простой пример программы для Windows (MASM)



Рисунок 1.2.2. Простой пример программы для Windows (MASM32).



Простой пример программы для Windows (TASM)



Рисунок 1.2.3. Простой пример программы для Windows (TASM32).

Кстати заметьте, что исполняемый модуль, транслируемый в TASM32, всегда несколько длиннее аналогичного модуля, транслируемого с помощью пакета MASM32.

Обратите внимание на то, как задаются свойства окон. Описание этих свойств можно найти в программе помощи для функции CreateWindow.

В принципе, подбор необходимых сочетаний свойств окна — довольно кропотливое занятие. Это одна из причин, из-за которой понятие ресурса стало неотъемлемой частью загружаемого модуля. С понятием ресурса мы познакомимся в последующих главах.

Приведенные примеры показывают, что процедура окна должна правильно реагировать на пришедшие сообщения. В дальнейшем Вы узнаете, что и сама процедура может посылать сообщения приложению, другим окнам, а также своему окну21.

Часть API-функций получила суффикс "А". Дело в том, что функции имеют два прототипа: с суффиксом "А" - поддерживают ANSI, а с суффиксом "W" - Unicode.

21 Собственно PostQuitMessage есть пример посылки сообщения своему приложению.



Работа программы со строкой редактирования (Программа на Рисунок ) IV



IV

Наконец последний в этой главе пример — окно-список. При создании окна в список помещаются названия цветов. Если произвести двойной щелчок по цвету, то появится окно-сообщение с названием этого цвета.

Двойной щелчок по элементу списка определяется по следующей схеме: отслеживается событие, происходящее со списком, а далее по старшему слову параметра WPARAM определяется, какое событие имело место (параметр [EBP+10Н], а его старшая часть [EBP+12Н]).

Не имея возможности подробно останавливаться на различных свойствах управляющих элементов, укажу основополагающую идею такого элемента, как список. Каждый элемент списка имеет ряд атрибутов, по которым он может быть найден в списке: порядковый номер в списке, название элемента, уникальный номер. Последняя характеристика наиболее важна, так как позволяет однозначно идентифицировать элемент.

; файл list.inc ; константы WM_SETFOCUS equ 7h ; сообщение приходит при закрытии окна WM_DESTROY equ 2 ; сообщение приходит при содании окна WM_CREATE equ 1 ; сообщение, если что-то происходит с элементами на окне WM_COMMAND equ 111h ; сообщение, позволяющее послать элементу строку WM_SETTEXT equ 0Ch ; сообщение, позволяющее получить строку WM_GETTEXT equ 0Dh ; сообщение — команда добавить строку LB_ADDSTRING equ 180h LB_GETTEXT equ 189h LB_GETCURSEL equ 188h LBN_DBLCLK equ 2 ; свойства окна CS_VREDRAW equ 1h CS_HREDRAW equ 2h CS_GLOBALCLASS equ 4000h WS_TABSTOP equ 10000h WS_SYSMENU equ 80000h WS_THICKFRAME equ 40000h WS_OVERLAPPEDWINDOW equ WS_TABSTOP + WS_SYSMENU STYLE equ CS_HREDRAW + CS_VREDRAW + CS_GLOBALCLASS CS_HREDRAW equ 2h BS_DEFPUSHBUTTON equ 1h WS_VISIBLE equ 10000000h WS_CHILD equ 40000000h WS_BORDER equ 800000h WS_VSCROLL equ 200000h LBS_NOTIFY equ 1h STYLBTN equ WS_CHILD + BS_DEFPUSHBUTTON + WS_VISIBLE + WS_TABSTOP STYLLST equ WS_THICKFRAME + WS_CHILD + WS_VISIBLE + WS_BORDER + WS_TABSTOP + WS_VSCROLL + LBS_NOTIFY ; идентификатор стандартной иконки IDI_APPLICATION equ 32512 ; идентификатор курсора IDC_ARROW equ 32512 ; режим показа окна — нормальный SW_SHOWNORMAL equ 1 ; прототипы внешних процедур EXTERN SetFocus@4: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 ; структуры ; структура сообщения 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




; файл list.asm .386P ; плоская модель .MODEL FLAT, stdcall include list.inc ; директивы копоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ;------------------------------------------------------------ ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' NEWHWND DD 0 MSG MSGSTRUCT <?> WC WNDCLASS <?> HINST DD 0 ; дескриптор приложения TITLENAME DB 'Пример - окно LISTBOX',0 CLASSNAME DB 'CLASS32',0 CPBUT DB' Выход', 0 ; выход CPLST DB ' ',0 CLSBUTN DB 'BUTTON',0 CLSLIST DB 'LISTBOX',0 HWNDBTN DWORD 0 HWNDLST DWORD 0 CAP DB 'Сообщение',0 CAP1 DB 'Выбран',0 MES DB 'Конец работы Программы',0 ; массив строк STR1 DB ' Красный',0 STR2 DB 'Зеленый',0 STR3 DB 'Синий',0 STR4 DB 'Желтый',0 STR5 DB 'Черный',0 STR6 DB 'Белый',0 ; указатели на строки PS DWORD OFFSET STR1 DWORD OFFSET STR2 DWORD OFFSET STR3 DWORD OFFSET STR4 DWORD OFFSET STR5 DWORD OFFSET STR6 BUF DB 30 dup(0) _DATA ENDS

; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения PUSH О 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], 0 MOV DWORD PTR [WC.CLNAME], OFFSET CLASSNAME PUSH OFFSET WC CALL RegisterClassA@4 ; создать окно зарегистрированного класса PUSH 0 PUSH [HINST] PUSH 0 PUSH 0 PUSH 200 ; DY - высота окна PUSH 250 ; DX - ширина окна PUSH 100 ; Y - координата левого верхнего угла PUSH 100 ; X - координата левого верхнего угла PUSH WS_OVERLAPPEDWINDOW PUSH OFFSET TITLENAME ; имя окна PUSH OFFSET CLASSNAME ; имя класса 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 ; команда перерисовать видимую ; часть окна, сообщение 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 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_COMMAND JE WMCOMMND JMP DEFWNDPROC WMCOMMND: MOV EAX,HWNDBTN CMP DWORD PTR [EBP+14H],EAX ; кнопка ? ; на выход ? JE WMDESTROY MOV EAX, HWNDLST CMP DWORD PTR [EBP+14H],EAX ; список ? JNE NOLIST ; работаем со списком CMP WORD PTR [EBP+12H], LBN_DBLCLK JNE NOLIST ; двойной щелчок есть, теперь определить ; выбранную строку ; вначале индекс PUSH 0 PUSH 0 PUSH LB_GETCURSEL PUSH HWNDLST CALL SendMessageA@16 ; теперь сам текст PUSH OFFSET BUF PUSH EAX PUSH LB_GETTEXT PUSH HWNDLST CALL SendMessageA@16 ; сообщить, что выбрано PUSH 0 PUSH OFFSET CAP1 PUSH OFFSET BUF PUSH DWORD PTR [EBP+08H] CALL MessageBoxA@16 NOLIST: 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 ; запомнить дескриптор кнопки ;------------------------------------------------------------ ; создать окно LISTBOX PUSH 0 PUSH [HINST] PUSH 0 PUSH DWORD PTR [EBP+08H] PUSH 90 ; DY PUSH 150 ; DX PUSH 50 ; Y PUSH 10 ; X PUSH STYLLST PUSH OFFSET CPLST ; имя окна PUSH OFFSET CLSLIST ; имя класса PUSH 0 CALL CreateWindowExA@48 MOV HWNDLST,EAX ; заполнить список PUSH PS PUSH 0 PUSH LB_ADDSTRING PUSH HWNDLST CALL SendMessageA@16 PUSH PS+4 PUSH 0 PUSH LB_ADDSTRING PUSH HWNDLST CALL SendMessageA@16 PUSH PS+8 PUSH 0 PUSH LB_ADDSTRING PUSH HWNDLST CALL SendMessageA@16 PUSH PS+12 PUSH 0 PUSH LB_ADDSTRING PUSH HWNDLST CALL SendMessageA@16 PUSH PS+16 PUSH 0 PUSH LB_ADDSTRING PUSH HWNDLST CALL SendMessageA@16 PUSH PS+20 PUSH 0 PUSH LB_ADDSTRING PUSH HWNDLST CALL SendMessageA@16 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



Puc. 1.3.4. Пример окна с простым списком.

В конце главы я бы хотел поговорить вот на какую тему. С окном на экране могут происходить самые интересные и необычные вещи.

Например, оно по желанию пользователя может перемещаться, менять свой размер, сворачиваться и разворачиваться, наконец, другие окна могут заслонять данное окно.

В таких ситуациях система посылает окну сообщение WM_PAINT. Такое же сообщение посылается окну при выполнении некоторых функций, связанных с перерисовкой окна, таких, например, как UpdateWindow.

Если сообщение WM_PAINT не обрабатывается процедурой окна, а возвращается системе посредством функции DefWindowProc, то система берет на себя не только перерисовку окна (что она и так делает), но и перерисовку содержимого окна. К содержимому окна, однако, относятся только дочерние окна, которыми являются кнопки, списки, окна редактирования и другие элементы управления. В следующих главах мы, в частности, будем говорить о том, как выводить в окно текстовую и графическую информацию.

Здесь, чтобы информация сохранялась в окне, нам не обойтись без обработки сообщения WM_PAINT.

В заключение заметим, что представленные в данной главе программы записаны для трансляции в MASM32.

Для того чтобы использовать их с TASM32, как и ранее, достаточно удалить из всех API-имен суффикс @N и подключить вместо USER32.LIB и KERNEL32.LIB библиотеку IMPORT32.LIB.


Редакторы



Редакторы.

Хотя сам я никогда не использую специализированные редакторы для написания программ на ассемблере, полноты ради кратко расскажу о двух известных мне редакторах. Начну с редактора QEDITOR.EXE, который поставляется вместе с пакетом MASM32. Сам редактор и все сопутствующие ему утилиты написаны на ассемблере. Анализ их размера и возможностей действительно впечатляет. Например, сам редактор имеет длину всего 27 Кб, а утилита, используемая для просмотра отчетов о трансляции — всего 6 Кб. Редактор вполне годится для работы с небольшими одномодульными приложениями. Для работы с несколькими модулями он не очень удобен. Работа редактора основана на взаимодействии с различными утилитами посредством пакетных файлов. Например, трансляцию программ осуществляет пакетный файл ASSMBL.BAT, который использует ассемблер ML.EXE, а результат ассемблирования направляется в текстовый файл ASMBL.TXT. Далее для просмотра этого файла используется простая утилита THEGUN.EXE. Аналогично осуществляется редактирование связей. Для дизассемблирования исполняемого модуля используется утилита DUMPPE.EXE, результат работы этой утилиты помещается в текстовый файл DISASM.TXT. Аналогично осуществляются и другие операции. Вы легко сможете настроить эти операции, отредактировав соответствующий пакетный файл с модификацией (при необходимости) используемых утилит (заменив, например, ML.EXE на TASM32.EXE и т.п.).

Вторая программа, с которой я хочу познакомить читателя, это EAS.EXE (Easy Assembler Shell). Редактор, а точнее оболочка, позволяет создавать и транслировать довольно сложные проекты, состоящие из ASM-,OВJ-,RC-,RES-,DEF-файлов. Программа позволяет работать как с TASM, так и MASM, а также с другими утилитами (отладчиками, редакторами ресурсов и т.д.). Непосредственно в программе можно настроить компиляторы и редакторы связей на определенный режим работы путем задания ключей этих утилит.



Редакторы ресурсов



Редакторы ресурсов

Обычно я пользуюсь редактором ресурсов из пакета ВС5 (Borland C++ 5.0) Простые ресурсы можно создавать в обычном текстовом редакторе. Язык описания ресурсов будет подробно рассмотрен далее.

14 Программа DEBUGER.EXE все еще поставляется с операционной системой Windows, но этот отладчик не поддерживает новый формат исполняемых файлов.



Регистрация класса окон



Регистрация класса окон.

Регистрация класса окон осуществляется с помощью функции RegisterClassA, единственным параметром которой является указатель на структуру WNDCLASS, содержащую информацию об окне (см. пример ниже).



Схема передачи параметров в процедуру



Рисунок 1.2.4. Схема передачи параметров в процедуру




Схема трансляции ассемблерного


(или TASM32.EXE и TLINK32.EXE в Турбо Ассемблере).

Пусть файл с текстом программы на языке ассемблера называется PROG.ASM, тогда, не вдаваясь в подробный анализ, две стадии трансляции будут выглядеть следующим образом: c:\masm32\bin\ml /c /coff PROG.ASM - в результате появляется модуль PROG.OBJ, а также c:\masm32\bin\link /SUBSYSTEM:WINDOWS PROG.OBJ - в результате появляется исполняемый модуль PROG.EXE. Как Вы, я надеюсь, догадались /с и /coff являются параметрами программы ML.EXE, a /SUBSYSTEM:WINDOWS является параметром для программы LINK.EXE. О других ключах этих программ более подробно см. Гл. 1.5.

Чем больше я размышляю об этой схеме трансляции, тем более совершенной она мне кажется. Действительно, формат конечного модуля зависит от операционной системы. Установив стандарт на структуру объектного модуля, мы получаем возможность: а) использовать уже готовые объектные модули, б) стыковать между собой программы, написанные на разных языках. Но самое прекрасное здесь то, что если стандарт объектного модуля распространить на разные операционные системы, то можно использовать модули, написанные в разных операционных системах8.

Чтобы процесс трансляции сделать для Вас привычным, рассмотрим несколько простых, "ничего не делающих" программ.

.386P ; плоская модель .MODEL FLAT, STDCALL ;-------------------------------------------------- ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' _DATA ENDS ; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: RET ; выход _TEXT ENDS END START



"Скелет" оконной


В данной книге термины процедура и функция являются синонимами.

20 Нам это никогда не понадобится.



Создание окна



Создание окна

. На основе зарегистрированного класса с помощью функции CreateWindowExA (или CreateWindowA) можно создать экземпляр окна. Как можно заметить, это весьма напоминает объектную модель программирования.



Свойства конкретного окна



I

Следуя правилу, я попробую сформулировать несколько положений, которые помогут в дальнейшем легко манипулировать окнами и создавать гибкие, мощные, производительные приложения.

Свойства конкретного окна задаются при вызове функции CreateWindow определением параметра Style. Константы, определяющие свойства окна, содержатся в специальных файлах, которые подключаются при компиляции. Поскольку свойства фактически определяются наличием или отсутствием того или иного бита в константе, комбинация свойств — это просто сумма констант. В отличие от многих рекомендаций для разработчиков все константы здесь определяются непосредственно в программах. Окно создается на основе зарегистрированного класса. Окно может содержать элементы управления — кнопки, окна редактирования, списки, полосы прокрутки и т.д. Все эти элементы могут создаваться как окна с предопределенными классами (для кнопок "BUTTON", для окна редактирования "EDIT", для списка "LISTBOX" и т.д.). Система общается с окном, а следовательно, и с самим приложением посредством посылки сообщений. Эти сообщения должны обрабатываться процедурой окна. Программирование под Windows в значительной степени является программированием обработки таких сообщений. Сообщения генерируются системой также в случаях каких-либо визуальных событий, происходящих с окном или управляющими элементами 22; на нем. К таким событиям относятся передвижение окна или изменение его размеров, нажатие кнопки, выбор элемента в списке, передвижение курсора мыши и т.д. и т.п. Это и понятно, программа должна как-то реагировать на подобные события. Сообщение имеет код (будем обозначать его в программе MES) и два параметра (WPARAM и LPARAM). Для каждого кода сообщения придумано свое макроимя, хотя это всего лишь целое число. Например, сообщение WM_CREATE приходит один раз, когда создается окно, WM_PAINT посылается окну", если оно перерисовывается, сообщение WM_RBUTTONDOWN генерируется, если щелкнуть правой кнопкой мыши при расположении курсора мыши в области окна и т.д. Параметры сообщения могут не иметь никакого смысла либо играть уточняющую роль. Например, сообщение WM_COMMAND генерируется системой, когда что-то происходит с управляющими элементами окна. В этом случае по значению параметров можно определить, какой это элемент и что с ним произошло (LPARAM — дескриптор элемента, старшее слово WPARAM — событие, младшее слово WPARAM — обычно идентификатор ресурса см. Часть II). Можно сказать, что сообщение WM_COMMAND несет сообщение от элемента на окне. Сообщение может генерироваться не только системой, но и самой программой. Например, можно послать сообщение-команду какому-либо элементу управления (добавить элемент в список, послать строку в окно редактирования и т.п.). Иногда посылка сообщений используется как прием программирования. Например, можно придумать свои сообщения так, чтобы при их посылке программа выполнила те или иные действия. Естественно, это сообщение должно "отлавливаться" либо в процедуре какого-либо окна, либо в цикле обработки сообщений. Такой подход очень удобен, поскольку позволяет фактически осуществлять циклические алгоритмы так, чтобы возможные изменения с окном во время исполнения такого цикла сразу проявлялись на экране.

22 Вообще, можно выделить как управляющие элементы (кнопки, переключатели), так и управляемые элементы (окна редактирования), но мы все их будем называть управляющими.

23 Хотя на самом деле вызывается процедура окна с соответствующими значениями параметров, мы и в дальнейшем будем говорить о посылке окну сообщения.



Включение в исполняемый файл отладочной информации



Включение в исполняемый файл отладочной информации

. Если в исполняемый файл была включена отладочная информация, то фирменный отладчик позволяет работать одновременно и с текстом программы, и с дизассемблированным кодом. Это особенно удобно для языков высокого уровня, а для программ на ассемблере это весьма мощный инструмент отладки. Пусть текст программы содержится в файле PROG.ASM. Для того чтобы включить отладочную информацию в исполняемый модуль, используем при трансляции следующие ключи:

MASM:

ML /c /coff /Zd /Zi prog.asm
LINK /subsystem:windows /debug prog.obj

При этом кроме файла PROG.EXE на диске появится файл PROG.PDB, содержащий отладочные данные. Теперь для отладки следует запустить фирменный 32-битный отладчик фирмы Microsoft - Code View.

TASM:

TASM32 /ml /zi prog.asm
TLINK32 -aa -v prog.obj

В результате в модуль PROG.EXE будет включена отладочная информация. Для отладки такого модуля следует использовать 32-битный Turbo Debugger - TD32.EXE. На Рисунок 1.5.1 представлено окно отладчика с отлаживаемой программой. Можно видеть, что на экране имеется и текст программы, и ее дизассемблированный код. Более подробно об отладчиках мы будем говорить в последней части книги.



Вопрос о передачи параметров через стек



IV

Здесь мне хотелось бы рассмотреть подробнее вопрос о передачи параметров через стек.

Это не единственный способ передачи параметров, но именно через стек передаются параметры API-функциям, поэтому на это необходимо обратить внимание.

Состояния стека до и после вызова процедуры приводится на Рисунок 1.2.4.

Рисунок 1.2.4 демонстрирует стандартный вход в процедуру, практикующийся в таких языках высокого уровня, как Паскаль и Си.

При входе в процедуру выполняется стандартная последовательность команд:

PUSH EBP MOV EBP, ESP SUB ESP, N ; N - количество байт для локальных переменных.

Адрес первого параметра определяется как [EBP+8Н], что мы уже неоднократно использовали. Адрес первой локальной переменной, если она зарезервирована, определяется как [EBP-4] (имеется в виду переменная типа DWORD). На ассемблере не очень удобно использовать локальные переменные, и мы не будем резервировать для них место (смотрите, однако, Главу 2.5).

В конце процедуры идут команды:

MOV ESP, EBP POP EBP RET М

Здесь M - объем, взятый у стека для передачи параметров.

Такого же результата можно добиться, используя команду ENTER N,0 (PUSH EBP\MOV EBP,ESP\SUB ESP) в начале процедуры и LEAVE (MOV ESP,EBP\POP EBP) в конце процедуры.

Эти команды появились еще у 286-ого процессора и дали возможность несколько оптимизировать транслируемый код программы, особенно в тех случаях, когда речь идет о больших по объему модулях, создаваемых на языке высокого уровня.

Хотелось бы остановиться еще на одном вопросе, связанным со структурой процедуры и ее вызова.

Существуют два основных подхода (см. [1]). Условно первый подход можно назвать Си-подходом, а второй — Паскаль-подходом.

Первый подход предполагает, что процедура "не знает", сколько параметров находится в стеке. Естественно, в этом случае освобождение стека от параметров должно происходить после команды вызова процедуры, например, с помощью команды POP или команды ADD ESP,N (N - количество байт в параметрах).

Второй подход основан на том, что количество параметров фиксировано, а стек можно освободить в самой процедуре. Это достигается выполнением команды RET N (N - количество байт в параметрах). Как Вы уже, наверное, догадались, вызов функций API осуществяется по второй схеме. Впрочем, есть и исключения, о которых вы узнаете несколько позже (см. Гл. 2.1).



Вопрос об использовании данных



IV

Рассмотрим теперь менее важный (для нас) вопрос об использовании данных (переменных), определенных в другом объектном модуле. Здесь читателю, просмотревшему предыдущий материал, должно быть все понятно, а модули PROG2.ASM и PROG1.ASM, демонстрирующие технику использования внешних9 переменных, приводятся на Рисунок 1.8-1.9.

.386P ; модуль PROG2.ASM ; плоская модель .MODEL FLAT, STDCALL PUBLIC PROC1 PUBLIC ALT ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' ALT DWORD 0 _DATA ENDS _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' PROC1 PROC MOV EAX,ALT ADD EAX,10 RET PROC1 ENDP _TEXT ENDS END



Второй пример касается использования окна редактирования



III

Второй пример касается использования окна редактирования. Результат работы программы показан на Рисунок 1.3.3, а сама программа — на Рисунок 1.3.2. При нажатии кнопки "Выход" в начале появляется окно-сообщение с отредактированной строкой.

Обратите внимание на то, как осуществляется посылка сообщения окну (управляющему элементу). Для этого используют в основном две функции: SendMessage и PostMessage. Отличие их друг от друга заключается в том, что первая вызывает процедуру окна с соответствующими параметрами и ждет, когда та возвратит управление; вторая функция ставит сообщение в очередь и сразу возвращает управление.

; файл edit.inc ; константы WM_SETFOCUS equ 7h ; сообщение приходит при закрытии окна WM_DESTROY equ 2 ; сообщение приходит при содании окна WM_CREATE equ 1 ; сообщение, если что-то происходит с элементами на окне WM_COMMAND equ 111h ; сообщение, позволяющее послать элементу строку WM_SETTEXT equ 0Ch ; сообщение, позволяющее получить строку WM_GETTEXT equ 0Dh ; свойства окна CS_VREDRAW equ 1h CS_HREDRAW equ 2h CS_GLOBALCLASS equ 4000h WS_TABSTOP equ 10000h WS_SYSMENU equ 50000h WS_OVERLAPPEDWINDOW equ 0+WS_TABSTOP+WS_SYSMENU STYLE equ CS_HREDRAW+CS_VREDRAW+CS_GLOBALCLASS CS_HREDRAW equ 2h BS_DEFPUSHBUTTON equ 1h WS_VISIBLE equ 10000000h WS_CHILD equ 40000000h WS_BORDER equ 800000h STYLBTN equ WS_CHILD+BS_DEFPUSHBUTTON+WS_VISIBLE+WS_TABSTOP STYLEDT equ WS_CHILD+WS_VISIBLE+WS_BORDER+WS_TABSTOP ; идентификатор стандартной иконки IDI_APPLICATION equ 32512 ; идентификатор курсора IDC_ARROW equ 32512 ; режим показа окна — нормальный SW_SHOWNORMAL equ 1 ; прототипы внешних процедур EXTERN SetFocus@4: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 ; структуры ; структура сообщения 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




; файл edit.asm .386P ; плоская модель .MODEL FLAT, stdcall include edit.inc ; директивы копоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\iib\kernel32.lib ;------------------------------------------------------------ ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' NEWHWND DD 0 MSG MSGSTRUCT <?> WC WNDCLASS <?> HINST DD 0 ; дескриптор приложения TITLENAME DB 'Пример — окно редактирования',0 CLASSNAME DB 'CLASS32',0 CPBUT DB 'Выход',0 ; выход CPEDT DB ' ',0 CLSBUTN DB 'BUTTON',0 CLSEDIT DB 'EDIT',0 HWNDBTN DWORD 0 HWNDEDT DWORD 0 CAP DB 'Сообщение',0 MES DB 'Конец работы программы',0 TEXT DB 'Строка редактирования',0 DB 50 DUP(0) ; продолжение буфера _DATA ENDS

; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения 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], 0 MOV DWORD PTR [WC.CLNAME], OFFSET CLASSNAME PUSH OFFSET WC CALL RegisterClassA@4 ; создать окно зарегистрированного класса PUSH 0 PUSH [HINST] PUSH 0 PUSH 0 PUSH 150 ; DY - высота окна PUSH 400 ; DX - ширина окна PUSH 100 ; Y - координата левого верхнего угла PUSH 100 ; X - координата левого верхнего угла PUSH WS_OVERLAPPEDWINDOW PUSH OFFSET TITLENAME ; имя окна PUSH OFFSET CLASSNAME ; имя класса PUSH 0 CALL CreateWindowExA@48 ; проверка на ошибку CMP EAX, 0 JZ _ERR MOV [NEWHWND], EAX ; дескриптор окна PUSH SW_SHOWNORMAL PUSH [NEWHWND] CALLShowWindow@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 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_COMMAND JE WMCOMMND JMP DEFWNDPROC WMCOMMND: MOV EAX,HWNDBTN CMP DWORD PTR [EBP+14H],EAX JNE NODESTROY ; получить отредактированную строку PUSH OFFSET TEXT PUSH 150 PUSH WM_GETTEXT PUSH HWNDEDT CALL SendMessageA@16 ; показать эту строку PUSH 0 PUSH OFFSET CAP PUSH OFFSET TEXT PUSH DWORD PTR [EBP+08H] ; дескриптор окна CALL MessageBoxA@16 ; на выход JMP WMDESTROY NODESTROY: 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 ; запомнить дескриптор кнопки ; создать окно редактирования PUSH 0 PUSH [HINST] PUSH 0 PUSH DWORD PTR [EBP+08H] PUSH 20 ; DY PUSH 350 ; DX PUSH 50 ; Y PUSH 10 ; X PUSH STYLEDT PUSH OFFSET CPEDT ; имя окна PUSH OFFSET CLSEDIT ; имя класса PUSH 0 CALL CreateWindowExA@48 MOV HWNDEDT,EAX ;--------- поместить строку в окно редактирования PUSH OFFSET TEXT PUSH 0 PUSH WM_SETTEXT PUSH HWNDEDT CALL SendMessageA@16 ;--------- установить фокус на окне редактирования PUSH HWNDEDT CALL SetFocus@4 ;------------------------------------------------------------ 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


Вводная информация



I

В данной главе я намерен дать некоторую вводную информацию по средствам программирования на языке ассемблера. Данная глава предназначена для начинающих программирование на ассемблере, поэтому программистам более опытным ее можно пропустить без особого ущерба для себя.

Прежде всего замечу, что в названии главы есть некоторая натяжка, т.к. технологии трансляции и в MS DOS, и в Windows весьма схожи. Однако программирование в MS DOS уходит в прошлое.