Структура данных и алгоритмы на языке Pascal

Интерфейс с программами на ассемблере и связь с операционной системой

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

ИНТЕРФЕЙС С АССЕМБЛЕРОМ

Имеется несколько причин, по которым требуется составлять программу на ассемблере:
  • для повышения скорости работы и эффективности использования памяти;
  • для выполнения машинно-зависимых функций, которые отсутствуют в Паскале;
  • для того, чтобы можно было воспользоваться пакетом программ общего назначения, написанных на ассемблере.
Хотя компилятор языка Паскаль создает эффективный компактный объектный код, никакой компилятор не сможет постоянно создавать более эффективный и компактный код, чем код, написанный компетентным программистом. Небольшое различие обычно не означает, что при написании программы на ассемблере не потребуется затратить заметно больше времени. Однако в особых случаях требуется составлять программы и процедуры на ассемблере, чтобы обеспечить быструю работу программы. Это требуется делать для часто используемых программ и процедур, существенно влияющих на общее быстродействие программы. Хорошим примером такого применения ассемблера является пакет подпрограмм, выполняющих операции над числами с плавающей запятой. Кроме того, специальное оборудование иногда требует точной синхронизации в работе, которая обеспечивается только программированием на ассемблере.
Многие ПЭВМ, включая машины, построенные на базе процессоров 8086 и 8088, обладают возможностями, которыми нельзя воспользоваться непосредственно в Паскале. Например, используя Паскаль нельзя изменить сегменты данных и возникают трудности при доступе к специальным регистрам.
На практике часто приобретаются библиотеки подпрограмм. В качестве таких библиотек подпрограмм можно назвать широко распространенные библиотеки подпрограмм для работы с числами с плавающей запятой и пакеты графических программ. Иногда имеются только объектные коды этих подпрограмм, поскольку разработчик не поставляет исходные тексты. В одних случаях эти подпрограммы могут просто вызываться в программе на Паскале. В других случаях приходится составлять интерфейсный модуль для обеспечения связи Паскаля с приобретенными подпрограммами.
Имеется два способа применения ассемблера в программе на Паскале. Во-первых, можно написать отдельную программу, ассемблировать ее и затем подсоединить ее к основной программе, используя команду "external". Во-вторых, в программе на языке Паскаль можно непосредственно записывать код на ассемблере.

ВНУТРЕННИЕ ФОРМАТЫ ДАННЫХ И СОГЛАШЕНИЯ О СВЯЗЯХ В ЯЗЫКЕ ПАСКАЛЬ

Прежде чем писать подпрограмму на ассемблере для использования ее в программе на языке Паскаль необходимо понять, как данные представляются в программе и как они передаются между подпрограммами. Для версии ИБМ все глобальные переменные и константы хранятся в сегменте данных и доступ к ним осуществляется с использованием регистра DS. Все локальные переменные помещаются в стек и доступ к ним осуществляется с применением регистра ВР, который используется для индексации стека. Рис.1 показывает способ хранения для данных каждого типа. Следует иметь в виду, что байты указателей задаются в обратном порядке. Следовательно, указатель, имеющий смещение В000 и сегмент 0010, будут храниться в следующем виде:

Соглашения о связях представляют собой метод, который используется компьютером языка Паскаль для передачи информации подпрограмм и передачи результатов. В языке Паскаль для передачи параметров в подпрограмму используется стек /он также используется для передачи результатов функции/. При этом в регистре АХ передается результат однобайтовой или однословной функции. Точное содержимое стека при вызове подпрограммы зависит от типа передаваемых переменных и от типа передачи параметра.

         ----------------------------------------------------------------
           Тип       Длина                     Комментарии
         данного    данного
         ----------------------------------------------------------------
         двоичный   1 байт
         однобай-   1 байт
         товый
         символьный 1 байт
         целый      2 байта
         целый ко-  1 байт
         роткий
         целый      4 байта
         длинный
         слово      2 байта
         вещест-    6 байт    Первый байт содержит мантиссу, следующий
         венный               байт является младшим и последний байт
                              является старшим
         одиночный  4 байта   Стандартный формат числа с плавающей
                              запятой
         двойной    8 байт    Стандартный формат числа с плавающей
                              запятой и с двойной точностью
         расширен-  10 байт   Стандартный формат числа с плавающей
         ный                  запятой и повышенной точностью
         для вы-    3 байт    Дополнительный код числа с плавающей
         числений             запятой
         строка     перемен-  Первый байт содержит текущую длину строки;
                    ная       следующий байт является первым символом
                              строки
         множество  перемен-  Поскольку каждый элемент использует один
                    ная       бит и максимальное число элементов равно
                              256, то максимальная длина множества равна
                              32 байта
         Указатель  4 байта   Два младших байта содержат смещение, а
                              старшие байты содержат сегмент. Байты
                              хранятся в обратном порядке. Нулевое
                              значение занимает 4 байта
         Массивы    перемен-  Значения с меньшим индексом имеют меньший
                    ная       адрес памяти
         Записи     перемен-  Первое поле имеет наименьший адрес,
                    ная       последнее поле имеет самый старший адрес
         ---------------------------------------------------------------
