<<
>>

Каркасное Windows-приложение на ассемблере

Одним из главных критериев выбора языка разработки Windows-приложения яв- ляется наличие в нем средств, способных поддержать строго определенную после- довательность шагов. Язык ассемблера является универсальным языком и приго- ден для реализации любых задач, поэтому можно смело предположить, что на нем можно написать также любое Windows-приложение.
Материал, изложенный ра- нее, наглядно это доказал. Более того, стали видны некоторые подробности кода, который должно содержать Windows-приложение на ассемблере. Но мало напи- сать сам текст Windows-приложения, необходимо знать средства пакета трансля- тора, специально предназначенные для разработки таких приложений, и уметь пользоваться этими средствами. В листинге 16.4 приведен текст каркасного приложения на ассемблере, функ- ционально эквивалентного Windows-приложению на C/C++ (см. листинг 16.1). Если не обращать внимания на особенности оформления кода, обусловленные тре- бованиями синтаксиса ассемблера, то хорошо видно, что на уровне функций его структура аналогична рассмотренному ранее Windows-приложению на C/C++.
Листинг 16.4. Каркасное Windows-приложение на ассемблере ;Пример каркасного приложения для Win32 .386 locals ;разреiает применение локальных меток в программе .model flat, SТDСАLL ;модель памяти flat ;SТDСАLL - передача параметров в стиле С (сщзава налево), ; вызываемая процедура чистит за собой стек include windowA.iпс;включаемый файл с описаниями базовых структур ;и 'констант Win32 ;Объявление внешними используемых в данной программе ;функций Win32 (ASCII): ехtrп GеtМоduIеНапdlеА: РRОС продолжение &
Листинг 16.4 (продолжение)

Листинг 16.4 (продолжение)

Каркасное Windows-приложение на ассемблере содержит один сегмент данных .data и один сегмент кода .code.
Сегмент стека в исходных текстах Windows-прило- жений непосредственно описывать не нужно. Windows выделяет для стека объем памяти, размер которого задан программистом в файле с расширением .dеf. Текст листинга 16.4 довольно большой. Поэтому для обсуждения разобьем его коммен-

тариями на характерные фрагменты, каждый из которых затем поясним с необхо- димой степенью детализации. Строка 3. Все символические имена в программе на ассемблере по умолчанию являются глобальными. Задание директивы LOCALS включает в трансляторе меха- низм контроля областей видимости имен и позволяет использовать в программе локальные имена (см. главу 10). Строка 4. Директива .MODELзадает модель сегментации (flat) и стиль генерации (см. главу 15) кода при входе в процедуры программы и выходе из них (stdcall). Модель памяти flat обозначает плоскую модель памяти. В соответствии с этой мо- делью компилятор создает программу, которая содержит один 32-разрядный сег- мент для данных и кода программы. Код загрузочного модуля, генерируемый с параметром flat, будет работать на процессорах iЗ86 и выше. По этой причине директиве .MODEL должна предшествовать одна из директив: .386, .486 или .586. Указание этой модели памяти заставляет компоновщик создать исполняемый файл с расширением .ехе. В программе с плоской моделью памяти используется адреса- ция программного кода и данных типа near. Это же касается и атрибута расстояния в директиве PROC, который также имеет тип near. Параметр stdcall определяет по- рядок передачи параметров через стек так, как это принято в языке C/C++, то есть справа налево. Этот параметр задает генерацию кода эпилога, который очищает стек по завершении работы процедуры (в стиле языка Pascal) от аргументов, пере- данных в эту процедуру при вызове. Такая передача параметров очень удобна при разработке Windows-приложений: программист может помещать параметры в стек в прямом порядке, и ему не нужно заботиться об очистке стека после окончания работы процедуры.

