Cамоучитель по Assembler

         

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

Глава 1. Средства программирования в Windows

I

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

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

Рис.1.1.1. Схема трансляции ассемблерного модуля.

Двум стадиям трансляции (Рис.1.1.1) соответствуют две основные программы: ассемблер ML.EXE и редактор связей LINK.EXE 7 (или 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

Рис.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

Рис. 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 Правда, весьма ограниченно, т.к. согласование системных вызовов в разных операционных системах может весьма сильно различаться.


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

Рис. 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

Рис. 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

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

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

Рис. 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.1.7. Пример использование библиотеки.

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

Рис. 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.9. Модуль, использующий переменную ALT, определенную в другом модуле (PROG2.ASM).

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


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


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

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


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

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

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


VI

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

Редакторы. Хотя сам я никогда не использую специализированные редакторы для написания программ на ассемблере, полноты ради кратко расскажу о двух известных мне редакторах. Начну с редактора 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, а также с другими утилитами (отладчиками, редакторами ресурсов и т.д.). Непосредственно в программе можно настроить компиляторы и редакторы связей на определенный режим работы путем задания ключей этих утилит.

Отладчики.

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

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

Дизассемблеры переводят исполняемый модуль в ассемблерный код. Примером простейшего дизассемблера является программа 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

Рис. 1.1.11. Пример дизассемблированш программы с помощью DUMPPE.EXE.

Отмечу также дизассемблер W32Dasm, который подробно будет описан в последней части книги, и знаменитый дизассемблер IDA Pro. В части IV мы будем подробно рассматривать и сами дизассемблеры, и методику их использования.

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

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

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

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

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

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


14 Программа DEBUGER.EXE все еще поставляется с операционной системой 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).


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. В такой программе имеется главное окно, а следовательно, и процедура главного окна. В целом, в коде программы можно выделить следующие секции:
Регистрация класса окон Создание главного окна Цикл обработки очереди сообщений Процедура главного окна

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

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

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

Цикл обработки очереди сообщений. Вот как выглядит этот цикл на языке Си:

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, см. ниже). Таким образом, цикл ожидания играет двоякую роль: определенным образом преобразуются сообщения, предназначенные для какого-либо окна, и ожидается сообщение о выходе из программы.

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

Вот прототип функции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

Puc. 1.2.1. "Скелет" оконной процедуры.

Прокомментируем фрагмент на Рис. 1.2.1.

RET 16 — выход с освобождением стека от четырех параметров (16=4*4).
Доступ к параметрам осуществляется через регистр EBP:
DWORD PTR [EBP+14H]; LPARAM (lParam)
DWORD PTR [EBP+10H]; WPARAM (wParam)
DWORD PTR [EBP+0CH]; MES (message) — код сообщения
DWORD PTR [EBP+08H]; HWND (hwnd) — дескриптор окна.

Функция DefWindowProc вызывается для тех сообщений, которые не обрабатываются в функции окна. В данном примере, как Вы понимаете, не обрабатываются все приходящие в функцию окна сообщения.


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

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


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

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

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.2.3. Простой пример программы для Windows (TASM32).

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

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

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

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

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


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


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).

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








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

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

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 Хотя на самом деле вызывается процедура окна с соответствующими значениями параметров, мы и в дальнейшем будем говорить о посылке окну сообщения.


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

Puc. 1.3.1. Пример окна с кнопкой выхода.

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

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

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

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.





Глава 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

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

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

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








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

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


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


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

Программа ML.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Выполнять только проверку синтаксиса.

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

ПараметрКомментарий
/? или /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Не помещать в объектный код отладочной информации.

Программа 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). Операционная система удаляет данное приложение из памяти в случае его простоя.

Программа 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.

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

Включение в исполняемый файл отладочной информации. Если в исполняемый файл была включена отладочная информация, то фирменный отладчик позволяет работать одновременно и с текстом программы, и с дизассемблированным кодом. Это особенно удобно для языков высокого уровня, а для программ на ассемблере это весьма мощный инструмент отладки. Пусть текст программы содержится в файле 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 представлено окно отладчика с отлаживаемой программой. Можно видеть, что на экране имеется и текст программы, и ее дизассемблированный код. Более подробно об отладчиках мы будем говорить в последней части книги.

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

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

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

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

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