Рис.1. Представление данных в памяти

Параметры-значения

Параметры-значения передаются в одном направлении: в подпрограмму передается значение параметра, но любые изменения этого параметра не оказывают влияния на действительную переменную, которая использовалась при вызове подпрограммы. Подпрограмме передается не адрес этой переменной, а копия ее значения и поэтому сама переменная не изменяется. По существу процедуре и функции передается лишь некоторое значение.
Значения будут помещаться в стек при передаче параметров следующих типов:
  • двоичного /булевского/;
  • символьного;
  • целого;
  • целого длинного;
  • целого короткого;
  • байта;
  • слова;
  • вещественного;
  • указателя;
  • перечисления.
Для двойных слов сначала в стек помещается старшая часть значения и затем младшая часть.
Массивы и записи, размер которых превышает четыре байта, в действительности не передаются функции. Передается адрес /сегмент и смещение/ переменной. Сначала в стек помещается сегмент и затем смещение. /Массивы, размер которых меньше пяти байт непосредственно помещаются в стек/. Множества и строки тоже передаются с использованием указателей на действительные переменные. Поскольку для данных этих типов в функции передаются адреса, необходимо делать копии действительных данных, чтобы изменения данных, произведенные внутри функции, не повлияли на эти переменные вне функции.

Параметры-переменные

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

Передача результата функции

Когда написанная на языке Паскаль функция завершает свою работу, она передает значение результата обратно в вызывающую программу. Для всех скалярных типов кроме вещественного значение передается через регистр АХ. Для булевской переменной должен также устанавливаться флажок нуля: единичное значение означает булевское значение "истина", а нулевое значение означает булевское значение "ложь". При передачи указателей сегмент передается в регистр DX, а смещение передается в регистр АХ. Вещественные переменные передаются в виде DX:BX:AX, причем в регистр DX помещается старшее слово, а в регистр АХ помещается младшее слово.
При передаче в качестве результата символьных строк, массивов и записей адрес значения передается в виде DX:AX. Результат функции помещается сразу за адресом возврата. На рис.2 показан вид стека при вызове функции.
Рис.2. Вид стека при вызове функции

Сохранение регистров

Во всех подпрограммах на ассемблере необходимо предусмотреть сохранение регистров BP, DS и SS. Обычно это делается при помощи инструкций "push" и "pop".
Если составляется программа на ассемблере, которая будет обращаться к подпрограмме на языке Паскаль, то нужно следовать всем соглашениям о связях, которые рассматривались ранее. Только при соблюдении этих соглашений можно обеспечить правильный вызов программы на языке Паскаль из модуля, написанном на ассемблере.

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

Теперь, когда рассмотрены соглашения о связях, приведем код действительной программы на ассемблере. Предположим, что требуется составить программу на ассемблере для следующей функции:
function Xmul(a, b: integer): integer; begin
  a: = a*b;
  Xnul: = a;
end;
Поскольку эта функция будет вызываться из программы на Паскале, то два аргумента целого типа будет помещаться в стек, занимая два слова. Поэтому при следующем вызове функции xmu (10,20); первым будет помещаться в стек значение 20 и затем значение 10.
Следует помнить, что для скалярных переменных значения помещаются в стек. Для массива и записи в стек помещается адрес.
Результат этой функции будет помещен в регистр АХ. Ниже приводится код этой функции на ассемблере:
code    segment 'code'
  assume cs:code
public  xmul
xmul    proc near

  ;сохранить указатель стека
    push bp
    mov bp,sp

  ;получить первый параметр
    mov ax,[bp]+4
  ;умножить на второй параметр
    mul word ptr [bp]+6
  ;восстановить "вр" и  очистить стек
  ;результат уже находится в регистре АХ
    pop bp
    ret 4
