Графические устройства

         

Оформление программных модулей

Общедоступные подпрограммы могут располагаться в исходных или в объектных модулях. Исходный модуль состоит из программного сегмента, содержащего описание одной или нескольких подпрограмм на языке Макроассемблера. Объектный модуль получается в результате обработки исходного модуля компоновщиком. Исходные модули могут располагаться непосредственно в тексте основной программы или храниться в отдельных файлах, в последнем случае они являются общедоступными. Объектные модули могут храниться в виде отдельных файлов, имеющих тип obj, или в библиотечных файлах, имеющих тип lib. Они подключаются к задаче только в процессе ее компоновки (сборки). В данном разделе описаны рекомендации, которых следует придерживаться при оформлении обоих типов модулей.

Пример модуля в теле задачи

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


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

Пример В.2. Сегмент с описанием подпрограмм NxtWin, SetWin, PrevWin

subr SEGMENT word public 'subr1 ; начало сегмента
ASSUME cs:subr, ds:@data ; установка соответствия ; .386 ; тип микропроцессора
; Далее располагается текст примера В.1, содержащий
; описание подпрограмм NxtWin, SetWin и PrevWin subr ENDS ; конец сегмента

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

Замечание
Обратите внимание на то, что между параметрами директивы SEGMENT отсутствуют запятые!

Директива ASSUME нужна для того, чтобы Макроассемблер мог определить, что будет находиться в сегментных регистрах cs и ds при выполнении подпрограмм. Без этого невозможна компиляция команд. С регистром cs всегда связывается имя сегмента, в котором расположена директива ASSUME. С регистром ds связывается имя сегмента данных, который описан вне данного модуля. Имя, с которым ассоциируется ds, зависит от способа описания сегмента данных в основном тексте программы. В примере В.2 предполагается, что сегмент данных был описан с помощью специальной директивы .data (см. раздел в приложении Б).

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

В примере В.2 строка, содержащая третью директиву, начинается с символа "точка с запятой". Подпрограммы Nxtwin, setwin и Prevwin составлены с использованием набора команд микропроцессора Intel 8086, поэтому в данном случае директива .386 не нужна. Однако большинство описанных в книге примеров рассчитано на возможности микропроцессора Intel 80386, и для их компиляции данная директива необходима.

Разрядность сегмента

При обработке директив описания сегментов Макроассемблер проверяет установленный тип микропроцессора и выбирает соответствующий режим выполнения команд, расположенных в сегменте (реальный или защищенный). По умолчанию установлен 16-разрядный (реальный) режим выполнения команд и набор инструкций для микропроцессора Intel 8086.

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

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

Начиная с версии 6.0, Макроассемблер поддерживает директивы .486 и .586, разрешающие использование новых инструкций микропроцессоров Intel 486 и Pentium. Кроме того, появилась возможность выбора разрядности сегментов по умолчанию или ее явного описания с помощью ключевых слов USE16 И USE32.

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

Расположение сегмента в тексте программы зависит от версии MASM, который вы используете. Если это MASM 6.0 и выше, то дополнительный сегмент может располагаться как перед основным текстом программы (перед сегментом кодов), так и после него. Но если вы работаете с MASM 5.1, то дополнительный сегмент может располагаться только перед сегментом кодов. В противном случае при каждом вызове подпрограмм, расположенных в дополнительном сегменте, MASM 5.1 выводит аварийное сообщение о необходимости предварительного описания подпрограммы.

Подключение исходного модуля.

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

Для включения содержимого файла в нужном месте текста программы указывается специальная директива:

INCLUDE спецификация_файла

Спецификация должна быть настолько подробной, чтобы Макроассемблер мог найти и прочитать файл. Очень часто эта директива применяется для подключения файлов, содержащих тексты макроопределений. Обычно в установочный комплект MASM включено несколько таких файлов, они могут располагаться в специальном каталоге INCLUDE.

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

  • каждый сегмент имеет уникальное имя;
  • размер кода после компиляции не превышает 65 536 байтов;
  • в тексте отсутствуют ошибки, т. е. он должен быть предварительно отлажен.

Таким образом, содержимое включаемого файла становится частью текста программы и компилируется Макроассемблером. Если в задании на компиляцию указан файл листинга, то, просмотрев его, вы увидите полный результат компиляции, в том числе и включенного файла.