Строка 7. Директива INCLUDE включает в программу файл wiпdоwА.iпс. Для чего нужен этот файл? Вы, наверняка, имеете некоторый опыт разработки Windows- приложений на C/C++ и знаете, что функции Win32 API в качестве передаваемых им параметров используют множество констант и данных определенной структу- ры. В пакете компилятора C/C++ эти данные расположены в заголовочных фай- лах, совокупность которых можно рассматривать как дерево с корнем в файле windows.h. Для того чтобы эти описания стали доступны приложению на C/C++, в его начало включается директива #iпсludе (см. листинг 16.1). Windows- приложение на ассемблере также использует вызовы функций Win32 API, поэто- му в нем должны быть выполнены аналогичные действия по определению необхо- димых констант и структур. Но просто взять из пакета C/C++ и затем использовать в программе на ассемблере файл windows.h и связанные с ним файлы напрямую нельзя. Причина банальна: транслятор ассемблера не понимает синтаксиса C/C++. Что делать? Пакет ТАSМ предоставляет в распоряжение программиста утилиты h2ash.exe и h2ash32.exe, которые должны помочь ему конвертировать содержимое включаемых файлов на языке C/C++ во включаемые файлы ассемблера. Однако, исходя из опыта, не стоит питать особых надежд на эффективность их работы. Поэтому проще формировать ассемблерный включаемый файл с описанием кон- стант и структур Windows самостоятельно. Кстати, пакет TASM 5.0 предоставля- ет в распоряжение программиста вариант такого включаемого файла wiпЗ2.iriс. Его можно найти среди прилагаемых к книге файлов в каталоге к данной главе, но пользоваться им нужно с осторожностью, так как его содержимое не совсем акту- ально. Например, файл wiпЗ2.i пс содержит описание структуры WNDCLASS, но в нем отсутствует расширенный вариант этой структуры WNDCLASSEX, который требует- ся в расширенном варианте функции RеgistеrСlаssЕхА. Как решить проблему акту- альности описаний? Проще всего сделать это, используя включаемые файлы по- следних на текущий момент времени версий компилятора C/C++ (сейчас это, например, VC++ 6.0 и 7.0).
Это один из основных источников подобной информа- ции, актуальность которой к тому же гарантирована разработчиком компиля- тора, являющегося одновременно и создателем операционной системы Windows. Поэтому программирование на ассемблере для Windows предполагает, что вы хо- рошо умеете ориентироваться во включаемых файлах компилятора C/C++. Пол- ностью пытаться конвертировать эти файлы не имеет смысла. Более правильно извлекать из них по мере необходимости описание нужных данных, приводить их в соответствие требованиям синтаксиса ассемблера и после этого записывать в не- который свой файл, который будет играть роль файла windows.h. Для Windows- приложений данной главы вам предлагается вариант такого включаемого фай- ла — ^^^^mvА.iпс, который включается в текст приложения директивой include windowA.inc. Файл windowA.inc имеется среди файлов, прилагаемых к книге. Его можно взять за основу для дальнейшей работы и наращивать по мере необхо- димости. Строки 9-33. Функции Win32 API, используемые в программе, должны быть объявлены внешними с помощью директивы EXTRN. Это необходимо для того, что- бы компилятор мог сгенерировать правильный код, так как тела функций Win32 API содержатся в библиотеках DLL системы Windows. Имейте в виду, что в различных источниках встречаются разные названия, ка- залось бы, одной и той же функции. Необходимо правильно понимать их. Напри- мер, уже упомянутая функция RеgistеrСLаss может иметь названия RegisterClassExA или RegisterClassExW. На самом деле две последние функции являются более совре- менными вариантами RegisterClass. Для разрешения подобных коллизий приходится обращаться к первоисточникам — документации с описанием функций Win3 2 API (помните, что она также может оказаться устаревшей). Самый лучший источник такой информации — включаемые файлы компилятора C/C++. Например, описа- ние функции RедistеrСlаss(ЕхА) содержится в файле winuser.h. В листинге 16.5 при- ведены строки из этого файла. Листинг 16.5. Фрагмент файла winuser.h (VC++ 6.0) WINUSЕRАРI АТОМ WINАРI RеgistеrСlаssА(СОNSТ WNDСLАSSА *lрW∩dСlаss) ; WINUSЕRАРI АТОМ WINАРI RеgistеrСlаssW(СОNSТ WNDСLАSSW *lрWпdСlаss); #ifdеf UNICODE , #dеfiпе RegisterClass Rеgi stеrСlаssW #еlsе #dеfiпе RegisterClass RеgistеrСlаssА #епdif // ! UNICODE #if(WINVЕR >= 0x0400) WINUSЕRАРI АТОМ WINАРI Rеgi stеrСlаssЕхА(СОNSТ WNDСLАS5ЕХА *); WINUSЕRАРI АТОМ WINАРI RеgistеrСlаssЕхW(СОNSТ WNDСLАSSЕХW *); #ifdеf UNICODE #dеfiпе Rедi stеrСlаssЕх RegisterClassExW _ Λ продолжение У Листинг 16.5 (продолжение) #еlsе #dеfiпе RegisterClassEx RеgistеrСlаssЕхА tfепdif // ! UNICODE Последние версии операционных систем Windows поддерживают две системы кодировки символов: однобайтную (ANSI) и двухбайтную (UNICODE).
Поддерж- ка кодировки UNICODE была введена Microsoft, чтобы облегчить локализацию программных продуктов на неанглоязычном рынке. Операционная система Windows NT поддерживает только кодировку UNICODE. Операционная система Windows 95/98 имеет довольно плохо скрытое наследие MS-DOS и не поддержи- вает в своей внутренней работе кодировку UNICODE (она поддерживается лишь на уровне функций Win32 API). Для работы с обеими кодировками программный интерфейс Win32 API имеет два варианта функций. Эти функции различаются последним символом в названии. Если это А, то данная функция работает в коди- ровке ANSI, если W, то функция работает в кодировке UNICODE. Следующий момент, требующий пояснения, — наличие суффикса Ех в названиях функций. Объяснение этому можно найти в листинге 16.5. Директива условной компиляции (строка 8) проверяет текущую версию операционной системы. Если это Windows 95/98 или NT, то можно использовать расширенные (с суффиксами Ех) варианты функций Win32 API. Они обладают дополнительными возможностями по сравне- нию со старыми вариантами функций Win32 API. Исчерпывающим источником информации по функциям Win32 API (и не только) является MSDN (Microsoft Developer Network — информационная система поддержки разработчика по про- дуктам Microsoft), его можно найти в Интернете по адресу httр://www. microsoft.com/ msdп/. Строка 35. В соответствии с соглашениями операционной системы Windows, оконная функция приложения должна быть видимой за пределами приложения, в котором она описана. Это связано с тем, что оконная функция вызывается самой операционной системой Windows при поступлении сообщений для данного при- ложения. По этой причине оконная функция нашего каркасного приложения объяв- лена общей (public). Строки 36-50 содержат описание сегмента данных, в котором определяются переменные и экземпляры структур, используемые в каркасном приложении. Упомянем еще об одном важном техническом моменте программирования для операционной системы Windows — соглашениях об именовании различных про- граммных объектов.
Обозначениям в Windows придается большое значение. Это объясняется сложностью разрабатываемых систем, а также тем, что пользователи и разработчики должны понимать друг друга при создании как программных про- дуктов, так и документации к ним. Без согласованных правил по обозначению тех или иных объектов им не обойтись. Единых требований на этот счет нет. В насто- ящее время де-факто в качестве системы обозначений принята так называемая форма венгерской записи. Более подробные сведения о ней с указанием первоис- точника приведены в файле \Lеssопlб\Нuпдагiап Notation.htm, который находится среди файлов, прилагаемых к книге. Не стоит принимать венгерскую форму запи- си за некую догму. Это всего лишь одна из возможных систем обозначений. При желании вы можете ввести свою собственную систему. Для того чтобы заставить компилятор ассемблера различать строчные и прописные буквы, необходимо ука- зать ключ /ml в командной строке. Перед рассмотрением сегмента кода обратите внимание на его начало (стро- ка 51). В нем отсутствуют привычные команды загрузки сегментного регистра дан- ных. Загрузчик Windows самостоятельно загружает сегментные регистры, при этом учитывается требуемая модель памяти (директива .model). В первой части главы мы упоминали, что для запуска приложения под управлением Windows необходи- мо выполнить нескольких шагов, в которых выполняется вызов ряда функций Win32 API. Перечислим их. 1. Выполнение стартового кода. 2. Выполнение главной функции (в C/C++ — вызов функции WiпМаiп), которая выполняет следующие действия: 1) регистрирует класс окна; 2) создает окно; 3) отображает окно; 4) запускает цикл обработки сообщений; 5) завершает выполнение приложения. 3. Организация обработки сообщений в оконной процедуре. А как вызывать функции Win32 API в программе на ассемблере? Вызов этих функций осуществляется аналогично вызову внешних функций (см. главу 15). Передача всех параметров осуществляется через стек. По этой причине важно знать размеры передаваемых величин. Здесь проявляется полезность венгерской систе- мы обозначений. О размере переменной можно судить по ее названию. К тому же в Win32 большинство переменных имеет размер двойного слова (четыре байта). В соответствии с параметром stdсаLLдирективы .MODEL параметры в стек должны по- мещаться справа налево, то есть первым в стек идет последний параметр функции. Продолжим рассмотрение каркасного Windows-приложения (см. листинг 16.4).
<< | >>
Источник: В. И. Юров. Assembler. Учебник для вузов. 2-е изд. 2003

Еще по теме Каркасное Windows-приложение на ассемблере:

  1. М.Руссинович, Д.Соломон. Внутреннее устройство Microsoft Windows (главы 1–4), 2005
  2. ПРИЛОЖЕНИЯ
  3. Приложения
  4. ПРИЛОЖЕНИЕ
  5. Приложение
  6. Приложение 1.
  7. Приложение 3.
  8. Приложение 4.
  9. Приложение 5.
  10. ПРИЛОЖЕНИЕ 1
  11. Приложение