xmul    endp
code    ends
end
Следует отметить, что все регистры сохраняются при помощи инструкций "push" и "рор" и доступ к аргументам осуществляется через стек. Кроме того, функция "xmul" объявлена как "public". Это необходимо для обеспечения Паскалем ее правильной связи с остальной программой. Если вы незнакомы с ассемблером процессоров 8086 и 8088, то вам могут помочь следующие пояснения. Рассмотрим следующий код:
mov  bp,sp
mov  ax,[bp]+4.
Эти инструкции помещают адрес вершины стека в регистр ВР и затем сдвигают четвертый байт дальше в стек /т.е. параметр "а" помещается в регистр АХ/. Параметры занимают четвертый и шестой байты, поскольку адрес возврата и инструкция "push bp" занимают четыре байта. Следовательно, параметры начинаются на четыре байта ниже вершины стека.
Внешняя функция "xmul" является "близкой" процедурой. Если процедура объявляется в программе или в разделе реализации блока, она должна объявляться с параметром "near". Если внешняя функция объявляется в секции интерфейса блока, то она должна будет объявляться с параметром "far".
Перед использованием этой внешней функции она должна быть ассемблирована с применением макроассемблера фирмы "Майкрософт". Следует помнить, что все процедуры, функции и переменные, к которым необходимо обеспечить доступ в вашей программе, должны объявляться с параметром "public".
В вашей программе на Паскале можно использовать указанную внешнюю функцию следующим образом:
{программа,  которая обеспечивает связь с внешней подпрограммой, написанной на ассемблере}
program asmtest;

  {SL XMUL}

var
  a, b, c: integer;

function xmul(x, y: integer): integer; external;

begin
  a: = 40;
  b: = 20;
  c: = xmul(a,b); {умножение "а" на "в" и получение результата}
  WriteLn(c);
end.
Директива компилятора $L используется для указания на необходимость
Следует помнить, что все примеры даются для ассемблера процессоров 8086 и 8088. Если используется Паскаль версии СР/М, то примеры должны быть изменены в соответствии с руководством пользователя по Паскалю. Кроме того, связь с подпрограммами на языке ассемблера будет отличаться для Паскаля версий более ранних, чем версия 4.0.

Встроенный код ассемблера

В Паскале предусматривается возможность непосредственного включения кода на ассемблере. Таким образом, нет необходимости составлять отдельную внешнюю подпрограмму. Такой подход имеет два преимущества: во-первых, программист не должен писать код, реализующий связь между программами; во-вторых, весь код расположен в одном месте, что делает простой сопровождение программы. Единственный недостаток заключается в том, что встроенный код ассемблера имеет неудобный формат.
Оператор "inline" позволяет внутри программы на Паскале использовать код на ассемблере. Общая форма этого оператора будет следующей: inline (значение/.../значение); где "значение" представляет собой любую правильную инструкцию ассемблера или данные. Для ссылки на счетчик адреса может использоваться звездочка. (В ассемблере процессора 8088 для этой цели используется валютный знак. Поскольку в Паскале валютный знак используется для шестнадцатеричных чисел, то для обозначения счетчика адреса используется звездочка).
Для малых значений, которые могут разместиться в одном байте, используется только один байт. В противном случае для хранения переменной используются два байта. Если вы хотите поступить иначе, то следует воспользоваться директивами ">" и "<". Если перед значением стоит знак меньше, то будет использован только младший байт. Если перед значением стоит знак больше, то будет сформировано двухбайтовое слово с нулевым старшим байтом. Например, <$1234 приведет к формированию только одного байта со значением $34, а >$12 приведет к формированию двух байт со значением $0012.
Следующая короткая программа перемножает два целых числа, используя функцию "Mul", которая использует код на ассемблере для выполнения действительного умножения. Сравните эту программу с внешней подпрограммой "xmul".
{ пример встроенного кода ассемблера }
program asm_inline;

var
  a, b, c: integer;