Пример объектного модуля

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

Изменения заключаются в том, что в начале текста модуля добавлены две директивы:

  • EXTERN — для описания используемых внешних имен;
  • PUBLIC — для объявления подпрограмм модуля общедоступными.

Кроме этого, нужен признак конца текста модуля, которым является директива END. В примере В.З показано, что изменится в модуле примера В.2.

Пример В.З. Исходный текст для получения объектного модуля

; Сюда надо вставить макроопределения из примера 2.12
subr SEGMENT word public 'subr' ; начало сегмента
EXTERN GrUnit:word, Cur win:word, VMC:dword
PUBLIC NxtWin, SetWin, PrevWin
ASSUME cs:subr ; установка соответствия
; .386 ; тип микропроцессора
; Далее располагается текст примера В.1, содержащий
; описание подпрограмм NxtWin, SetWin и PrevWin
subr ENDS ; конец сегмента
END ; конец текста модуля

Текст примера В.З начинается с комментария, напоминающего о том, что перед описанием сегмента надо расположить макроопределения PushReg и PopReg. Они используются в подпрограмме SetWin для сохранения в стеке и последующего восстановления содержимого регистров ах, bx и dx (см. пример В.1). Можно отказаться от их включения, заменив первый макровызов тремя командами push, а второй тремя командами pop. Раньше мы об этом не говорили, поскольку модуль предназначался для совместной компиляции с текстом программы, в котором описаны указанные макроопределения.

В директиве EXTERN перечислены имена переменных GrUnit, cur_win и VMC, которые описаны в сегменте данных основной программы. Назначение и способ определения значений этих переменных подробно обсуждались в главе 2 , а их описание показано в примере 2.11 той же главы.

Следующая директива PUBLIC объявляет имена подпрограмм Nxtwin, setwin и PrevWin общедоступными.

Обратите внимание на то, что в директиве ASSUME описан только кодовый сегмент, а если вы работаете с MASM 6.0 или более поздней версией, то эту директиву можно вообще исключить.

Далее в модуле должно располагаться описание подпрограмм, текст которого приведен в примере В.1, а после него директива END без указания метки, поскольку модуль не является выполняемой задачей.

Текст примера В.З не может быть использован для включения в основную программу с помощью директивы INCLUDE. Он компилируется отдельно. При этом Макроассемблер формирует объектный модуль, который понадобится компоновщику при построении задачи.

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

PUBLIC GrUnit, Cur_win, VMC
EXTERN NxtWin:far, SetWin:far, PrevWin:far

Первая из них объявляет переменные GrUnit, cur_win и VMC общедоступными, а вторая описывает имена и типы внешних подпрограмм. После включения указанных директив основной текст задачи компилируется для получения объектного модуля.

Построение задачи

Условимся считать, что файл, содержащий объектный модуль основного текста будущей задачи, имеет имя bmpsuper.obj, а файл, содержащий объектные модули подпрограмм, имя bmpsub.obj. Для их объединения в одну задачу выполняется следующая команда:
link bmpsuper bmpsub или link bmpsuper+bmpsub

В данном случае предполагается, что файлы bmpsuper и bmpsub имеют тип obj и расположены в том же каталоге, в котором находится задача iink.exe (компоновщик). Если это не так, то указывается спецификация, позволяющая найти файлы в других каталогах.

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

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

Заключение.
При программировании на ассемблере можно использовать подпрограммы, хранящиеся либо в исходных модулях, подключаемых во время компиляции основного текста, либо в виде объектных модулей, объединяемых с главным модулем при построении задачи. Чему отдать предпочтение, решать вам.
Однако если основной модуль создается на одном из алгоритмических языков (Фортран, Паскаль, Си и пр.), то вспомогательные подпрограммы, составленные на ассемблере, должны оформляться в виде объектных модулей.
И последний совет. Постепенно у вас накопится достаточно много объектных модулей. Для упрощения собственного труда их лучше объединить в одну или несколько библиотек. В комплект поставки Макроассемблера обязательно входит библиотекарь, хранящийся в файле нь.ехе. Он выполняет много полезных функций, связанных с созданием, просмотром, пополнением и изменением библиотек объектных модулей. Библиотекарь поддерживает активный диалог с оператором, поэтому научиться работать с ним несложно.


Содержание раздела