Министерство образования Пензенской области
ГБПОУ ПО «Кузнецкий колледж электронной техники»
ПОЯСНИТЕЛЬНАЯ ЗАПИСКА
к курсовому проекту (работе)
по дисциплине МДК 01.01 «Системное программирование»
на тему «Разработка Windows-приложений на языке ассемблера»
Выполнил Ахметов Т.Д. ____________
(подпись)
Группа 3п5-11
Специальность 230115
Руководитель Демаева С.Н ____________
(подпись)
Проект (работа) защищен (а) «___» ___________20__г.
Оценка «___» ___________
2014
Реферат
Сегодня трудно найти компьютер, на котором бы не была установлена одна из версий Windows, в реферате пойдет речь о программировании для Windows на платформе Intel.
В подавляющем большинстве книг о программировании для Windows изложение, как правило, ведется на базе языков C/C++, реже — на базе Pascal. Курсовая работа направлена на правильное понимание места ассемблера в архитектуре компьютера. Любая программа на языке самого высокого уровня по сути представляет собой последовательность машинных кодов. А раз так, то всегда остается теоретическая возможность написать ту же программу, но уже на языке ассемблера. Чем можно обосновать необходимость разработки Windows-приложений на языке ассемблера? Приведем следующие аргументы:
Язык ассемблера позволяет программисту полностью контролировать создаваемый им программный код и оптимизировать его по своему усмотрению;
Компиляторы языков высокого уровня помещают в загрузочный модуль программы избыточную информацию, поэтому эквивалентные исполняемые модули, исходный текст которых написан на ассемблере, имеют в несколько раз меньший размер;
При программировании на ассемблере сохраняется полный доступ аппаратным ресурсам компьютера;
Приложение, написанное на ассемблере, как правило, быстрее загружается в оперативную память компьютера;
Приложение, написанное на ассемблере, обладает, как правило, более высокой скоростью работы и ответа на действия пользователя.
Разумеется, эти аргументы не следует воспринимать, как некоторую рекламную кампанию в поддержку языка ассемблера. Тем более что компиляторы языков высокого уровня постоянно совершенствуются и подчас способны создавать код, весьма близкий по эффективности к ассемблерному. По этой причине приведенные аргументы не являются бесспорными. И все же нельзя забывать о том, что существует бесконечное множество прикладных задач, ждущих своей очереди на компьютерную реализацию. Далеко не все из этих задач требуют тяжеловесных средств разработки — многие из них могут быть изящно исполнены на языке ассемблера, не теряя привлекательности, например, оконных Windows-приложений.
Перед началом обсуждения поясним, в чем состоит разница между программированием для DOS и для Windows. Операционные системы MS-DOS и Windows поддерживают две совершенно разные идеологии программирования. В чем разница? Программа DOS после своего запуска должна быть постоянно активной.
Если ей, к примеру, требуется получить очередную порцию данных с устройства ввода-вывода, то она сама должна выполнять соответствующие запросы к операционной системе. При этом программа DOS работает по определенному алгоритму, она всегда знает, что и когда ей следует делать. В Windows все наоборот. Программа пассивна.
После запуска она ждет, когда ей уделит внимание операционная система. Операционная система делает это посылкой специально оформленных групп данных, называемых сообщениями. Сообщения могут быть разного типа, они функционируют в системе довольно хаотично, и приложение не знает, какого типа сообщение придет следующим. Отсюда следует, что логика построения Windows-приложения должна обеспечивать корректную и предсказуемую работу при поступлении сообщений любого типа. Тут можно провести определенную аналогию между механизмом сообщений Windows и механизмом прерываний в архитектуре IBM PC. Для нормального функционирования своей программы программист должен уметь эффективно использовать функции интерфейса прикладного программирования (Application Program Interface, API) операционной системы.
Windows поддерживает два типа приложений.
Оконное приложение строится на базе специального набора функций API, составляющих графический интерфейс пользователя (Graphic User Interface,
GUI). Оконное приложение представляет собой программу, которая весь вывод на экран производит в графическом виде. Первым результатом работы оконного приложения является отображение на экране специального объекта — окна. После того как окно появилось на экране, вся работа приложения направлена на то, чтобы поддерживать его в актуальном состоянии.
Неоконное приложение, также называемое консольным, представляет собой программу, работающую в текстовом режиме. Работа консольного приложения напоминает работу программы MS-DOS. Но это лишь внешнее впечатление.
Поддержка работы консольного приложения обеспечивается специальными функциями Windows.
Вся разница между двумя типами Windows-приложений состоит в том, с каким типом информации они работают. Основной тип приложений в Windows — оконные, поэтому с них мы и начнем знакомство с процессом разработки программ для этой операционной системы.
Курсовая работа посвящена созданию терминальных и графических приложений для WlN32.
В введении рассматривается актуальность выбранной темы, ставятся цели курсового проектирования.
В 1 разделе автор рассматривает особенности построения консольных и графических приложений.
Второй раздел автор посвятил рассмотрению необходимых процедур и функций.
В заключении делается вывод о проделанной работе.
ВведениеЯзык ассемблера — это символическое представление машинного языка.
Все процессы в машине на самом низком, аппаратном уровне приводятся в действие только командами (инструкциями) машинного языка. Отсюда понятно, что, несмотря на общее название, язык ассемблера для каждого типа компьютера свой. Это касается и внешнего вида программ, написанных на ассемблере, и идей, отражением которых этот язык является.
По-настоящему решить проблемы, связанные с аппаратурой (или даже, более того, зависящие от аппаратуры как, к примеру, повышение быстродействия программы), невозможно без знания ассемблера.
Программист или любой другой пользователь может использовать любые высокоуровневые средства, вплоть до программ построения виртуальных миров и, возможно, даже не подозревать, что на самом деле компьютер выполняет не команды языка, на котором написана его программа, а их трансформированное представление в форме скучной и унылой последовательности команд совсем другого языка — машинного. А теперь представим, что у такого пользователя возникла нестандартная проблема или просто что-то не заладилось. К примеру, его программа должна работать с некоторым необычным устройством или выполнять другие действия, требующие знания принципов работы аппаратуры компьютера. И вот здесь-то и начинается совсем другая история.... Каким бы умным ни был программист, каким бы хорошим ни был язык, на котором он написал свою чудную программу, без знания ассемблера ему не обойтись. И не случайно практически все компиляторы языков высокого уровня содержат средства связи своих модулей с модулями на ассемблере либо поддерживают выход на ассемблерный уровень программирования.
Из всего вышесказанного можно сделать вывод, что, так как язык ассемблера для компьютера “родной”, то и самая эффективная программа может быть написана только на нем (при условии, что ее пишет квалифицированный программист). Здесь есть одно маленькое “но”: это очень трудоемкий, требующий большого внимания и практического опыта процесс. Поэтому реально на ассемблере пишут в основном программы, которые должны обеспечить эффективную работу с аппаратной частью. Иногда на ассемблере пишутся критичные по времени выполнения или расходованию памяти участки программы. Впоследствии они оформляются в виде подпрограмм и совмещаются с кодом на языке высокого уровня.
Содержание
1. Создание терминальных приложений для Win32 …………………..…..1
1.1. Вводная информация …………………………………………………….2
1.2. Терминальные функции Win32 …………………………………………..4
1.3. Чтение данных с терминала ……………………………………………...
1.4. Вывод на терминал ……………………………………………………….
1.5. Файловый ВВОД-ВЫВОД ……………………………………………….
1.6. Операции с окном терминала ……………………………………………
1.7. Управление курсором ……………………………………………………
1.8. Изменение цвета текста …………………………………………………
1.9. Функции для работы со временем и датой ……………………………
2. СОЗДАНИЕ ГРАФИЧЕСКИХ ПРИЛОЖЕНИЙ ДЛЯ WINDOWS
2.1. Необходимые структуры ………………………………………………
2.2. Функция MessageBox …………………………………………………..
2.3. Процедура WinMain ……………………………………………………
2.4. Процедура WinProc …………………………………………………….
2.5. Процедура ErrorHandler ………………………………………………..
2.6. Листинг программы ……………………………………………………
3. УПРАВЛЕНИЕ ПАМЯТЬЮ В ПРОЦЕССОРАХ СЕМЕЙСТВА IA-32
3.1.Линейные адреса ………………………………………………………
3.2. Страничная переадресация …………………………………………...
3.5. Процедура ReadString ………………………………………………..
3.6. Ввод и вывод строк …………………………………………………..
3.7.0чисткаэкрана …………………………………………………………
3.8. Случайный вывод на экран ………………………………………...
3.9. Рисование прямоугольников ……………………………………...
3.10. Программа регистрации учащихся ……………………………….
3.11. Прокрутка текстового окна ………………………………………..
3.12. Блочная анимация…………………………………………………..
Заключение………………………………………………………………72
1. Создание терминальных приложений для Win32
При чтении этой книги у вас наверняка возник ряд вопросов наподобие тех, что перечислены ниже.
• Как в 32-разрядных программах выполняется ввод-вывод текстовых данных?
• Как в 32-разрядных программах вывести на экран терминала цветной текст?
• Как работает библиотека объектных модулей lrvine32?
• Как в системе Microsoft Windows выполняются операции с датой и временем?
• Какие функции системы Microsoft Windows используются для чтения и записи данных в файлы?
• Можно ли на языке ассемблера написать фактическое приложение для системы Windows?
• Как в защищенном режиме преобразовываются адреса в форме
"сегмент-смещение", используемые в программе, в физические адреса?
• Что такое виртуальная память и как она работает?
В этой главе вы найдете ответы на эти и многие другие вопросы, поскольку здесь речь пойдет об основах 32-разрядного программирования для системы Microsoft Windows.
Большую часть времени мы посвятим созданию 32-разрядных терминальных
приложений, работающих в текстовом режиме, поскольку их легче всего запрограммировать. Мы опишем необходимые структуры и параметры функций.
При написании процедур библиотеки lrvine32.1ib были использованы только терминальные функции системы Win32,
поэтому вы сможете сравнить их исходный код с теми сведениями, которые вы почерпнете из этой главы.
Вы спросите, почему бы нам не начать рассмотрение с написания фактического приложения для системы Windows, к которым мы все уже так привыкли? Основная причина ~ оно получится слишком длинным и в нем нужно будет учесть много разных моментов.
О том, как писать программы на языках С и С++ для системы Windows, уже написано много хороших книг, в которых рассматриваются масса технических деталей, таких как дескрипторы графических устройств, обработка сообщений, графики шрифтов, битовые карты устройств и режимы отображения. Однако на просторах Web существует несколько групп увлеченных языком ассемблера программистов, которые хорошо разбираются в вопросах низкоуровневого программирования для Windows.
Однако тот, кто хочет писать на ассемблере фактические приложения для Windows, не будет совсем уж разочарован. В разделе 11.2 мы в общих чертах рассмотрим небольшое 32-разрядное фактическое приложение для Windows, которое можно считать хорошей отправной точкой для дальнейшего изучения этого вопроса. Дело в том, что тема программирования для Windows на ассемблере очень увлекательна. Поэтому для тех, кто захочет изучить этот вопрос самостоятельно, в конце этой главы будет приведен список рекомендованных книг.
1.1. Создание терминальных приложений для Win32 485
На первый взгляд 32-разрядные терминальные программы для Windows очень похожи на 16-разрядные программы для MS DOS, работающие в текстовом режиме. Оба типа программ выполняют чтение данных со стандартного устройства ввода и записывают данные в стандартное устройство вывода. Они поддерживают перенаправление потоков данных из командной строки и могут вывести на экран текстовые данные в цвете. Однако при более детальном рассмотрении 32-разрядные терминальные программы для Windows существенно отличаются от 16-разрядных программ для MS DOS. Они используют 32-разрядный защищенный режим работы процессора, тогда как программы для MS DOS работают в реальном режиме адресации. По понятным причинам в этих программах используются совершенно разные библиотеки функций. Терминальные приложения, написанные для Win32, вызывают функции из той же библиотеки, что и другие фактические приложения системы Windows. В программах для MS DOS используются функции BIOS и системы DOS, вызываемые посредством программных прерываний, механизм которых 6bui придуман еще при разработке первой модели персонального компьютера IBM PC.
В системе Windows для обращения к функциям используется стандартный интерфейс прикладных программ (Application Programming Interface, или APF), который представляет собой набор определений структур, констант и функций, использующихся во всех программах. Благодаря этому обеспечивается возможность прямого манипулирования любым объектом системы посредством обычных вызовов функций. Таким образом, API Win32 позволяет использовать объекты, составляющие 32-разрядную версию системы MS Windows. Интерфейс прикладных программ (AP1) Win32 подробно описан в документе, озаглавленном Platform SDK, изданном фирмой Microsoft. Аббревиатура SDK расшифровывается как Software Development Kit, или набор инструментальных средств разработки программного обеспечения. Он состоит из различных утилит, библиотек, примеров программного кода и документации, которая помогает программистам создавать приложения для системы Windows. В названии SDK присутствует слово platform (т.е. платформа), под которым подразумевается определенный тип операционной системы или группы связанных между собой операционных систем.
1.2. Вводная информация
При запуске любого приложения в системе Windows для него создается либо текстовое (терминальное), либо фактическое окно. Для создания терминального приложения при компоновке программы необходимо указать в командной строке программы LINK указанную ниже опцию (мы используем ее командном файлетаке32. bat):
/SUBSYSTEM:CONSOLE
В терминальных программах данные читаются со стандартного устройства ввода, а выводиться информация может либо на стандартное устройство вывода, либо на стандартное устройство вывода сообщений об ошибках. Для терминального приложения создается один входной буфер и один или несколько буферов для вывода на экран, как описано ниже.
• Входной буфер состоит из очереди входных записей, каждая из которых содержит информацию об одном событии, поступившем от какого-либо устройства ввода. В качестве примеров таких событий можно привести нажатие на клавишу на клавиатуре, щелчок кнопкой мыши либо изменение пользователем размеров терминального окна.
• Буфер экрана представляет собой двумерный массив, элементы которого содержат данные и атрибуты, влияющие на внешний вид текста, отображаемого на экране терминала.
1.3. Терминальные функции Win32
В табл. 1 приведен полный список терминальных функций Win32 и их краткое описание.
Функция |
Описание |
AllocConsole |
Создать новый терминал для вызывающего процесса |
CreateConsoleScreenBuffer |
Создать буфер экрана для терминала |
FillConsoleOutputAttribute |
Устанавливает цвет символов и фона для указанного числа текстовых ячеек |
FillConsoleOutputCharacter |
Выводит символ на экран указанное число раз |
FlushConsolelnputBuffer |
Очищает входной буфер терминала |
FreeConsole |
Отключает терминал от вызывающего процесса |
GenerateConsoleCtr!Event |
Посылает указанный сигнал группе обработки терминала, совместно использующей терминал, назначенный вызывающему процессу |
GetConsoleCP |
Определяет номер входной кодовой страницы, которая используется в терминале, назначенной вызывающему процессу |
GetConsoleCP |
Определяет информацию о размере и внешнем виде курсора для указанногоэкранного буфера терминала |
Таблица 1 - Список терминальных функций
Продолжение таблицы 1, Список терминальных функций
Функция |
Описание |
GetConsoleMode |
Определяет текущий режим ввода для входного буфера терминала или текущий режим вывода для экранного буфера терминала |
GetConsoleOutputCP |
Определяет номер выходной кодовой страницы, которая используется в терминале, назначенной вызывающему процессу |
GetConsoleScreenBufferinfо |
Определяет информацию об указанном экранном буфере терминала |
GetConsoleTitle |
Возвращает строку заголовка для текущего окна терминала |
GetConsoleWindow |
Определяет дескриптор окна, используемого для терминала, которая назначена вызывающему процессу |
GetLargeStConsoleWindowSize |
Возвращает размер максимально возможного окна терминала |
GetNumberOfConsoleInputEvents |
Возвращает число непрочитанных введенных записей во входном буфере терминала |
GetNumberOfConsoleMouseButtons |
Возвращается число кнопок мыщи, используемой в текущем терминале |
GetStdHandle |
Возвращается дескриптор для стандартного устройства ввода, стандартного устройства вывода либо устройства вывода сообщений об ощибках |
PeekConsoleInput |
Читает данные из указанного входного буфера терминала без удаления их из буфера |
ReadConsole |
Читает введенные символы из указанного входного буфера терминала и удаляет их из буфера |
ReadConsoleInput |
Читает данные из указанного входного буфера терминала и удаляет их из буфера |
ReadConsoleOutput |
Читает символы и цветовые атрибуты из указанного прямоугольного блока символьных ячеек буфера экрана терминала |
ReadConsoleOutputAttribute |
Копирует указанное количество цветовых атрибутов символов и фона из последовательности символьных ячеек буфера экрана терминала |
WriteConsoleInput |
Записывает данные напрямую во входной буфер терминала |
WriteConsoleOutput |
Записывает символы и цветовые атрибуты в указанный прямоугольный блок символьных ячеек буфера экрана терминала |
WriteConsoleOutputAttribute |
Копирует указанное количество цветовых j атрибутов символов и фона в последовательность символьных ячеек буфера экрана терминала |
WriteConsoleOutputCharacter |
Копирует указанное количество символов в последовательность символьных ячеек буфера экрана терминала |
Продолжение таблицы 1, Список терминальных функций
1.3. Чтение данных с терминала
До недавнего времени для чтения данных с терминала мы всего несколько раз пользовались процедурами ReadString и ReadChar, входящими в библиотеку объектных модулей автора книги. Они были созданы для того, чтобы упростить вам жизнь и не отвлекать вас от решения самой задачи. В обеих процедурах используется функция ReadConsole системы Win32. По сути, они являются оболочками для этой функции. (Оболочка— это
такая процедура, которая упрощает использование другой процедуры.)
Входной буфер терминала. В системе Win32 каждому терминалу назначается входной буфер, реализованный в виде массива записей, каждая из которых содержит информацию об одном событии, поступившем от какого-либо устройства ввода. При наступлении события ввода, такого как нажатие на клавишу, перемещение мыши или щелчке кнопкой мыши, во входном буфере терминала создается соответствующая запись. При использовании функций высокого уровня, таких как ReadConsole, эти записи отфильтровываются и вызвавшей программе возвращается только поток символов.
1.4. Вывод на терминал
При изложении материала в предыдущих главах нам было важно, чтобы вывод текста на экран был максимально упрощен. Напомним, что в главе 5, "Процедуры", была описана процедура WriteString из библиотеки lrvine32 . lib, в качестве единственного параметра которой нужно передать в регистре EDX адрес нуль-завершенной строки. На самом деле процедура WriteString просто является оболочкой вяя более сложной функции Win32, которая нaзывaeтcя WritвConsolв.
Тем не менее, в этой главе вы познакомитесь с тем, как выполнять прямые вызовы функций Win32, такие KaKWriteConsole и WriteConsoleOutputCharacter. Это потребует от вас изучения некоторых деталей, но в результате вы сможете реализовать те функциональные возможности, которые не обеспечивают процедуры библиотеки
Irvine32.lib.
1.5. Файловый ввод-вывод
Функция CreateFile
Данная функция позволяет создать новый файл либо открыть существующий файл. Если операция проходит успешно, в вызвавшую ее программу возвращается дескриптор открытого файла. В противном случае возвращается специальная константа INVALID_HANDLE_VALUE.
Вот прототип фyнкции CreateFile:
CreateFile PROTO,
pFilename:PTR BYTE, ; Адрес строки, содержащей имя файла
desiredAccess:DWORD, ; Требуемый режим доступа
shareMode:DWORD, ; Режим совместного использования
lpSecurity:DWORD, ; Адрес атрибутов безопасности
creationDisposition:DWORD, ; Действия, выполняемые при
; создании файла
flagsAndAttributes:DWORD, / Атрибуты файла
htemplate:DWORD ; Дескриптор файла, используемого
; в качестве шаблона
Первый параметр функции CreateFile — это адрес нуль-завершенной строки,
содержащей частично или полностью определенное имя файла в виде: устройство: путьимя_файяа. Параметр desiredAccess определяет требуемый режим доступа к файлу (по чтению или записи). Параметр shareMode управляет режимом доступа к открытому файлу со стороны других программ, запущенных в системе. Параметр lpSecuri ty- это адрес структуры, с помощью которой в системах Windows NT, 2000 и XP выполняется управление правами доступа к файлу со стороны пользователей. Значение параметра creationDisposition определяет, какие действия будет выполнять операционная система во время создания файла в случае, если такой файл уже есть или его еще не существует.
Параметр flagsAndAttributes представляет собой набор битов, значение которых определяет атрибуты файла, такие как архивируемый, зашифрованный, обычный, системный или временный. Параметр htemplate необязательный. Он определяет дескриптор другого открытого ранее шаблонного файла, атрибуты которого (обычные ирасширенные) будут использоваться при создании текущего файла. Если шаблонный файл не используется, вместо параметра h template нужно подставить нулевое значение.
Требуемый режим доступа. Задав соответствующее значение параметра desiredAccess, приведенного в табл. 2, программа может получить доступ к файлу по чтению, записи, чтению/записи или запросить доступ к устройству как к файлу. Указанные в табл. 2 значения можно комбинировать. Кроме них существует еще большое количество разных значений флагов, которые не приведены в таблице.
Таблица 2. Возможные значения параметра desiredAccess
Значение |
Описание |
0 |
Запрос на доступ к устройству как к файлу. При этом прикладная программа может опросить атрибуты устройства без доступа к нему i на физическом уровне |
GENERIC_READ |
Запрос на доступ к файлу по чтению. При этом данные могут быть считаны с файла с помощью последующих вызовов функции ReadFile. Чтение данных из файла вызывает перемещение внутреннего указателя на величину счетчика прочитанных данных. Чтобы получить доступ к файлу по чтению/записи, добавьте в атрибуты константу GENERI C_WRI ТЕ |
GENERIC_WRITE |
Запрос на доступ к файлу по записи. Запись данных в файл выполняется с помощью последующих вызовов функции WriteFile. При этом изменяется значение внуфеннего указателя позиции в файле на величину счетчика записанных данных. Чтобы получить доступ к файлу по чтению/записи, добавьте в атрибуты константу GENERIC_READ |
Действия, выполняемые при создании файла. Параметр creationDisposition определяет, какие действия будет выполнять операционная система во время создания файла в случае, если такой файл уже есть или его еще не существует. Его значения приведены в табл. 3.
В табл. 4 перечислены наиболее употребительные значения параметра flagsAn- dAttributes. (Полный список приведен в документации по Microsoft MSDN.) Допускается любая комбинация указанных в таблице атрибутов, однако нужно учитывать, что любой указанный атрибут файла замещает атрибут FlLE_ATTRlBUTE_NORMAL.
Примеры. Ниже приведено несколько примеров вызовов функций, позволяющих прояснить, как создавать и открывать файлы. Чтобы получить дополнительную информацию, обратитесь к описанию функции CreateFile, приведенному в документации Microsoft MSDN.
• Открытие существующего файла для чтения:
INVOKE CreateFile,
ADDR filename, ;Адрес строки, содержащей имя файла
GENERIC_READ, ; Требуемый режим доступа
DO_NOT_SHARE, ; Запрет на совместное использование
NULL, ; Атрибуты прав доступа отсутствуют
OPEN_EXISTING, ; Открыть существующий файл
FILE_ATTRIBUTE__NORMAL, ; Атрибуты файла
FILE_ATTRIBUTE__NORMAL, ; Атрибуты файла
0 ; Шаблонный файл отсутствует
Таблица 3. Возможные значения параметра creationDisposition
Значение |
Описание |
CREATE_NEW |
Создать новый файл. Если файл с указанным именем существует, функция аварийно завершает свою работу |
CREATE ALWAYS |
Создать новый файл. Если файл с указанным именем существует, его содержимое будет затерто. При этом существующие атрибуты файла сбрасываются. Затем функция объединяет атрибуты файла и флажки, указанные в параметре flagsAndAttributes с константой FILE_ATTRIBUTE_ARCHIVE |
OPEN_EXISTING |
Открывается существующий файл. Если файл не найден, функция аварийно завершает свою работу |
OPEN_ALWAYS |
Открывается существующий файл. Если файл не найден, он будет создан так, как если было бы указано значение CREATE_NEW |
TRUNCATE_EXISTING |
Открывается существующий файл по записи и его длина усекается до нуля. При этом в запросе на доступ к файлу должен быть указан атрибут GENERIC_WRITE. Если файл не найден, функция аварийно завершает свою работу |
Значение |
Описание |
FILE_ATTRIBUTE_ARCHIVE |
Архивируемый файл. Приложения используют значение этого атрибута для отбора файлов для резервного копирования или восстановления |
FILE_ATTRIBUTE_HIDDEN |
Скрытый файл. Подобные файлы не включаются в список файлов при обычном просмотре каталога |
FILE_ATTRIBUTE_NORMAL |
Файлу не назначены никакие другие атрибуты. Этот атрибут должен использоваться только сам по себе |
FILE_ATTRIBUTE_READONLY |
Только для чтения. Приложению разрешается считывать данные из файла, но запрещается изменять в нем данные и удалять файл |
FILE_ATTRIBUTE_TEMPORARY |
Файл используется для временного хранения данных |
Продолжение таблицы 3. Возможные значения параметра creationDisposition
• Открытие существующего файла для записи:
INVOKE CreateFile,
ADDR filename, ; Адрес строки, содержащей имя файла
GENERIC_WRITE, ; Требуемый режим доступа
DO_NOT_SHARE, ; Запрет на совместное использование
NULL, ; Атрибуты прав доступа отсутствуют
OPEN_EXISTING, ; Открыть существующий файл
FILE_ATTRIBUTE_NORMAL, ; Атрибуты файла
0 ; Шаблонньй файл отсутствует
Создание нового файла с обычными атрибутами, если указанный файл существует, его содержимое стирается:
INVOKE CreateFile,
ADDR filename,
GENERIC_WRITE,
DO_NOT_SHARE,
NULL,
CREATE_ALWAYS, ; He уничтожать существующий файл
FILE_ATTRIBUTE_NORMAL,
0
1.6. Функция CIoseHandle
Эта функция закрывает ранее открытый файл, определяемый по дескриптору. Вот ее прототип:
CIoseHandle PROTO, handle:DWORD
1.7. Функция ReadFile
Эта функция позволяет прочитатьданные из файла, открытого в режиме для чтения. У функции ReadFile предусмотрен дополнительный асинхронный режим работы, при использовании которого программа может работать, не дожидаясь, пока завершится операция ввода. Вот прототип функции:
ReadFile;
PROTO, ; ; Читать в буфер из файла
handle:DWORD, ; Дескриптор файла
pBuffer: ; Адрес буфера
nBufsize: DWORD, ; ; Размер буфера или сколько байтов нужно прочитать
pBytesRead:PTR DWORD, ; ; Адрес переменной, в которую записывается реальное количество прочитанных данных
pOverlapped:PTR DWORD ; Адрес структуры типа OVERLAPPED, предназначенной для синхронизации операций ввода-вывода
Первый параметр handle— это дескриптор файла, открытого с помощью функции CreateFile. Второй параметр pBuffer содержит адрес буфера, куда будут записываться данные. Параметр nBufsize определяет размер буфера или максимальное количество байтов, которое требуется прочитать из файла. Параметр pBytesRead содержит адрес 32-разрядной переменной, в которую записывается реальное количество прочитанных данных. Последний параметр pOverlapped необязательный. Он содержит адрес структурной переменной типа OVERLAPPED, которая используется для выполнения асинхронного чтения файла. Если используется обычная (синхронная) операция чтения файла, принятая по умолчанию, вместо адреса структуры подставьте вместо параметра pOverlapped нулевое значение.
1.8. Функция WriteFiIe
Эта функция предназначена для записи данных в файл, указанный с помощью дескриптора. В качестве дескриптора может использоваться дескриптор буфера экрана или текстового файла. Место в файле, в которые будут записаны данные, отмечается специальным внутренним указателем. После завершения записи к значению этого указателя прибавляется реальное количество записанных байтов. Ниже приведен прототип функции:
WriteFile PROTO,
fileHandle:DWORD, ;Выходной дескриптор
pBuffer:PTR BYTE, ; Адрес буфера
nBufsize:DWORD,; Размер буфера
pBytesWritten:PTR DWORD, ; Адрес переменной, в которую помещается реальное количество записанных данных
pOverlapped:PTR DWORD; Адрес структуры типа OVERLAPPED, предназначенной для синхронизации операций ввода-вывода
1.9. Пример: nporpaMMaWriteFiie.asm
Ниже приведена программа Writefile. asm, в которой создается новый файл, и в него записывается некоторый текст. При создании файла используется опция CREATE_ALWAYS, поэтому если файл с таким именем уже существует, его содержимое стирается.
TITLE Использование функции WriteFile (WriteFile.asm)
INCLUDE Irvine32.inc
. data
buffer BYTE "Этот текст будет записан в файл.",Ос1Ь,ОаЬ
bufSize = ($-buffer)
errMsg BYTE "Ошибка при создании файла.",Ос1Ь,ОаЬ,0
filename BYTE "output.txt",0
fileHandle DWORD ? ; Дескриптор файла для записи
bytesWritten DWORD ? ; Число записанных байтов
. code
main PROC
INVOKE CreateFile,
mov
ADDR filename, GENERIC_WRITE, DO_NOT_SHARE, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0
fileHandle,eax ; Сохраним дескриптор файла
.IF eax == INVALID_HANDLE_VALUE
mov edx,OFFSET errMsg ; Выведем сообщение об ошибке
call WriteString
jmp QuitNow
.ENDIF
INVOKE WriteFile, ; Запишем текст в файл
fileHandle, ; Дескриптор файла
ADDR buffer, ; Адрес буфера
bufSize, ; Число байтов для записи
ADDR bytesWritten, ; Адрес переменной
0 ; Адрес структуры OVERLAPPED не задан
INVOKE CloseHandle, ; Закроем файл
fileHandle QuitNow:
INVOKE ExitProcess,0 ; Завершим программу
main ENDP
END main
1.10. Перемещение файлового указателя
Функция SetFilePointer предназначена для перемещения указателя в открытом файле. С помощью этой функции можно сделать так, чтобы при записи данные добавлялись в конец файла, а также организовать доступ к произвольным участкам данных файла. Прототип функции приведен ниже:
SetFilePointer PROTO,
handle:DWORD, ; Дескриптор файла
nDistanceLo:SDWORD, ; Число байт для перемещения
pDistanceHi:PTR SDWORD, ; Адрес 32-разрядной переменной, содержащей старшее слово б4-разрядного числа байт для перемещения
moveMethod:DWORD; Начальная точка для перемещения
Параметр moveMethod определяет отправную точку, относительно которой выполняется перемещение указателя. Он может принимать одно из трех значений: FILE_BEGIN, FILE_CURRENT И FILE_END. Собственно значение, определяющее количество байтов ддя перемещения указателя, является 64-разрядным числом со знаком, разделенное на 2 части:
• nDistanceLo- младшие 32-бита;
• pDistanceHi — адрес переменной, содержащей старшие 32-бита.
Если при вызове функции SetFilePointer параметр pDistanceHi равен нулю, для перемещения файлового указателя будет использоваться только значение параметра nDistanceLo. Ниже приведен пример вызова этой функции для перемещения указателя в конец файла, чтобы при последующей операции записи данные добавлялись в его конец.
INVOKE SetFilePointer,
fileHandle, ; Дескриптор файла
0. ; Младшее значение числа байт
0, ; Указатель на старшее значение равен нулю
FILE END; Способ перемещения — относительно конца файла
1.11. Пример программы: ReadFile.asm
В программе ReadFile.asm открывается текстовый файл, созданный при запуске программы WriteFile. asm, затем из него считываются данные, файл закрывается и на экране отображается его содержимое:
TITLE Использование функции ReadFile (ReadFile.asm)
INCLUDE Irvine32.inc
.data
buffer BYTE 500 DUP(?)
bufSize = ($-buffer)
errMsg BYTE "Ошибка при открытии фaйлa.",Odh,Oah,0
filename BYTE "output.txt",0
fileHandle DWORD ? ; Дескриптор файла
byteCount DWORD ? ; Число прочитанных байтов
.code
main PROC
INVOKE CreateFile, ; Откроем файл для чтения
ADDR filename, GENERIC_READ,
DO_NOT_SHARE, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, 0
mov fileHandle,eax ; Сохраним дескриптор файла
.IF eax == INVALID_HANDLE_VALUE
mov edx,OFFSET errMsg ; Выведем сообщение об ошибке
call WriteString
jmp QuitNow
.ENDIF
INVOKE ReadFile, ; Читаем содержимое файла в буфер
fileHandle, ADDR buffer,
bufSize, ADDR byteCount, 0
INVOKE CloseHandle, ; Закроем файл
fileHandle
mov esi,byteCount ; Вставим нулевой байт в конец
mov buffer[esi],0 / прочитанной строки
mov edx,OFFSET buffer ; Отобразим содержимое буфера
call WriteString
QuitNow:
INVOKE ExitProcess,0 ; Завершим выполнение программы
main ENDP
END main
1.12. Операции с окном терминала
Среди функций Win32 API предусмотрены такие, которые позволяют выполнять ряд ограниченных операций с окном терминала, а также с буфером экрана, хранящим отображаемые в окне терминала данные. По сути, окно терминала выполняет своего рода роль "просмотрового окошка", в котором отображается только часть содержимого буфера экрана.
Существует несколько функций, позволяющих изменить размер окна терминала и его положение относительно буфера экрана. Функция SetConsoleWindowlnfo задает размер и положения окна терминала относительно буфера экрана. Функция GetConsoleScreen- Bufferlnfo возвращает (кроме всего прочего) координаты прямоугольного окна терминала относительно буфера экрана. Функция SetConsoleCursorPosition позволяет установить курсор в любую позицию буфера экрана. Если окажется, что это область в настоящий момент не видна на экране, выполняется автоматическое перемещение окна терминала, чтобы курсор стал видимым. Функция ScrollConsoleScreenBuffer перемещает часть текста или весь текст, находящийся в буфере экрана, на указанное число позиций. Это непосредственно влияет на содержимое окна терминала.
1.13. Функция SetConsoleTitIe
Эта функция позволяет изменить содержимое строки заголовка окна терминала. Вот пример:
.data
titleStr BYTE "Заголовок окна",0
. code
INVOKE SetConsoleTitIe, ADDR titleStr
1.14. Функция GetConsoIeScreenBufferInfo
Эта функция возвращает информацию о текущем состоянии окна терминала. Ей передается два параметра: дескриптор терминала и адрес структуры, в которую помещается разнообразная информация о текущем состоянии окна терминала. Вот ее прототип:
GetConsoIeScreenBufferInfo PROTO,
outHandle:DWORD, ; Дескриптор буфера экрана
терминала
pBufferInfo:PTR CONSOLE_SCREEN_BUFFER_INFO
Структура CONSOLE_SCREEN__BUFFER_INFOoпpeдeляeтcя так:
CONSOLE SCREEN BUFFER INFO STRUCT
dwSize COORD <>
dwCursorPos COORD <>
wAttributes WORD ?
srWindow SMALL RECT <>
maxWinSize COORD <>
C0NS0LE_SCREEN_BUFFER_INF0 ENDS
После вызова функции GetConsoIeScreenBufferInfo в поле dwSize этой структуры будет содержаться размер буфера экрана, заданный в виде количества столбцов и строк. Поле dwCursorPos содержит координаты положения курсора в буфере. Оба поля являются структурами типа COORD. В поле wAttributes будут находиться атрибуты цвета символов и фона, которые используются при выводе текста на терминал с помощью функции WriteConsole. Поле srWindow содержит координаты положения окна терминала относительно буфера экрана. В поле maxWinSize возвращается максимальный размер экрана терминала, заданный в виде числа столбцов и строк, который рассчитывается исходя из размеров буфера экрана и шрифта, а также используемого разрешения экрана. Ниже приведен пример вызова этой функции.
.data
consoleInfo CONSOLE_SCREEN_BUFFER_INFO О
.code
INVOKE GetConsoIeScreenBufferInfo, outHandle,
ADDR consoleInfo
1.15. Функция SetConsoleWindowInfo
Эта функция устанавливает размер и положение окна терминала относительно буфера экрана. Вот прототип функции:
SetConsoleWindowInfo PROTO, ; Устанавливает позицию окна терминала
nStdHandle:DWORD, ; Дескриптор буфера экрана
bAbsolute:DWORD, ; Тип координат
pConsoleRect:PTR SMALL_RECT; Адрес структуры с координатами окна
Значение параметра bAbsolute влияет на то, как интерпретируются координаты окна, заданные в структуре типа SMALL_RECT, адрес которой указывается в параметре pConsoleRect. Если оно истинно, то в параметре pConsoleRect указаны новые абсолютные координаты левого верхнего и правого нижнего углов окна терминала. Если значение параметра bAbsolute ложно, то новые координаты окна считаются относительными и прибавляются к его текущим координатам. Ниже приведен исходный код программы Scroll.asm, которая выводит пятьдесят строк текста в буфер экрана терминала. Затем она изменяет размер и положения окна терминала, что вызывает моментальную прокрутку текста в обратном направлении. В программе используется функция SetConsoleWindowInfo:
TITLE Прокрутка окна терминала (Scroll.asm)
INCLUDE Irvine32.inc
.data
message BYTE ": Эта строка текста была записана "
BYTE "в буфер экрана тepминaлa.",Odh,Oah
messageSize = ($-message)
outHandle DWORD 0 Дескриптор стандартного
bytesWritten DWORD ? устройства вывода
lineNum DWORD 0 Число записанных байтов
windowRect SMALL RECT <0,0,60,11> ; Координаты окна: / левого верхнего и правого ; нижнего угла
.code
main PROC
INVOKE GetStdHandle, STD_OUTPUT_HANDLE
mov outHandle,eax
.REPEAT
mov eax,lineNuro
call WriteDec ; Выведем десятичный номер строки
INVOKE WriteConsole,
outHandle, ; Дескриптор вывода на терминал
ADDR message, ; Адрес выводимой строки
messageSize, ; Длина строки
ADDR bytesWritten, ; Возвращается число реально записанных байтов
0 ; Не используется
inc lineNum ; Перейдем к следующей строке
.UNTIL lineNum > 50
Изменим размер и положение окна терминала по отношению к буферу экрана
INVOKE SetConsoleWindowInfo,
outHandle,
TRUE,
ADDR windowRect Новый размер окна
call ReadChar Ждем нажатия на клавишу
call ClrScr Очистим буфер экрана
call ReadChar Ждем еще одного нажатия
на клавишу
INVOKE ExitProcess,0
main ENDP
END main
Лучше всего запустить эту программу непосредственно из окна программы Проводник системы Windows, а не из интегрированной среды текстового редактора. Дело в том, что программа редактора может изменить внешний вид и режим работы окна терминала. Обратите внимание, что в процессе работы программы вы должны дважды нажать любую клавишу на клавиатуре: один раз для очистки буфера экрана, а второй раз — для завершения работы программы. Это сделано для того, чтобы облегчить вам наблюдение за работой программы.
1.16. Функция SetConsoleScreenBuflTerSize
Эта функция позволяет задать размер буфера экрана в виде количества столбцов и строк. Вот ее прототип:
SetConsoleScreenBufferSize PROTO,
outHandle:DWORD/ ; Дескриптор вывода на терминал
dwSize:COORD ; Новый размер буфера экрана
11.1.7. Управление курсором
Эта функция возвращает информацию о размере курсора и виден ли он на экране или нет. Кроме дескриптора терминала ей передается объектная переменная типа CONSOLE_CURSOR_INFO;
GetConsoIeCursorInfo PROTO,
outHandle:DWORD, ; Дескриптор терминала
pCursorInfo:PTR CONSOLE_CURSOR_INFO ; Параметры курсора
По умолчанию размер курсора равен 25. Это означает, что курсор будет занимать 25% символьной ячейки.
11.1.7.2. Функция SetConsoleCursorInfo
С помощью этой функции можно задать размеры курсора и отобразить или скрыть его на экране. Кроме дескриптора терминала, ей передается объектная переменная типа CONSOLE_CURSOR__INFO;
SetConsoleCursorInfo PROTO,
outHandle:DWORD, ; Дескриптор терминала
pCursorInfo:PTR CONSOLE__CURSOR_INFO ; Параметры курсора
11.1.8. Изменение цвета текста
Существует два способа изменения цвета текста, отображаемого в окне терминала. Во-первых, вы можете изменить текущий цвет текста, вызвав функцию SetConso- leTextAttribute, что повлияет на все последующие операции вывода текста на терминал. Во-вторых, можно установить цветовые атрибуты определенных ячеек на экране, вызвав функцию WriteConsoleOutputAttributa
11.1.8.1. Функция SetConsoIeTextAttribute
С помощью этой функции задаются цвета символов и фона, что повлияет на все последующие операции вывода текста на терминал. Вот прототип функции:
SetConsoIeTextAttribute PROTO,
outHandle:DWORD, / Дескриптор терминала
nColor:DWORD ; Цветовые атрибуты
Значение атрибутов цвета хранится в младшем байте параметра nColor. Цвета кодируются точно так же, как и при работе с видео функциями BlOS, которые описаны в разделе 15.3.2.
11.1.8.2. Функция WriteConsoIeOutputAttribute
Эта функция копирует массив цветовых атрибутов в последовательность символьных ячеек буфера экрана терминала, начинающийся с указанной позиции. Вот ее прототип:
WriteConsoIeOutputAttribute PROTO,
outHandle:DWORD, Дескриптор терминала
pAttribute:PTR WORD, Адрес массива атрибутов
nLength:DWORD Число ячеек
xyCoord:COORD, , Координаты первой ячейки
lpCount:PTR DWORD Переменная, содержащая реальное число записанных ячеек.
Параметр pAttribute — это адрес массива слов, каждый элемент которого содержит в младшем байте цветовые атрибуты для соответствующей ячейки буфера экрана. Длина массива задается в параметре nLength. Координаты начальной ячейки в буфере экрана задаются с помощью параметра xyCoord. После вызова функции переменная, адрес которой указан в параметре IpCount, будет содержать реальное число записанных ячеек.
11Л.8.3. Пример программы: WriteCoIors
Чтобы продемонстрировать на примере использование атрибутов цвета, в программе WriteColors.asm создается два массива: массив символов и массив атрибутов, соответствующих каждому символу. Затем в программе вызывается функция WriteConsoIeOutputAttribute, которая копирует атрибуты в буфер экрана и функция WriteConsoleOutputCharacter, которая копирует массив символов в те же ячейки буфера экрана:
TITLE Вывод цветного текста (WriteColors.asm)
INCLUDE Irvine32.inc
.data
outHandle DWORD ?
cellsWritten DWORD ?
xyPos COORD <10,2>
; Массив кодов символов:
buffer BYTE 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
BYTE 16,17,18,19,20
BufSize = {$ - buffer)
; Массив цветовых атрибутов:
attributes WORD OFh,OEh,ODh,OCh,OBh,OAh,9,8,7,6
WORD 5,4,3,2,1,OFOh,OEOh,ODOh,OCOh,OBOh
.code
main PROC
; Определим дескриптор стандартного устройства вывода:
INVOKE GetStdHandle,STD_OUTPUT_HANDLE
mov outHandle,eax
; Зададим цвета смежных ячеек на экране:
INVOKE WriteConsoleOutputAttribute,
outHandle, ADDR attributes,
BufSize, xyPos,
ADDR cellsWritten
; Выведем на экран коды символов от 1 до 20:
INVOKE WriteConsoleOutputCharacter,
outHandle, ADDR buffer, BufSize,
xyPos, ADDR cellsWritten
call ReadChar ; Ждем нажатия на клавишу
INVOKE ExitProcess,0 ; Завершим программу
main ENDP
END main
11.1.9. Функции для работы со временем и датой
Существует довольно большой набор функций Win32 API, предназначенный для работы со временем и датой (табл. 5). Однако в данном разделе мы рассмотрим только небольшое их подмножество, с помощью которого можно считывать и устанавливать текущее значение даты и времени.
Таблица 5. Набор функций Win32 API
Функция |
Описание |
CompareFileTime |
Сравнивает две 64-разрядные временные характеристики файла |
DosDateTimeToFileTime |
Преобразовывает дату и время создания или модификации файла, заданную в формате MS DOS в 64-разрядную временную характеристику файла |
FileTimeToDosDateTime |
Преобразовывает 64-разрядную временную характеристику файла в формат MS DOS |
FileTimeToLocalFileTime |
Преобразовывает временную характеристику файла, заданную в формате UTC (универсальное скоординированное время) в локальную временную характеристику |
FileTimeToSystemTime |
Преобразовывает 64-разрядную временную характеристику файла в формат системного времени |
GetFileTime |
Определяет дату и время создания, последнего обращения и последней модификации файла |
GetLocalTime |
Определяет текущее локальное время и дату |
GetSystemTime |
Определяет текущее время и дату в формате UTC |
GetSystemTimeAdjustment |
Позволяет узнать, выполняется ли в операционной системе периодическая коррекция значения таймера текущего времени |
GetSystemTimeAsFileTime |
Определяет текущее системное время и дату в формате UTC |
GetTickCount |
Определяет время в миллисекундах, которое прошло с момента последней загрузки системы |
GetTimeZoneInformation |
Определяет текущие параметры временной зоны | |
LocalFileTimeToFileTime |
Преобразует временную характеристику файла, заданную в локальном формате, в формат UTC |
SetFileTime |
Задает дату и время создания, последнего обращения и последней модификации файла |
SetLocalTime |
Устанавливает текущее локальное время и дату |
SetSystemTime |
Задает текущее системное время и дату в формате UTC |
SetSystemTimeAdjustment |
Разрещает или запрещает периодическую коррекцию значения системного таймера текущего времени |
SetTimeZoneInformation |
Задает текущие параметры временной зоны |
SystemTimeToFileTime |
Преобразует системное время в 64-разрядный формат временной характеристики файла |
SystemTimeToTzSpecificLocalTime |
Преобразует время, заданное в формате UTC, в местное время указанной временной зоны |
Продолжение таблицы 5. Набор функций Win32 API
Структура SYSTEMTIME, Эта структура так или иначе используется практически во всех функциях для работы со временем и датойУ1пдоУ8 API. Вот ее определение:
SYSTEMTIME STRUCT
wYear WORD ? ; Год D цифры)
wMonth WORD ? ; Месяц A-12)
wDayOfWeek WORD ? ; День недели @-6)
wDay WORD ? ; День месяца A-31)
wHour WORD ? ; Часы @-23)
wMinute WORD ? ; Минуты @-59)
wSecond WORD ? ; Секунды @-5 9)
wMilliseconds WORD ? ; Миллисекунды @-999)
SYSTEMTIME ENDS
В поле wDayOfWeek указываются числа, соответствующие дням недели: 0 — воскресенье, 1 — понедельник и т.д. Значение в поле wMilliseconds указано с некоторой погрешностью, поскольку операционная система не может мгновенно обновить значение внутреннего таймера компьютера.
11.1.9.1. Функции GetLocaITime и SetLocalTime
Функция GetLocaITime возвращает текущую дату и время на основании показаний системного таймера. Значение времени соответствует локальному значению установленной временной зоны. При вызове функции ей нужно передать адрес структурной переменной типа SYSTEMTIME:
GetLocaITime PROTO,
pSystemTime:PTR SYSTEMTIME
Функция SetLocalTime устанавливает локальное время и дату. При вызове нужно указать адрес структурной переменной типа SYSTEMTIME, содержащей нужную информацию:
SetLocalTime PROTO,
pSystemTime:PTR SYSTEMTIME
Если выполнение функции завершено успешно, возвращается ненулевое значение. При аварийном завершении функция возвращает нулевое значение. Ниже приведен пример вызова функции GetLocalTime:
.data
sysTime SYSTEMTIME <>
. code
INVOKE GetLocalTime,ADDR sysTime
11.1.9.2. Функция GetTickCount
Эта функция возвращает время в миллисекундах, которое прошло с момента последней загрузки системы:
GetTickCount PROTO ; Значение возвращается
; в регистре EAX
Поскольку функция возвращает интервал времени в виде целого 32-разрядного числа, его значение будет периодически обнуляться через каждые 49,7 дня непрерывной работы системы. Эта функция обычно используется в программе для отслеживания интервалов времени, например времени выполнения некоторого цикла, когда нужно по истечении заданного интервала прервать его выполнение. В приведенной ниже программе каждые 100 мс на экран выводится точка и проверяется, не прошло ли с момента запуска программы 5000 мс. Фрагмент этого кода можно использовать в разных программах:
TITLE Отслеживание интервалов времени (TimingLoop.asm)
; В этой программе используется функция GetTickCount для
/ определения интервала времени в мс, прошедшего с момента
/ запуска программы
INCLUDE Irvine32.inc
TIME_LIMIT = 5000
.data
startTime DWORD ?
dot BYTE ".",0
. code
main PROC
INVOKE GetTickCount ; Опросим значение таймера
mov startTime,eax
L1:
mov edx,OFFSET dot ; Выведем точку
call WriteString
INVOKE Sleep,100 ; Заморозим выполнение программы
; на 100 мс
INVOKE GetTickCount
sub eax,startTime ; Определим прошедший интервал времени
cmp eax,TIME_LIMIT
jb L1
L2:
exit
main ENDP
END main
11.1.9.3. Функция Sleep
Эта функция замораживает выполнение текущей программы на указанный в миллисекундах интервал времени:
Sleep PROTO,
dwMilliseconds:DWORD
11.1.9.4. Процедура GetDateTime
Эта процедура входит в библиотеку lrvine32.1ib автора книги. Она возвращает 64-разрядное целое число, которое обозначает время в 100-наносекундных интервалах, прошедшее с I января 1601 года. Этот факт вам может показаться немного странным, по- скольку в то далекое время компьютеров не было и в помине. Тем не менее, специалисты фирмы Microsoft выбрали в качестве точки отсчета именно эту дату для отслеживания времени и даты создания файлов (так называемой временной характеристики файлов). Ниже описана последовательность действий, рекомендованная в Win32 SDK, для преобразования текущего времени и даты в целое 64-разрядное число, удобное для выполнения арифметических операций с датой.
1. Вызовите функцию GetLocalTime, которая проинициализирует поля структурной переменной SYSTEMTIME.
2. Преобразуйте тип структурной переменной с SYSTEMTIME в FILETIME, вызвав функцию SystemTimeToFileTime
3. Скопируйте содержимое структурной переменной типа FILETIME в 64-разрядное учетверенное слово.
Структура FILETIME состоит из двух двойных слов:
FILETIME STRUCT
loDateTime DWORD ?
hiDateTime DWORD ?
FILETIME ENDS
Ниже приведен исходный код процедуры OetDateTime, которой передается адрес 64-разрядной переменной. Она формирует в этой переменной структуру типа FILETIME и заполняет ее поля.
GetDateTime PROC,
pStartTime:PTR QWORD
LOCAL sysTime:SYSTEMTIME, flTime:FILETIME
Определяет текущее время и дату и сохраняет его в виде
б4-разрядного целого числа в формате FILETIME
Определим системное локальное время
INVOKE GetLocalTime,
ADDR sysTime
; Преобразуем его из формата SYSTEMTIME в формат FILETIME
INVOKE SystemTimeToFileTime,
ADDR sysTime,
ADDR flTime
; Скопируем локальную переменную типа FILETIME в 64-разрядное
; целое число
mov esi,pStartTime
mov eax,flTime.loDateTime
mov DWORD PTR [esi],eax
mov eax,flTime.hiDateTime
mov DWORD PTR [esi+4],eax
ret
GetDateTime ENDP
11.1.9.5. Простейший секундомер
Воспользовавшись функцией GetTickCount, мы создадим две процедуры, применяя которые в паре можно получить простейшую программу-секундомер. Одна из процедур называется TimerStart, в ее функции входит фиксация текущего времени. Вторая процедура TimerStop возвращает количество миллисекунд, прошедших с момента вызова процедуры TimerStart. Ниже приведен исходный код профаммы Timer.asm, в которой вызываются обе процедуры и вводится искусственная задержка с помощью вызова функции Sleep:
TITLE Определение прошедшего интервала времени (Timer.asm)
; Демонстрационная программа простейшего секундомера,
; в которой используется функция GetTickCount Win32 API
INCLUDE Irvine32.inc
TimerStart PROTO,
pSavedTime: PTR DWORD
TimerStop PROTO,
pSavedTime: PTR DWORD
.data
msgl BYTE "Прошло ",0
msg2 BYTE " миллиceкyнд"^Odh,Oah,0
timerl DVIORD ?
.code
main PROC
INVOKE TimerStart, Запустим таймер
ADDR timerl
INVOKE Sleep, 5000 Запустим таймер
INVOKE TimerStop, В EAX число прошедших миллисекунд
ADDR timerl
mov edx,OFFSET msgl
call WriteString
call WriteDec ;Выведем общее время
mov edx,OFFSET msg2
call WriteString
exit
main ENDP
TimerStart PROC uses eax esi,
pSavedTime: PTR DWORD
Запускает таймер секундомера.
Передается: адрес переменной, в которую записывается
текущее время
Возвращается: ничего
INVOKE GetTickCount
mov esi,pSavedTime
mov [esi],eax
ret
TimerStart ENDP
TimerStop PROC uses esi,
pSavedTime: PTR DWORD
Останавливает таймер секундомера. Передается: адрес переменной, содержащей время запуска таймера Возвращается: EAX = величина интервала времени в миллисекундах Примечание: точность отсчета составляет примерно 10 мс
INVOKE GetTickCount
mov esi,pSavedTime
sub eax,[esi]
ret
TimerStop ENDP
END main
В процедуру TimerStart передается адрес двойного слова, в которое записывается текущее значение системного таймера. Процедуре TimerStop передается адрес двойного слова, в которое процедура TisnerStart поместила текущее значение таймера. В регистре EAX возвращается значение интервала времени в миллисекундах, прошедшего с момента вызова процедуры TimerStart. Системные функции работы со временем обеспечивают лишь точность измерения интервалов времени, которая не превышает 10 мс.
11.2. Cоздание графических приложений для Windows.
В этом разделе мы рассмотрим процесс создания простейшего фактического приложения для Microsoft Windows. Наша программа будет создавать и отображать основное окно, выводить на экран окна сообщений и реагировать на события, поступающие от мыши. В этом разделе приведены лишь самые общие сведения, поскольку подробное описание процесса разработки даже самого простого графического приложения для Windows заняло бы целую главу. За более подробной информацией по этому вопросу обратитесь к разделу Platform SDK, Win32API компакт-диска Microsoft MSDN Library, входящего в комплект Visual Studio. Необходимые файлы. В табл. 4 перечислен список файлов, которые вам понадобятся для компиляции и запуска ассемблерной программы, описанной в этом разделе.
Таблица 4. Необходимые файлы
Имя файла |
Описание |
make32.bat |
Командный файл для создания исполняемого файла программы |
WinApp.asm |
Исходный код программы |
GraphWin.inc |
Включаемый файл, содержащий описание структур, констант и прототипов функций,используемых в программе |
kernel32.1ib |
Файл описания точек входа системной библиотеки kerne 132 . dll, содержащей основные функции Win32 API. Он использовался нами и раньше при компоновке терминальных приложений в файле make32.bat |
user32.lib |
Файл описания точек входа системной библиотеки user32 . dll, содержащей дополнительные функции Win32 API |
В файле make32 . bat находятся команды для вызова компилятора ассемблера и компоновщика. Они практически идентичны тем, которые мы использовали до сих пор для создания терминальных приложений. Но есть одно отличие:
ML -с ~coff %l.asm
LINK %l.obj kernel32.1ib user32.1ib /SUBSYSTEM:WINDOWS
Обратите внимание, что вместо опции командной строки /SUBSYSTEM:CONSOLE, которую мы использовали до сих пор, здесь указана опция /SUBSYSTEM:WlNDOWS. Кроме того, в командной строке при вызове компоновщика указаны две стандартные библиотеки системы Windows: kernel32.1ib и user32.1ib, содержащие функции, которые вызываются в нашей программе. Основное окно программы. При запуске программа отображает на экране основное окно, которое приведено на рис. 11.4. Нам пришлось немного уменьшить его размеры, чтобы оно поместилось на странице этой книги.
11.2.1. Необходимые структуры
Структура POlNT определяет горизонтальную X и вертикальную Y координаты точки на экране, измеряемые в пикселях. Она используется, в частности, для размещения на экране графических объектов, окон и обработки щелчков кнопки мыши:
POINT STRUCT
ptX DWORD
ptY DWORD
POINT ENDS
Структура MSGStruct определяет формат сообщения, которыми операционная система Windows обменивается со своими приложениями:
MSGStruct STRUCT
msgWnd DWORD ?
msgMessage DWORD ?
msgWparam DWORD ?
msgLparam DWORD ?
msgTime DWORD ?
msgPt POINT <>
MSGStruct ENDS
C помощью структуры WNDCLXSS определяется класс окна. Каждое окно, создаваемое программой, должно относиться к какому-нибудь классу. Поэтому в каждой программе нужно определить класс ее основного окна. Далее, прежде чем окно будет отображено на экране, программа должна зарегистрировать этот класс в операционной системе. Вот определение структуры:
WNDCLASS STRUC
style DWORD ? Параметры стиля окна
IpfnWndProc DWORD ? Адрес функции WinProc
cbClsExtra DWORD ? Размер общей области класса
CbWndExtra DWORD ? Размер дополнительной области окна
hlnstance DWORD ? Дескриптор текущей программы
hlcon DWORD ? Дескриптор пиктограммы
hCursor DWORD ? Дескриптор курсора
hbrBackground DWORD ? Дескриптор фона окна
lpszMenuName DWORD ? Адрес строки, содержащей имя меню
IpszClassName DWORD ? ; Адрес строки, содержащей имя
; класса
; WinClass ENDS
Ниже приведено краткое описание полей структуры.
• style — определяет внешний вид и характеристики окна программы; допускается комбинация различных параметров стиля, таких KaKWS_CAPTlON и s_BORDER.
• lpfnWndProc — адрес функции в текущей программе, которая обрабатывает различные сообщения, сгенерированные операционной системой в ответ на действия пользователя.
• cbClsExtra — определяет количество общей памяти, которая используется всеми окнами, относящимися к текущему классу; по умолчанию равно нулю.
• cbWndExtra — определяет количество дополнительных байтов, которые вьщеляются после создания экземпляра окна.
• hlnstance — содержит дескриптор экземпляра текущей программы.
• hlcon и hCursor- содержат дескрипторы ресурсов, определяющий пиктограмму и курсор текущей программы.
• hbrBackground- определяет цвет фона окна программы или дескриптор кисточки, с помощью которой рисуется фон окна.
• lpszMenuName ~ содержит адрес нуль-завершенной текстовой строки, в которой указано название меню.
• lpszClassName~- содержит адрес нуль-завершенной текстовой строки, определяющей название класса окна.
11.2.2. Функция MessageBox
В фактических приложениях проще всего вывести текст на экран с помощью окна сообщений. При этом текст в окне сообщений находится на экране до тех пор, пока пользователь не щелкнет на кнопке ОК. Для вывода окна сообщений служит функция Win32 API MessageBox. Вот ее прототип:
MessageBox PROTO,
hWnd:DWORD,
pText:PTR BYTE,
pCaption:PTR BYTE,
style:DWORD
Параметр hWnd определяет дескриптор текущего окна. Вместо параметра pText подставляется адрес нуль-завершенной текстовой строки, которая появится внутри окна сообщений. Параметр pCaption определяет адрес нуль-завершенной текстовой строки, размещаемой в строке заголовка окна сообщений. Параметр style является целым числом, значение которого определяет тип пиктофаммы, количество и тип кнопок, которые могут быть размещены внутри окна. Пиктограмма в окне сообщений может отсутствовать, тогда как кнопки ~ нет. Число и тип кнопок определяется с помощью констант, таких как MB_OK и MB_YESNO. Пиктограммы также определяются с помощью констант, например MB_iCONQUESTlON. При вызове функции константы, определяющие пиктограмму и кнопки, объединяются вместе:
INVOKE MessageBox, hWnd, ADDR QuestionText,
ADDR QuestionTitle, MB_OK + MB_ICONQUESTION
11.2.3. Процедура WinMain
В каждом приложении системы Windows должна быть предусмотрена процедура начального запуска, которая обычно называется winMain. В ней обычно выполняются перечисленные ниже действия:
• определяется дескриптор текущей программы;
• загружаются образы пиктограммы и курсора мыши программы из раздела ресурсов исполняемого файла;
• регистрируется класс основного окна программы и определяется процедура, которая будет обрабатывать поступающие сообщения, сгенерированные операционной системой в ответ на действия пользователя с окном программы;
• создается основное окно программы;
• отображается и обновляется содержимое основного окна программы;
• создается цикл, в котором выполняется получение, перенаправление и обработка сообщений.
11.2.4. Процедура WinProc
Эта процедура обрабатывает все поступающие сообщения, связанные с событиями, происходящими с окном программы. Большинство событий генерируются операционной системой в ответ на какие-либо действия пользователя, например щелчок кнопкой мыши, перетаскивание указателя мыши, нажатие клавиши на клавиатуре и т.п.
Поэтому основная задача процедуры WinProc ~ декодировать каждое поступившее сообщение и, в случае если оно распознано, выполнить в программе связанные с ним действия. Оператор объявления процедуры выглядит так:
WinProc PROC,
hWnd: DWORD, Дескриптор окна
localMsg: DWORD, Идентификатор сообщения
wParam: DWORD, Параметр 1 (зависит от сообщения)
lParam: DWORD Параметр 2 (зависит от сообщения)
Обратите внимание, что значение третьего и четвертого параметров процедуры зависит от типа поступившего сообщения. Например, при обработке щелчка кнопкой мыши, параметр lPararn указывает координаты X и Y точки на экране, в которой находится указатель в момент щелчка. В примере программы, которую мы скоро рассмотрим, процедура WinProc будет обрабатывать всего три сообщения:
• WMLBUTTONDOWN — генерируется в ответ на щелчок левой кнопкой мыши;
• WM_CREATE — уведомляет программу о создании основного окна;
• WM_CLOSE — информирует программу о том, что ее основное окно закрывается.
11.2.5. Процедура ErrorHandler
Эта процедура не является обязательной и создана нами исключительно ради удобства. Она вызывается в случае, если при регистрации класса и создании основного окна программы возникнет ошибка. Например, если класс основного окна программы был успешно зарегистрирован, функция RegieterClaes возвращает ненулевое значение. Если эта функция вернет нулевое значение, вызывается процедура ErrorHandler, в которой отображается сообщение об ошибке, а затем работа программы завершается:
INVOKE RegisterClass, ADDR MainWin
.IF eax == 0
call ErrorHandler
jmp Exit_Program
.ENDIF
В процедуре ErrorHandler выполняются несколько важных действий,
перечисленных ниже:
• вызывается функция QetLaetError, с помощью которой определяется
системный код ошибки;
• вызывается функция formatMeesage, которая возвращает адрес строки,
содержащей сообщение об ошибке, сформированное операционной системой;
• вызывается функция MessageBox, с помощью которой полученная от функции Рогmаt Меssages текстовая строка выводится на экран в окне сообщений;
• вызывается функция LocalFree, которая освобождает память, занимаемую строкой, содержащей сообщение об ошибке.
11.2.6. Листинг программы
Вас не должна пугать длина этой программы. Дело в том, что большая часть этого кода повторяется практически во всех графических приложениях для cиcтeмыWindows:
.386
.model flat,STDCALL
INCLUDE GraphWin.inc
AppLoadMsgTitle BYTE ; "Приложение загружено",0
AppLoadMsgText BYTE; "Это окно отображено после получения " "сообщения WM__CREATE",0
PopupTitle BYTE; "Окно сообщения",0
PopupText BYTE; "Это окно было активизировано после "
BYTE; "Окно сообщения",0
GreetTitle BYTE; "Это окно было активизировано после "
BYTE; "получения сообщения WM_LBUTTONDOWN",0
GreetText BYTE; "Основное окно программы активизировано",0
BYTE; "Это окно отображено сразу после вызова "
CloseMsg BYTE; "Получено сообщение WM_CLOSE",0
ErrorTitle BYTE "Ошибка!",0
WindowName BYTE "Графическая ассемблерная программа",0
className BYTE "ASMWin",0
; Определим структурную переменную, описывающую класс окна
MainWin
WNDCLASS COLOR_WINDOW,NULL,className>
msg
MSGStruct <> winRect
RECT <> hMainWnd
DWORD ? hInstance
DWORD ? .code
WinMain
PROC ;
Определим дескриптор текущего процесса INVOKE
GetModuleHandle , NULL mov
hInstance , eax mov
MainWin.hInstance, eax ;
Загрузим образы пиктограммы и курсора программы. INVOKE
LoadIcon, NULL, IDI_APPLICATION mov
MainWin.hIcon , eax INVOKE
LoadCursor, NULL, IDC__ARROW mov
MainWin.hCursor , eax ;
Зарегистрируем
класс
окна
INVOKE
RegisterClass, ADDR MainWin .IF
eax == 0 call
ErrorHandler jmp
Exit_Program .ENDIF
;
Создадим
основное
окно
программы
INVOKE
CreateWindowEx, 0, ADDR className, ADDR
WindowName,MAIN_WINDOW_STYLE, CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
CW_USEDEFAULT,NULL,NULL,hInstance,NULL
;
Если функция CreateWindowEx завершилась аварийно, отобразим ;
сообщение в вьшдем из программы. .IF
eax == 0 call
ErrorHandler jmp
Exit_Program .ENDIF ;
Запомним дескриптор окна, отобразим окно на экране и ;
обновим его содержимое mov
hMainWnd ,eax INVOKE
ShowWindow , hMainWnd, SW_SHOW INVOKE
UpdateWindow, hMainWnd ;
Выведем
приветственное
сообщение INVOKE
MessageBox, hMainWnd, ADDR GreetText, ADDR
GreetTitle, MB_OK ;
Создадим цикл обработки сообщений Message_Loop:
;
Получим новое сообщение из очереди INVOKE
GetMessage, ADDR msq, NULL,NULL,NULL ;
Если в очереди больще нет сообщений, завершим ;
работу
программы
.IF
eax == 0 jmp
Exit_Program .ENDIF
;
Отправим сообщение на обработку процедуре WinProc нашей программы INVOKE
DispatchMessage, ADDR msg jmp
Message_Loop Exit_Program:
INVOKE
ExitProcess,0 WinMain
ENDP WinProc
PROC, hWnd:DWORD,
localMsg:DWORD, wParam:DWORD, lParam:DWORD Эта
процедура обрабатывает некоторые сообщения, посылаемые системой
Windows нашему приложению. Обработка
остальных сообщений выполняется стандартной процедурой
системы Windows. mov
eax,localMsg .IF
eax == WM_LBUTTONDOWN ; Щелчок левой кнопкой мьши? INVOKE
MessageBox, hWnd, ADDR PopupText, ADDR
PopupTitle, MB_OK jmp
WinProcExit .ELSEIF
eax == WM_CREATE ; Окно создано?
INVOKE
MessageBox, hWnd, ADDR AppLoadMsgText, ADDR
AppLoadMsgTitle, MB_OK jmp
WinProcExit .ELSEIF
eax == WM_CLOSE ; Окно закрыто?
INVOKE
MessageBox, hWnd, ADDR CloseMsg, ADDR
WindowName, MB_OK INVOKE
PostQuitMessage,0 jmp
WinProcExit .ELSE
; Другие
сообщения
INVOKE
DefWindowProc, hWnd, localMsg, wParam, lParam jmp
WinProcExit .ENDIF
WinProcExit:
ret
WinProc
ENDP ErrorHandler
PROC ;
Выведем системное сообщение об ошибке .data
pErrorMsg
DWORD ? messageID
DWORD ? .code
INVOKE
GetLastError mov
messageID,eax ;
Адрес
сообщения
об
ошибке
;
В EAX возвращается код ошибки ;
Определим адрес текстового сообщения об ошибке INVOKE
FormatMessage, FORMAT_MESSAGE_ALLOCATE_BUFFER + FORMAT_MESSAGE_FROM_SYSTEM,NULL,messageID,NULL,
ADDR
pErrorMsg,NULL,NULL ;
Отобразим сообщение об ошибке INVOKE
MessageBox,NULL, pErrorMsg, ADDR ErrorTitle, MB_IConerror+MB_OK
;
Освободим память, занимаемую текстовой строкой ;
сообщения
об
ошибке
INVOKE
LocalFree, pErrorMsg ret
ErrorHandler
ENDP END
WinMain 11.3.
Управление памятью в процессорах семейства IA-32 После
появления системы Microsoft Windows версии 3.0 программисты с большим интересом
начали обсуждать тему написания программ для защищенного режима работы
процессора. Напомним, что до этого все программы писались для реального режима
работы процессора и системы MS DOS. Те, кому приходилось создавать программы
для системы Windows версии 2.x, расскажут вам, насколько непросто было "вписаться"
в те 640 Кбайт оперативной памяти, которые выделялись программе в реальном
режиме адресации! Данную проблему удалось преодолеть только после того, как в
системе Windows стал поддерживаться защищенный (а чуть позже и виртуальный)
режим работы процессора. При этом программистам пришлось осваивать совершенно
новые аппаратные средства, которые открывали перед ними доселе невиданные
возможности. Однако не стоит забывать, что все это стало возможным только после
появления процессора Intel386, который и стал родоначальником семейства IA-32.
За прошедшее десятилетие мы наблюдали процесс эволюции операционных систем и
появление новых версий системы Windows и Linux. Их возможности и стабильность
работы не идут ни в какое сравнение со старой версией Windows 3.0. В этом
разделе мы опишем две основные особенности системы управления памятью
процессоров семейства IA-32: •
преобразование логических (сегментированных) адресов в линейные адреса; •
преобразование линейных адресов в физические (страничная организация памяти). А
теперь давайте вспомним несколько основных терминов, относящихся к системе
управления памятью процессоров семейства1А-32, которые были описаны в главе 2. •
Многозадачность позволяет одновременно запускать в операционной системе
несколько программ (или задач). При этом каждой задаче выделяется небольшой
квант времени процессора, в течение которого ЦПУ физически выполняет команды
этой задачи. •
Сегментами называются области памяти переменной длины, в которых хранится
программный код или данные. •
Благодаря поддержке механизма сегментации на аппаратном уровне удалось
изолировать участки памяти один от другого. В результате выполняемые
одновременно программы не могут повлиять друг на друга. •
Дескриптор сегмента — это 64-разрядное число, в котором зашифрована информация
об одном сегменте памяти: его базовый адрес, права доступа, длина, тип и способ
использования. Теперь мы должны добавить к этому списку еще несколько терминов.
•
Селектор сегмента— это 16-разрядное число, которое зафужается в сегментные
регистры (CS, DS, SS, ES, FS ИЛИ GS). По сути, ОНО является указателем
дескриптора сегмента, расположенным в одной из системных таблиц дескрипторов. •
Логический адрес — это комбинация селектора сегмента и 32-разрядного смешения.
До сих пор мы мало уделяли внимания работе с сегментными регистрами, поскольку
в защищенном режиме их содержимое никогда не меняется прикладными программами.
В своих программах мы использовали только 32-разрядные смещения. Тем не менее,
сегментные регистры очень важны при создании системных программ, поскольку
косвенно они указывают на сегменты памяти. 11.3.1.
Линейные адреса 11.3.1.1.
Преобразование логических адресов в линейные Как
известно, в многозадачной операционной системе допускается одновременное
выполнение нескольких загруженных в память программ (или задач). При этом для
каждой программы
вышляется отдельная область данных. Предположим, что в каждой из трех
выполняемых программ существует переменная, расположенная со смещением 200h
относительно начала сегмента данных. Возникает вопрос: как разделить эти
переменные друг от друга так, чтобы изменение их значения в одной из программ
не влияло на другие программы? Для этой цели в процессорах семейства lA-32
используется одно-
или двухэтапный процесс преобразования смещения переменной в уникальный
физический адрес памяти. На первом этапе логический адрес, состоящий из
селектора сегмента и смещения переменной, преобразуется линейный адрес,
который по сути может являться физическим адресом переменной в памяти. Однако в
современных развитых операционных системах, таких как Microsoft Windows и
Linux, задействуется еще один механизм процессоров семейства IA-32, который
называется страничной организацией памяти. Благодаря ему размер используемого
в программах линейного адресного пространства может превышать физический размер
памяти компьютера. Таким образом, на втором этапе линейный адрес памяти
преобразовывается в физический адрес с помощью механизма страничной
переадресации, который подробнее будет рассмотрен в разделе 11.3.2. Для начала
давайте разберемся в том, как процессор по значению селектора сегмента и
смещению определяет линейный адрес переменной. Как вы уже знаете, каждый
селектор является указателем дескриптора сегмента, расположенного в одной из
системных таблиц дескрипторов. Поэтому сначала по значению селектора
определяется адрес дескриптора сегмента, из которого извлекается базовый адрес
сегмента памяти. После этого к значению базового адреса прибавляется
32-разрядное смещение переменной, в результате чего и получается линейный адрес
переменной в памяти. Линейный
адрес.Линейный адрес является 32-разрядным целым числом, значение
которого находится в диапазоне от 0 до FFFFFFFFh и определяет адрес объекта в
памяти. Линейный адрес может соответствовать физическому адресу объекта в
памяти, если механизм страничной переадресации не используется. 11.3.1.2.
Страничная организация памяти Основной
особенностью процессоров семейства IA-32 является поддержка страничной
организации памяти. При ее использовании операционная система может
предоставить в распоряжение прикладных программ такой объем оперативной памяти,
какой им требуется для работы, независимо от объема физической памяти,
установленной в компьютере. При этом суммарный объем памяти, используемый
всеми приложениями, может превышать объем физической памяти компьютера. Это
стало возможным благодаря тому, что при выполнении программы в физической
памяти компьютера находятся только те участки программы, к которым процессор
обращается в текущий момент времени. Все остальные участки программы хранятся
на диске и загружаются в физическую память компьютера по мере того, как в них
возникает потребность. Вся область памяти, используемой программой разбивается
на участки небольшой длины (как правило 4 Кбайт каждый), называемых страницами.
Во время выполнения программы процессор выгружает из памяти на диск те
страницы, к которым долго не было обращения и загружает на их место другие
страницы, к которым нужно немедленно получить доступ. Для отслеживания всех
страниц памяти, используемых программами, в операционной системе создается
специальный набор таблиц, состоящий из страничного каталога и ряда таблиц
страниц. При обращении в программе частичку памяти, его линейный адрес
автоматически преобразовывается процессором в физический адрес. Этот процесс
преобразования называется страничной переадресацией. Если страница, к которой
происходит обращение, не находится в памяти, в процессоре возникает специальное
прерывание из-за отсутствия страницы Q?agefault). Во время обработки данного
прерывания операционная система находит нужную страницу на диске, загружает ее
в свободный участок памяти, изменяет соответствующим образом содержимое таблицы
страниц и возобновляет выполнение программы. Страничная переадресация и
прерывание из-за отсутствия страницы происходят совершенно не заметно для
прикладной программы. Чтобы почувствовать разницу между физической и
виртуальной памятью, воспользуйтесь диспетчером задач системы Windows 2000/ХР.
На рис. 11.12 показана вкладка Быстродействие (Performance) диалогового окна
диспетчера задач для компьютера, оснащенного 1 Гбайт физической памяти. Общее
количество виртуальной памяти, которое используется в настоящий момент в
операционной системе, можно посмотреть в разделе Выделение памяти (Conimit
Charge) вкладки Быстродействие диалогового окна диспетчера задач. Обратите
внимание, что установленный предельный размер виртуальной памяти, равный
2521476 Кбайт, практически в 2,5 раза превышает объем физической памяти
компьютера^ 11.3.1.3.
Таблицы дескрипторов Дескрипторы
сегментов могут находиться в одной из двух системных таблиц: в таблице
глобальных дескрипторов {Global Descriptor Table, или GDT) или в таблице
локальных дескрипторов {Local
Descriptor Table,
или LDT). Таблица
глобальных скрипторов (GDT) ,В процессорах семейства IA-32
поддерживается только одна таблица глобальных дескрипторов. Она создается
операционной системой компьютера в момент переключения процессора в защищенный
режим работы. Базовый адрес таблицы глобальных дескрипторов помещается в
специальный системный управляющий регистр, называемый GDTR {Global Descriptor
Table Register, или Регистр таблицы глобальных дескрипторов). Элементы этой
таблицы называются дескрипторами сегментов {Segment descriptors). Как вы уже
знаете, в этих дескрипторах хранится информация, описывающая конкретный
сегмент памяти. В таблице глобальных дескрипторов операционная система хранит
описание только тех сегментов, которые используются во всех программах. Таблица
локальных дескрипторов (LDT), В многозадачных операционных системах обычно для
каждой задачи выделяется собственная таблица дескрипторов сегментов, которая
называется таблицей локальных дескрипторов. Базовый адрес этой таблицы
загружается в момент переключения контекста задачи в специальный системный
управляющий регистр, называемый LDTR {Local Descriptor Table Register, или
Регистр таблицы локальных дескрипторов). В каждом дескрипторе сегмента
содержится базовый адрес сегмента, заданный в линейном адресном пространстве.
Обычно все сегменты программы находятся в непересекающихся областях памяти,
как показано на рис. 11.13. Как видите, обращение к трем разным участкам
памяти, заданных своими логическими адресами, связано с выборкой трех разных
дескрипторов сегментов, находящихся в LDT. На нашем рисунке предполагается,
что механизм страничной переадресации отключен, поэтому линейные адреса соответствуют
физическим адресам памяти. 11.3.1.4.
Описание дескриптора сегмента Дескриптор
сегмента представляет собой набор битовых полей, в которых закодированы важные
параметры сегмента, такие как его длина (точнее его максимальное смещение) или
тип. Например, для кодовых сегментов автоматически назначается атрибут
"только для чтения". Поэтому, если в программе будет предпринята
попытка изменить содержимое кодового сегмента, это вызовет прерывание в работе
процессора. В дескрипторе также указывается уровень защиты сегмента, что
позволяет защитить данные операционной системы от доступа со стороны
прикладных программ. Ниже приведено описание основных полей дескриптора
сегмента. Базовый
адрес. Представляет собой 32-разрядное целое число, определяющее адрес начала
сегмента (т.е. адрес байта со смещением 0) в четырехгигабайтовом линейном
адресном пространстве. Уровень
привилегий. Каждому сегменту назначается специальный уровень привилегий,
который представляет собой число от 0 до 3. Число 0 соответствует самому
высокому уровню привилегий, который обычно используется только программами
ядра операционной системы. Если программа, которой назначен больший (в
числовом измерении) уровень привилегий, попытается воспользоваться сегментом с
меньшим уровнем привилегий, возникнет прерывание процессора. Тип
сегмента. Биты этого поля определяют тип сегмента и способы доступа к нему, а
также направление его роста (от младших адресов к старшим или наоборот).
Сегменты данных (к ним относится также и сегмент стека) можно защитить от
записи, т.е. сделать доступными только для чтения. Поскольку данные в сегмент
стека обычно помещаются от старших адресов к младшим, вне скрипторе сегмента
был предусмотрен специальный бит для обозначения подобных сегментов. Кроме того,
можно запретить считывание данных из сегмента кода и сделать его доступным
только для выполнения программ (как вы помните, запись данных в сегмент кода
запрещена по определению). Флаг присутствия сегмента в памяти. Этот бит позволяет
отслеживать, находится ли данный сегмент в физической памяти. Наличие этого
бита позволяло операционной системе компьютера на основе процессора Intel286
вынуждать на диск сегмент при недостатке физической памяти. В связи с
поддержкой страничной организации памяти в процессорах семейства IA-32, этот бит
давно перестал использоваться. Флагвеличины гранулы. Значение этого бита определяет способ интерпретации поля,
определяющего максимальную границу сегмента (т.е. его длину). Если бит величины
гранулы не установлен, граница сегмента задана в байтах. В противном случае
граница сегмента задается в страницах размером 4 Кбайт. Поле
границы сегмента. Представляет собой 20-разрядное целое число, значение которого
определяет максимальную длину сегмента (точнее, максимально допустимое смещение
в пределах данного сегмента, которое равно длине сегмента минус единица). В
зависимости от значения бита величины гранулы, могут существовать следующие два
типа сегментов: • размером от 1 байтадо 1 Мбайта; • размером от 4096 байтов до
4 Гбайт. 11.3.2.
Страничная переадресация При
включении механизма страничной переадресации процессор будет преобразовывать
32-разрядные линейные адреса в 32-разрядные физические aдpeca. При
этом используются три перечисленные ниже структуры данных. •
Страничный каталог— массив, состоящий из 1024 элементов, каждый из которых
имеет длину 32 бита. •
Таблица страниц — массив, состоящий также из 1024 элементов, каждый из которых
имеет длину 32 бита. •
Страница— область памяти, размер которой составляет либо 4Кбайт, либо 4
Мбайт. Для упрощения последующего изложения будем считать, что используются
страницы размером 4 Кбайт. Линейный
адрес, длина которого составляет 32 бита, разбивается на 3 поля. Первое поле
определяет индекс используемого элемента страничного каталога, второе поле —
индекс используемого элемента таблицы страниц, а третье поле определяет
смещение в текущей странице. Адрес страничного каталога загружается
операционной системой в управляющий регистр процессора CR3. 1.
Программа обращается в память, указывая линейный адрес объекта. 2.
Из линейного адреса извлекается значение 10-битового поля, определяющего
индекс элемента в страничном каталоге. По этому индексу находится
соответствующий элемент страничного каталога, содержащий базовый физический
адрес таблицы страниц. 3.
Из линейного адреса извлекается значение 10-битового поля, определяющего
индекс элемента в таблице страниц, адрес которой бьш определен в п. 1. По этому
индексу находится соответствующий элемент таблицы страниц, содержащий базовый
физический адрес страницы памяти. 4. К полученному в п. 2 базовому физическому
адресу страницы памяти прибавляется значение 12-битового поля смещения, в
результате чего получается 32-разрядный физический адрес операнда в памяти. В
зависимости от типа операционной системы, для всех запущенных задач может
использоваться только один страничный каталог, либо для каждой задачи создается
свой страничный каталог. Возможен также комбинированный вариант. 11.3.2.1.
Диспетчер виртуальных машин системы Microsoft Windows После
того как мы в общих чертах описали систему управления памятью, используемую в
процессорах семейства LA-32, будет интересно посмотреть на то, как происходит
этот процесс в операционной системе Windows. Ниже приведена выдержка из
документации Microsoft Platform SDK для операционных cncTeMWindows 95/98. Основу
ядра операционных систем Windows 95/98 составляет диспетчер виртуальных машин
(Vlrtual Machine Manager, или VMM), который является 32-разрядной программой,
написанной для защищенного режима работ процессора. К его основным функциям
относятся: создание, запуск, отслеживание и завершение работы виртуальных
машин. Кроме того, VMM обеспечивает поддержку функций, предназначенных для
распределения памяти, управления процессами, прерываниями и исключениями. Он
также обеспечивает работу виртуальных устройств ^ 32-разрядных модулей,
обрабатывающих прерывания и ошибки, генерируемые реальными устройствами, и
управляющих доступом со стороны прикладных программ к оборудованию компьютера и
установленного программного обеспечения. И VMM, и модули виртуальных устройств
выполняются в едином 32-разрядном линейном адресном пространстве с нулевым
уровнем привилегий. Операционная система создает в таблице глобальных
дескрипторов два элемента: один для сегмента кода, а другой для сегмента
данных. Базовый адрес обоих сегментов равен нулю и никогда не изменяется.
Диспетчер виртуальных машин обеспечивает поддержку выполнения множества потоков
команд, а также многозадачность с вытеснением на основе приоритетов. Он
позволяет одновременно запускать несколько приложений в отдельных виртуальных
машинах и распределять время центрального процессора между ними. Нам осталось
только уточнить терминологию. В приведенном выше отрывке из документации
Microsoft Platform SDK под термином виртуальная машина понимается процесс или
задача, по крайней мере, так это определено в документации по процессорам
семейства IA-32 фирмы Intel. Виртуальная машина состоит из программного кода,
обслуживающих ее программ, памяти и регистров. Каждой виртуальной машине
назначается собственное адресное пространство, пространство портов
ввода-вывода, таблица векторов прерываний и таблица локальных дескрипторов.
Приложениям, которые запускаются в виртуальной машине в режиме эмуляции
процессора 8086, назначается третий уровень привилегий. Программы, написанные
для защищенного режима, могут выполняться с первым, вторым или третьим уровнем
привилегий. Программа
1
.386
.model flat,stdcall
option casemap:none
include C:masm32INCLUDEWINDOWS.INC
include C:masm32INCLUDEKERNEL32.INC
include C:masm32INCLUDEUSER32.INC
include C:masm32INCLUDEADVAPI32.INC
include
C:masm32INCLUDEGDI32.INC
include my.inc
includelib C:masm32libcomctl32.lib
includelib C:masm32libuser32.lib
includelib C:masm32libgdi32.lib
includelib C:masm32libkernel32.lib
includelib C:masm32libuser32.lib
includelib C:masm32libadvapi32.lib ;########################################################### MAIN_WINDOW_PROC
PROTO :DWORD , :DWORD , :DWORD , :DWORD ;########################################################### ;data--data--data--data--data--data--data--data--data--data--
PROC ;---------------------------------------------------------------------------------------------- .DATA HINST
DWORD NULL HWND_WIN
DWORD NULL ;- String_CLASS
DB "MY_WINDOW",0 String_CAPTION
DB "MY_CAPTION",0 ;- MSG_WIN
MSG <0> ;############################################################## ;code--code--code--code--code--code--code--code--code--code--
PROC ;-------------------------------------------------------------------------------------------------- .CODE
START:
invoke GetModuleHandle , Null
mov HINST , EAX
;-
CALL MY_REGISTER_CLASS
cmp EAX , null
je EXIT
;-
invoke CreateWindowEx , Null , addr String_CLASS , addr
String_CAPTION ,
WS_VISIBLE + WS_CAPTION + WS_SYSMENU , 100 , 100 , 500 ,
500 ,
NULL , NULL , HINST , NULL
;-
MOV HWND_WIN , EAX
;-
invoke ShowWindow , HWND_WIN , TRUE
invoke UpdateWindow , HWND_WIN
;==================== MSG_LOOP:
invoke GetMessage , addr MSG_WIN , null , null , null
CMP Eax , FALSE
JE EXIT
invoke TranslateMessage , addr MSG_WIN
invoke DispatchMessage , addr MSG_WIN
JMP MSG_LOOP
;==================== EXIT:
invoke ExitProcess , Null ;---------------------------------------------------------------------------------------------------------- ;
GET POINT SCREEN ;----------------------------------------------------------------------------------------------------------
MY_REGISTER_CLASS
PROC local
_Struct_WNDCLASS : WNDCLASS ;-
Mov _Struct_WNDCLASS.style , NULL ; стиль окна
; Mov Eax ,
Mov _Struct_WNDCLASS.lpfnWndProc , MAIN_WINDOW_PROC ; процедура окна
Mov _Struct_WNDCLASS.cbClsExtra , null ; дополнительная память для класса
Mov _Struct_WNDCLASS.cbWndExtra , null ; дополнительная память для окна
Mov Eax , HINST
Mov _Struct_WNDCLASS.hInstance , Eax ; handle приложения
Mov _Struct_WNDCLASS.lpszMenuName , NULL ; идентификатор меню
Mov _Struct_WNDCLASS.lpszClassName , offset String_CLASS ; адрес строки класса ;-
invoke LoadIcon , NULL , IDI_ASTERISK
Mov _Struct_WNDCLASS.hIcon , Eax
;-
invoke LoadCursor , NULL , IDC_IBEAM
Mov _Struct_WNDCLASS.hCursor , Eax ;-
; invoke CreateSolidBrush , 00FF0000h ; возвратит идентификатор кисти
invoke GetStockObject , NULL_BRUSH
Mov _Struct_WNDCLASS.hbrBackground , Eax ;==============
invoke RegisterClassA , addr _Struct_WNDCLASS ;==============
ret MY_REGISTER_CLASS
ENDP ;--------------------------------------------------------------------------------------------------------------------------- ;
WINDOW
PROCEDURE ;--------------------------------------------------------------------------------------------------------------------------- MAIN_WINDOW_PROC
PROC USES EBX ESI EDI
hWnd_ :DWORD , MESG :DWORD , wParam :DWORD ,
lParam:DWORD ;-
CMP MESG , WM_DESTROY
JE WMDESTROY ;----
invoke DefWindowProc , hWnd_ , MESG , wParam , lParam
jmp FINISH ;----
WMDESTROY:
invoke PostQuitMessage , False ;- FINISH: RET
16 MAIN_WINDOW_PROC
ENDP ;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ END START Программа
2
.386
.model flat,stdcall
option casemap:none
include C:masm32INCLUDEWINDOWS.INC
include C:masm32INCLUDEKERNEL32.INC
include C:masm32INCLUDEUSER32.INC
include C:masm32INCLUDEADVAPI32.INC
include
C:masm32INCLUDEGDI32.INC
include my.inc
includelib C:masm32libcomctl32.lib
includelib C:masm32libuser32.lib
includelib C:masm32libgdi32.lib
includelib C:masm32libkernel32.lib
includelib C:masm32libuser32.lib
includelib C:masm32libadvapi32.lib ;########################################################### MAIN_WINDOW_PROC
PROTO :DWORD , :DWORD , :DWORD , :DWORD ;########################################################### ;data--data--data--data--data--data--data--data--data--data--
PROC ;---------------------------------------------------------------------------------------------- .DATA HINST
DWORD NULL HWND_WIN
DWORD NULL ;- ZAGLUSHKA
DWORD FALSE ;- String_CLASS
DB "MY_WINDOW",0 String_CAPTION
DB "MY_CAPTION",0 String_CAPTION_SECOND
DB "MY_SECOND_CAPTION",0 ;- MSG_WIN
MSG <0> ;############################################################## ;code--code--code--code--code--code--code--code--code--code--
PROC ;-------------------------------------------------------------------------------------------------- .CODE
START:
invoke GetModuleHandle , Null
mov HINST , EAX
;-
CALL MY_REGISTER_CLASS
cmp EAX , null
je EXIT
;-
invoke CreateWindowEx , NULL , addr String_CLASS , addr
String_CAPTION ,
WS_OVERLAPPEDWINDOW , 100 , 100 , 500 , 500 ,
NULL , NULL , HINST , NULL
;-
MOV HWND_WIN , EAX
;-
invoke ShowWindow , HWND_WIN , TRUE
invoke UpdateWindow , HWND_WIN
;==================== MSG_LOOP:
invoke GetMessage , addr MSG_WIN , null , null , null
CMP Eax , FALSE
JE EXIT
invoke TranslateMessage , addr MSG_WIN
invoke DispatchMessage , addr MSG_WIN
JMP MSG_LOOP
;==================== EXIT:
invoke ExitProcess , Null ;---------------------------------------------------------------------------------------------------------- ;
GET POINT SCREEN ;----------------------------------------------------------------------------------------------------------
MY_REGISTER_CLASS
PROC local
_Struct_WNDCLASS : WNDCLASS ;-
Mov _Struct_WNDCLASS.style , NULL ; стиль окна
; Mov Eax ,
Mov _Struct_WNDCLASS.lpfnWndProc , MAIN_WINDOW_PROC ; процедура окна
Mov _Struct_WNDCLASS.cbClsExtra , null ; дополнительная память для класса
Mov _Struct_WNDCLASS.cbWndExtra , null ; дополнительная память для окна
Mov Eax , HINST
Mov _Struct_WNDCLASS.hInstance , Eax ; handle приложения
Mov _Struct_WNDCLASS.lpszMenuName , NULL ; идентификатор меню
Mov _Struct_WNDCLASS.lpszClassName , offset String_CLASS ; адрес строки класса ;-
invoke LoadIcon , NULL , IDI_ASTERISK
Mov _Struct_WNDCLASS.hIcon , Eax
;-
invoke LoadCursor , NULL , IDC_IBEAM
Mov _Struct_WNDCLASS.hCursor , Eax
;-
; invoke CreateSolidBrush , 00FF0000h ; возвратит идентификатор кисти
invoke GetStockObject , LTGRAY_BRUSH
Mov _Struct_WNDCLASS.hbrBackground , Eax ;==============
invoke RegisterClassA , addr _Struct_WNDCLASS ;==============
ret MY_REGISTER_CLASS
ENDP ;--------------------------------------------------------------------------------------------------------------------------- ;
WINDOW
PROCEDURE ;--------------------------------------------------------------------------------------------------------------------------- MAIN_WINDOW_PROC
PROC USES EBX ESI EDI
hWnd_ :DWORD , MESG :DWORD , wParam :DWORD , lParam:DWORD
LOCAL
_hwnd_Win :DWORD ;-
CMP MESG , WM_DESTROY
JE WMDESTROY
CMP MESG , WM_CREATE
JE WMCREATE ;----
invoke DefWindowProc , hWnd_ , MESG , wParam , lParam
jmp FINISH ;----
WMCREATE:
CMP ZAGLUSHKA , TRUE
JE FINISH
MOV ZAGLUSHKA , TRUE
invoke CreateWindowEx , NULL , addr String_CLASS ,
addr
String_CAPTION_SECOND ,
WS_CHILD + WS_CAPTION + WS_SYSMENU , 200 , 200 , 200 , 200 ,
hWnd_ , NULL , HINST , NULL
;-
Mov _hwnd_Win , Eax ;-
invoke ShowWindow , _hwnd_Win , TRUE
invoke UpdateWindow , _hwnd_Win
jmp FINISH WMDESTROY:
mov Eax , hWnd_
cmp Eax , HWND_WIN
Jne FINISH
invoke PostQuitMessage , False ;- FINISH: RET
16 MAIN_WINDOW_PROC
ENDP ;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ END
START Заключение В данной курсовой работе был
изучен теоретический материал по работе с прерываниями DOS,
вводом и выводом чисел при помощи подключаемого модуля IO,
работе с одномерными и двухмерными массивами, тестированию состояния флагов,
рассмотрены основные принципы и навыки работы с компилятором и отладчиком.
Разработана схема алгоритма программы и реализована на языке низкого уровня
ассемблер. Язык Ассемблера - мощное средство программирования. Он позволяет
программисту осуществлять всестороннее управление аппаратными средствами ЭВМ.
Однако такое управление заставляет программиста вникать в детали, далекие от
основного содержания программы. Все преимущества языка Ассемблера оборачиваются
подчас пустой тратой времени на многочисленные детали. Несмотря на то, что Ассемблер является машинно-ориентированным
языком, то есть языком низкого уровня, программист может применять его для
работы, как на высоком. К преимуществам Ассемблера можно отнести: 1. Данный язык программирования позволяет создавать приложения,
которые будут более эффективны, чем аналогичные приложения, написанные на языке
высокого уровня, т.е. приложения будут более короткими и при этом более быстро выполнимыми. 2. Язык Ассемблера позволяет программисту выполнять действия,
которые либо вообще нельзя реализовать на других языках и в частности на языках
высокого уровня, либо выполнение которых займет слишком много машинного времени
в случае привлечения дорогих средств языка высокого уровня. К недостаткам языка следует отнести: 1. По мере увеличения своего размера программа на Ассемблере
теряет наглядность. Это связано с тем, что в ассемблерных программах следует
уделять много внимания деталям. Язык требует планирования каждого шага ЭВМ.
Конечно, в случае небольших программ это позволяет сделать их оптимальными с
точки зрения эффективности использования аппаратных средств. В случае же
больших программ бесконечное число деталей может помешать добиться оптимальности
программы в целом, несмотря на то, что отдельные фрагменты программы будут
написаны очень хорошо. 2. Для программирования на данном языке необходимо очень хорошо
знать структуру компьютера и работу аппаратных устройств, так как Ассемблер
работает непосредственно с устройствами. Можно сделать вывод, что на языке Ассемблера можно сделать любое
приложение, любую программу, но для написания больших программ лучше
использовать языки высокого уровня. Цели курсового
проектирования были достигнуты. Список источников 1.
Юров В.И. Ассемблер: Учебник для вузов. - СПб.: Питер, 2003. – 637
с. 2.
Юров В.И. Ассемблер. Практикум. 2-е изд. – СПб.: Питер, 2006. –
399 с. 3.
Калашников О.А. Ассемблер? Это просто! Учимся программировать. –
СПб.: БХВ -Петербург, 2006. – 384 с. 4.
Магда Ю.С. Ассемблер для процессоров Intel Pentium. – СПб.: Питер, 2006. – 410 с. 5.
В.Ю.
Пирогов. Assembler. Учебный курс.: М., 2001. 6.
Марек Р. Ассемблер на примерах. Базовый курс. – СПб.: Наука и
техника, 2005. – 240 с. 7.
Фролов А.В., Фролов Г.В. Библиотека системного программиста. Т. 1.
Часть 1, 2, 3. Операционная система MS-DOS. М: ДИАЛОГ-МИФИ, 1991, 1993 8.
Рудаков П. И., Финогенов К. Г. Язык ассемблера: уроки
программирования. - М.: ДИАЛОГ-МИФИ, 2001. - 640 с. 9.
Ирвин, Кип. Язык ассемблера для процессоров Intel, 4-е издание.:
Пер. с англ. — M.: Издательский дом "Вильямс", 2005. — 912 с. 10.
Питер Абель, Ассемблер и программирование для IBM PC, Пер. с англ.
- Технологический институт Британская Колумбия