function Mul(x, y: integer): integer;
begin
  inline($80/$46/$04/   {mov ax,[bp]+4}
  $F6/366/$06/   {mul [bp]+6   }
  $89/$EC/       {mov sp,bp}
  $56/           {pop bp}
  $02/$06/$00/   {ret 6}

end;

begin
  a: = 10;
  b: = 20;
  c: = Mul(a,b);
  WriteLn(c);
end.
В данном случае компилятор Паскаля автоматически генерирует код для возврата из функции. Когда компилятор встречает оператор "inline", в этом месте он генерирует указанные оператором машинные инструкции.
Часто встроенный код ассемблера используется для обеспечения связи с оборудованием, которое не поддерживается непосредственно в языке Паскаль. Например, приводимая ниже подпрограмма может использоваться для включения вентилятора, когда показание датчика температуры достигнет определенной величины. В данном случае предполагается, что установка в единичное значение порта 200 приводит к включению вентилятора:
procedure fan(temp:integer);
{вентилятор  включается,  когда  температура достигнет 100 градусов }
begin
  if temp>=100 then
  inline(100/00/01/  {mov AX,1}
  $E7/$C8);   {out 200,AX}
end;
Следует помнить, что компилятор Паскаля обеспечивает необходимый код для входа и выхода из функции. Пользователю необходимо лишь обеспечить тело функции и придерживаться соглашений о связях при доступе к аргументам.
При использовании указанного метода создается машинно-зависимый код, что затрудняет перенос программы на новую машину или в среду новой операционной системы. Однако в тех случаях, когда нельзя обойтись без кода ассемблера, применение указанного метода оправдано.

Когда следует применять ассемблер

Большинство программистов используют ассемблер только при крайней необходимости, поскольку программировать на ассемблере достаточно сложно. Общее правило заключается в том, что вообще не следует использовать ассемблер - он создает слишком много проблем. Однако можно указать два случая практического применения ассемблера. Первый возникает, когда нет другого пути решения задачи. Например, когда требуется обеспечить непосредственную связь с оборудованием, управление которым не предусмотрено в языке Паскаль.
Во-вторых, такая ситуация возникает при необходимости уменьшения времени выполнения программ и все возможности оптимизации кода Паскаля исчерпаны. В данном случае необходимо делать тщательный выбор функций для их кодирования на ассемблере. Если выбор будет сделан неправильно, то эффект будет незначительный. При правильном выборе эффект может быть очень большим. Для того, чтобы определить какие подпрограммы требуют перекодировки, необходимо определить операционную блок-схему вашей программы. Обычно для реализации на ассемблере выбираются подпрограммы, которые используются внутри циклов, поскольку они выполняются много раз. Кодирование на ассемблере процедуры или функции, которые выполняются один или два раза, может не дать заметного эффекта, а кодирование на ассемблере функции, которая выполняется много раз, может дать такой эффект. Например, рассмотрим следующую процедуру:
procedure ABC;
var
  t: integer;

begin
  init;
  for t:=0 to 1000 do begin
    phase1;
    phase2;
    if t=10 then phase3;
  end;
  byebye;
end;
Перекодировка процедур "init" и "byebye" может не повлиять заметно на скорость выполнения программы, поскольку они выполняются только один раз. Процедуры "phase1" и "phase2" выполняются 1000 раз и их перекодировка, несомненно, даст заметный эффект. Несмотря на то, что процедура "phase3" расположена внутри цикла, она выполняется лишь один раз и поэтому ее перекодировка не даст эффекта.
При тщательном выборе процедур для их кодировки на ассемблере можно добиться улучшения быстродействия программы за счет перекодировки лишь небольшого числа подпрограмм.

СВЯЗЬ С ОПЕРАЦИОННОЙ СИСТЕМОЙ

Поскольку часто системные программы пишутся на языке Паскаль, необходимо обеспечить непосредственную связь с операционной системой для выполнения определенных операций в обход стандартного интерфейса Паскаля. Может возникнуть также потребность в специальных системных функциях, которые отсутствуют в Паскале. По этой причине применение специальных средств операционной системы является обычным при программировании на Паскале.
В настоящее время несколько операционных систем поддерживает Паскаль:
  • PC-DOS или MS-DOS;
  • СР/М;
  • СР/М-86.
Все операционные системы предусматривают возможность применения в программах таких функций, как открытие дисковых файлов, ввод символов с консоли и вывод символов на консоль, выделение памяти для выполнения программы. Способ применения этих функций зависит от операционной системы, но во всех случаях используется таблица переходов. В такой операционной системе как СР/М вызов системной функции осуществляется инструкцией CALL с передачей управления в определенный участок памяти, когда регистр содержит требуемый код функции. В операционной системе PC-DOS применяется программное прерывание. В обоих случаях для связи системной функции с вашей программой используется таблица переходов. На рис.3 показано расположение операционной системы и таблицы переходов в памяти.
Рис.3. Расположение в памяти операционной системы и таблицы переходов

Доступ к системным ресурсам в операционной системе PC-DOS

В операционной системе PC-DOS доступ к системным функциям осуществляется посредством программных прерываний. Каждое прерывание позволяет сделать обращение к функциям определенной категории. Тип функции определяется значением регистра АН. Дополнительная информация при необходимости передается через регистры AL, BX, CX и DX. Операционная система PC-DOS состоит из базовой системы ввода-вывода и ДОС /дисковой операционной системой/. Базовая система ввода-вывода обеспечивает процедуры ввода-вывода самого низкого уровня, которые используются в ДОС для реализации процедур ввода-вывода более высокого уровня. Возможности этих двух систем перекрываются, однако в основном доступ к ним осуществляется одинаково. Ниже дается список таких прерываний:
          Прерывание               Функция
              5        Утилита вывода экрана
              10       Ввод-вывод на дисплей
              11       Список оборудования
              12       Размер памяти
              13       Ввод-вывод на диск
              14       Ввод-вывод на последовательный порт
              15       Управление кассетой
              16       Ввод-вывод с помощью клавиатуры
              17       Ввод-вывод на печать
              18       Вызов Бейсика, расположенного в ПЗУ
              19       Выполнить начальную загрузку
              21       Вызов процедуры ДОС высокого уровня
              IA       Время и дата
Полный список прерываний и их подробное описание можно найти в техническом справочном руководстве фирмы ИБМ.
Каждое из этих прерываний предоставляет ряд возможностей, которые зависят от значения регистра АН. В табл.1 дается неполный список возможностей для каждого прерывания. К функциям, которые приводятся в табл.1 можно обращаться двумя способами. Во-первых, посредством предусмотренной в Паскале встроенной функции MsDos /для операционной системы PC-DOS/. Во-вторых, через интерфейс с ассемблера.

Применение процедуры Ms Dos

Процедура MsDos осуществляет прерывание 2In для доступа к одной из функций операционной системы высокого уровня. Обращение к этой процедуре имеет следующий общий вид: MsDos (регистры); где "регистры" представляет собой запись типа "registrs", которая определяется в блоке ДОС. Регистровый тип определяется следующим образом:
regisrers = record
  Case integer of
  0: (AX, BX, CX, DX, BP, SI, DI, DS, ES, FLAGS: word);
  1: (AL, AH,BL, BH, CL, CH, DL, DH: byte);
end;
Такое определение позволяет вам смешивать значения типа "байт" и "слово". В каждой конкретной ситуации вы должны решить, какой тип подходит лучше.
Приводимые ниже в этой главе примеры отчасти дублируют стандартные процедуры, которые уже реализованы в Паскале. Такой выбор сделан по трем причинам. Во-первых, Паскаль имеет почти все, что требуется в большинстве случаях. Во-вторых, требуется, возможно, полнее проиллюстрировать принципы построения интерфейса, чтобы можно было их применить в конкретных ситуациях. В-третьих, приводимые примеры в некоторой мере проясняют способ реализации процедур и функций в Паскале.
Ниже приводится простой пример. Эта функция определяет, было ли нажатие клавиши. Она аналогична функции "keyressed", встроенной в язык Паскаль. Результат этой функции "KbHrt" будет "истина", если нажата некоторая клавиша, или "ложь" в противном случае. Она использует прерывание 21n с шестнадцатеричным номером $B. Следует помнить, что перед шестнадцатеричным числом должен стоять валютный знак, который для компилятора является указателем шестнадцатеричного числа. Сама программа будет выводить на экран точки до тех пор, пока не будет нажата какая-нибудь клавиша:
{ демонстрация процедуры  MsDos }
program kb;

uses Dos;

function KbHit:boolean; { функция  специфична  для DOS }
var
  regs: registers;
begin
  regs.AY:=SB;
  MsDos(regs);
  if regs.AL=0 then KbHit:=FALSE
  else KbHit:=TRUE;
end;

begin
  repeat
    Write('.');
  until KbHit;
end.
Следует отметить, что в этом вызове нет необходимости задавать значения остальным регистрам, поскольку здесь требуется функция с единственным номером $B. В общем случае если какой-то регистр не используется при вызове, то его значение может не устанавливаться.
Таблица 1
Системные подпрограммы, вызываемые посредством прерываний
         ----------------------------------------------------------------
         Регистр АН                     Функция
         ----------------------------------------------------------------
             Функции ввода-вывода на дисплей - прерывание 10h

             0         Установка режима экрана
                        Если AL=0: 40х25 черно-белый;
                                1: 40х25 цветной;
                                2: 80х25 черно-белый;
                                3: 80х25 цветной;
                                4: 320х200 цветной графический;
                                5: 320х200 черно-белый графический;
                                6: 340х200 черно-белый графический
             1         Установка строк курсора
                        Биты 0-4 СН содержат начало строки,
                        биты 5-7 нулевые;
                        биты 0-4 CL содержат конец строки,
                        биты 5-7 нулевые
             2         Установка позиции курсора
                        DH: строка,
                        DL: столбец,
                        ВН: номер страницы экрана
             3         Читать позицию курсора
                        ВН: номер страницы экрана
                       Результат:
                        DH: строка,
                        DL: столбец,
                        СХ: режим
             4         Читать позицию светового пера
                       Результат:
                        если АН=0, то световое перо не инициировано;
                        если АН=1, то световое перо инициировано;
                        DH: строка,
                        DL: столбец,
                        СН: строка растра (0-199)
                        ВХ: столбец элемента изображения (0-319 или
                                                          0-639)
             5         Установка активной страницы экрана
                        AL может принимать значение от 0 до 7

             Функции ввода-вывода на дисплей - прерывание 10h

             6         Просмотр страницы вверх
                        AL: число сдвигаемых строк (от нуля до всех)
                        СН: строка верхнего левого угла,
                        CL: столбец верхнего левого угла,
                        DH: строка нижнего правого угла,
                        DL: столбец нижнего правого угла,
                        ВН: атрибуты пустой строки
             7         Просмотр страницы вниз
                        см. предыдущую функцию
             8         Чтение символа в позиции курсора
                        ВН: страница экрана,
                       Результат:
                        AL: считанный символ,
                        АН: атрибут
             9         Записать символ и атрибут в позицию курсора
                        ВН: страница экрана,
                        BL: атрибут,
                        СХ: число символов записи,
                        AL: символ
            10         Записать символ в текущей позиции курсора
                        ВН: страница курсора,
                        СХ: число символов записи,
                        AL: символ
            11          Установить палитру цвета
                         ВН: номер палитры,
                         BL: цвет
            12          Записать точку
                         DX: номер строки,
                         СХ: номер столбца,
                         AL: цвет
            13          Читать точку
                         DX: номер строки,
                         СХ: номер столбца
                        Результат:
                         AL: считанная точка
            14          Записать символ на экран и продвинуть курсор
                         AL: символ,
                         BL: цвет,
                         ВН: страница экрана
            15          Читать состояние экрана
                        Результат:
                         AL: текущий режим,
                         АН: число столбцов на экране,
                         ВН: текущая активная страница экрана

             Список оборудования - прерывание 11h

                        Читать список оборудования
                        Результат:
                         АХ: список установленного оборудования:
                          бит 0: имеется одна из дискет,
                          бит 1: не используется,
                          бит 2,3: ЗУ системной платы, 11=64К,
                          бит 4,5: начальный режим экрана:
                           10: 80 столбцов, цветной,
                           11: монохромный,
                           01: 40 столбцов, цветной,
                          бит 6,7: число дисковых накопителей, 0=1
                          бит 8: установка микросхемы прямого доступа в
                                 память, 0 - установлена
                          бит 9,10,11: число портов интерфейса RS-232
                          бит 12: 1 - установлен игровой адаптер,
                          бит 13: 1 - последовательное печатающее
                                      устройство /только типа PCir/
                          бит 14,15: число печатающих устройств

             Размер памяти - прерывание 12h

                         Результат представляет собой число килобайт
                         оперативной памяти, имеющейся в системе
                         Результат:
                          АХ: число килобайт ОЗУ

             Функции ввода-вывода на диск - прерывание 13h

             0           Сброс дисковой системы
             1           Чтение состояния диска
                         Результат:
                          AL: состояние/см. техническое справочное
                              руководство фирмы ИБМ/
             2           Чтение секторов в память
                          DL: номер драйвера,
                          DH: номер головки,
                          СН: номер дорожки,
                          CL: номер сектора,
                          AL: число считываемых секторов,
                          ES:BX: адрес буфера
                         Результат:
                          AL: число считанных секторов,
                          АН: нуль при успешном чтении, в противном
                              случае выдается состояние
             3           Запись секторов на диск
                          /как для операции чтения/
             4           Проверить
                          /как для операции чтения/
             5           Формат дорожки
                          DL: номер драйвера,
                          DH: номер головки,
                          СН: номер дорожки,
                          EL:BX: информация сектора

             Функции ввода-вывода посредством клавиатуры - прерывание 16h

             0           Чтение кода сканирования
                         Результат:
                          АН: код сканирования,
                          AL: код символа
             1           Получить состояние буфера
                         Результат:
                          ZE: 1 при пустом буфере,
                              0 при наличии символов и следующим
                              символом в регистре АХ
             2           Получить состояние клавиатуры
                         (см. техническое справочное руководство
                          фирмы IBM)

             Функции ввода-вывода на печатающее устройство - прерывание
                                  17h

             0           Печатать символ
                          AL: символ,
                          DX: номер печатающего устройства
                         Результат:
                          АН: состояние
             1           Инициализировать печатающее устройство
                          DX: номер печатающего устройства
                         Результат:
                          АН: состояние
             2           Читать состояние
                          DX: номер печатающего устройства
                         Результат:
                          АН: состояние

             Функции ДОС высокого уровня - прерывание 21h (неполный
                               список)

             1           Чтение символа с клавиатуры
                         Результат:
                          AL: символ
             2           Вывод символа на экран
                          DL: символ
             3           Чтение символа с асинхронного порта
                         Результат:
                          AL: символ
             4           Запись символа по асинхронному порту
                          DL: символ
             5           Выдать символ на устройство из списка
                          DL: символ
             7           Чтение символа с клавиатуры без вывода на экран
                         Результат:
                          AL: символ
             В           Проверить состояние клавиатуры
                         Результат:
                          AL: OFFH при нажатии клавиши; 0 в противном
                              случае
             D           Сбросить диск
             E           Установить стандартный драйвер
                          DL: номер драйвера /0-А, 1-В,.../
            11           Поиск имени файла
            /4Е под 2.х/  DX: адрес блока FCB

             Функции ввода-вывода на экран - прерывание 10h

                         Результат:
                          AL: 0, если найден, FFh, если не найден
            12           Найти следующее имя файла
            /4F под 2.х/  /как в предыдущем случае/
            1А           Установить адрес передачи диска
                          DX: адрес передачи диска
            2А           Получить дату системы
                         Результат:
                          СХ: год /1980-2099/,


                          DX: месяц /1-12/,
                          DL: день /1-31/
            2В           Установить системную дату
                          СХ: год /1980-2099/,
                          DH: месяц /1-12/,
                          DL: день /1-31/
            2С           Получить системное время
                         Результат:
                          СН: часы /0-23/,
                          CL: минуты /0-59/,
                          DH: секунды /0-59/,
                          DL: сотые секунды /0-99/
            2D           Установить системное время
                          СН: часы /0-23/,
                          CL: минуты /0-59/,
                          DH: секунды /0-59/,
                          DL: сотые секунды /0-99/
         ---------------------------------------------------------------

Использование функций базовой системы ввода-вывода и ДОС

В некоторых случаях желательно иметь возможность использовать внешние программы, написанные на ассемблере, для доступа к системным функциям. Рассмотрим несколько примеров. Пусть во время работы программы требуется изменить режим экрана. Ниже приводятся семь режимов экрана при использовании цветного графического адаптера в операционной среде PC-DOS:
         Режим Тип            Размеры            Адаптеры

         0     Текст, черно-  40 х 25            CGA, EGA
               белый

         1     Текст, 16      40 х 25            CGA, EGA
               цветов

         2     Текст, черно-  80 х 25            CGA, EGA
               белый

         3     Текст, 16      80 х 25            CGA, EGA
               цветов

         4     Графика, 4     320 х 200          CGA, EGA
               цвета

         5     Графика, 4     320 х 200          CGA, EGA
               черно-белых
               тона

         6     Графика        640 х 200          CGA, EGA
               черно-белая

         7     Текст, черно-  80 х 25            Монохроматический
               белый

         8     Графика, 16    160 х 200          PCjr
               цветов

         9     Графика, 16    320 х 200          PCjr
               цветов

         10    Графика        320 х 200          PCjr, EGA
               PCjr - 4 цвета
               EGA - 16 цветов

         13    Графика, 16    320 х 200          EGA
               цветов

         14    Графика, 16    640 х 200          EGA
               цветов
         15    Графика, 4     640 х 350          EGA
               цвета
Приводимая ниже процедура "mode" выполняет обращение к функции 1 базовой системы ввода-вывода (функция установки режима) для перевода экрана в графический режим 640х200, выдачи на экран сообщения-приветствия "HI" и ожидания нажатия пользователем клавиши "RETURN". При нажатии указанной клавиши будет сделан возврат в режим цветного экрана с размерами 80х25 (приводимая программа будет работать только в операционной системе PC-DOS при наличии цветного графического адаптера):
program modetest;

{SL MODE}

procedure Mode(ModeSet:integer):external;

begin
  Mode(6);
  WriteLn('hi'); read;
  Mode(3);
end.
Ниже приводится внешняя функция "mode", написанная на ассемблере:
; Эта процедура делает установку режима экрана, используя
; 1 целочисленный параметр.
code    segment 'code'
  assume cs:code
public  mode
mode    proc near

; сохранить стек
  push bp
  mov bp,sp
; получить режим
  mov ax,[bp]+4
  mov ah,0    ; команда для переключения режима
  int 010h    ; вызов базовой системы ввода-вывода
; восстановление и выход
  pop bp
  ret 2
mode    endp
code    ends
end
Ниже приводится функция, которая очищает экран посредством процедуры "clr":
program clr_screen;

{SL CLR}

procedure Clr; external;

begin
  WriteLn('нажмите ВВОД для чистки экрана');
  ReadLn;
  Clr;
  WriteLn('экран полностью очищен');
end.
Ниже приводится внешняя процедура "clr", написанная на ассемблере.
; прерывание с номером 6
cseq segment 'code'
assume cs:cseq

public clr

clr proc near
; сохранить используемые регистры
  push ax
  push bx
  push cx
  push dx
  mov cx, 0  ; начать сначала координат
  mov dh, 24 ; конец в строке 24
  mov dl, 79 ; столбец 79
  mov ah, 0  ; установить режим просмотра
  mov al, 0  ; очистить экран
  mov bh, 7
  int 10h

; восстановление и выход
  pop ax
  pop bx
  pop cx
  pop dx
clr endp
cseq ends
  enu
Другим примером связи с базовой системой ввода-вывода посредством применения ассемблера является функция "xy", которая устанавливает курсор в координаты Х и Y. Эта функция аналогична процедуре "GotoXY", предусмотренной в языке Паскаль. В ПЭВМ фирмы IBM левый верхний угол экрана имеет координаты (0,0).
program gotoxy;

{$l XY}

var
  t: integer;

procedure xy(x, y): integer); external ;

begin
  for t := 0 to 24 do begin
    xy(t,t);
    write(t);
  end;
end.
Ниже приводится внешняя процедура "xy", написанная на ассемблере:
; эта функция  устанавливает курсор в координаты (Х,Y)
сode       cseq segment 'code'
  assume cs:cseq

public     xy
xy         proc near

; сохранение указателя стека
  puch bp
  mov bp,sp

; получить первый параметр
  mov dh,[up]+4  ; получить Х
  mov dl,[up]+8  ; получить Y
  mov ah,2       ; указатель точки перехода для
                        ; BIOS
  mov bh,0       ; номер страницы
  int 10h

  pop bp
  ret 4

  xy         endp
  code       ends
end

Использование кодов клавиш сканирования

При работе на ПЭВМ фирмы ИБМ наиболее сложно обрабатывают программные коды функциональных клавиш и клавиш со стрелками /также клавиш INS, DEL, PGOP, PGDN, END и HOME/. При нажатии такой клавиши генерируется не восьмибитовый /однобайтовый/ код, как делается при нажатии других клавиш. При нажатии такой клавиши в действительности генерируется шестнадцатибитовый код, называемый кодом сканирования. Код сканирования состоит из младшего байта, который при нажатии обычной клавиши будет содержать код ASCII для этой клавиши, и старшего байта, который содержит позицию клавиши на клавиатуре.
Для большинства клавиш операционная система преобразует код сканирования в соответствующий восьмибитовый код ASCII. Но для функциональных клавиш и клавиш со стрелками это преобразование не делается, поскольку код символа для специальной клавиши будет иметь нулевое значение. Это означает, что для определения нажатой клавиши необходимо воспользоваться кодом позиции. Программу чтения символа с клавиатуры посредством обращения к функции ДОС с номером I нельзя использовать для чтения специальных клавиш. Это, очевидно, приводит к трудностям, когда в программе необходимо использовать специальные клавиши. В Паскале версии 4 предусматривается функция "Readkey", предназначенная для чтения и символов и кодов. Однако в приводимой ниже процедуре используется другой подход. Здесь делается прерывание $16 для получения полного шестнадцатибитового кода клавиши.
; эта процедура выдает шестнадцатибитовый код,  младший байт
; которого содержит либо символ ASCII,  либо нулевое  значе-
; ние. В последнем случае старший байт содержит код сканиро-
; вания

code    segment 'code'
  assume cs:code

public  scan
scan    proc near

; сохранить указатель  стека
  push bp
  mov bp,sp
; получить первый параметр
  mov ah,0
  int 16h
  mov [bx+2],ax; возвращаемое значение
; восстановление и выход
  pop bp
  ret 2
scan   endp
code   endx
  end
После вызова код сканирования и код символа уже будут находиться в регистре АХ, который следует использовать для передачи информации в вызывающую процедуру. После прерывания 16n с нулевым функциональным номером код позиции будет находиться в регистре АН, а код символа будет находиться в регистре AL. Процедура "scan" написана с учетом того, что при нажатии специальной клавиши код символа имеет нулевое значение.
Если код символа имеет нулевое значение, то декодируется код позиции для определения, какая клавиша была нужна. Для обработки всякого ввода с клавиатуры посредством указанной функции решения следует принимать на основе содержимого регистров АН и AL. Один из таких способов иллюстрируется ниже в короткой программе:
program special_keys;

{SL SCAN}

var
  t: integer;

function Scan:integer; external;

begin
  repeat
    t: = Scan;
    if lo(t)=0 then WriteLn('scan code is', hi(t))
    else WriteLn(chr(lo(t)));
  until  chr(lo(t))='q';
end.
Для доступа к обеим половинам шестнадцатиразрядного значения, полученного процедурой "scan", можно воспользоваться предусмотренными в Паскале стандартными функциями "Ht" и "Lo". Кроме того, для преобразования целого числа в символ потребуется функция "Chr".
Для декодирования кода сканирования вы можете воспользоваться техническим справочным руководством фирмы ИБМ. Другой, более интересный способ, заключается в написании короткой программы, единственным назначением которой является лишь экспериментальная выдача кодов нажатых клавиш. Для начала приведем коды сканирования для клавиш со стрелками: Левая стрелка - 75, Правая стрелка - 77, Стрелка вверх - 72, Стрелка вниз - 80.
Для полного совмещения специальных клавиш с обычными клавишами необходимо написать специальные функции ввода данных и использовать их вместо обычных функций "read" и "readln". К сожалению, этот путь является единственным. Однако наградой будет возможность работать в вашей программе с полным набором клавиш ПЭВМ фирмы ИБМ.
    Содержание                                                      <<Назад         Далее>>
    Учебник по языку Pascal          Лабораторные работы по программированию          Справочник

Реклама