Содержание     Внешние процедуры (функции) Машинные коды Функции ОС Процедуры обработки прерываний Запуск внешних программ  
 

 

Другие возможности Турбо-Паскаля

 

1. Внешние процедуры (функции)

С помощью внешних процедур (функций) можно вызывать из Турбо-Паскалевой программы процедуры или функции, написанные на языке ассемблера и других языках. Машинно-ориентированный язык ассемблера предоставляет квалифицированному программисту богатейшие возможности использования всех особенностей архитектуры ПЭВМ. Ассемблерные программы выполняются значительно быстрее и занимают меньший объем памяти, чем Турбо-Паскалевые программы, однако низкий уровень языка существенно снижает производительность труда программиста и резко усложняет отладку программ, Как правило, на языке ассемблера пишутся сравнительно небольшие фрагменты программ, учитывающие особенности архитектуры ПЭВМ, которые невозможно использовать в Турбо- Паскалевой программе. Внешняя процедура (функция) в Турбо-Паскалевой программе объявляется заголовком, за которым следует зарезервированное слово ЕХТЕRNАL, например:

Function LoCase(ch: char): char; external;
Procedure Swapping(Var a, b; N: word); externa1;

Как видим, тело внешней процедуры (функции) отсутствует его заменяет кодовое слово ЕХТЕRNАL. Для подключения ассемблерной программы необходимо предварительно ее откомпилировать и получить OBJ - файл с перемещаемым кодом программы. Непосредственно перед описанием внешней процедуры (функции) в основной программе вставляется директива компилятора {$L < имя файла >}, где < имя файла > - имя OBJ - файла. Диск и каталог, в котором следует искать этот файл, если он не обнаружен в текущем каталоге, указывается опцией "OPTIONS/DIRECTORIES/OBJECT DIRECTORIES" ИПО.

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

Перед передачей управления внешней процедуре (функции) Турбо- Паскалевая программа заталкивает параметры обращения в программный стек в том порядке, как они перечислены в заголовке процедуры (функции). Ассемблерная процедура должна сохранить регистры BP, SP, SS и DS центрального процессора в самом начале своей работы и восстановить содержимое этих регистров перед возвратом управления в Турбо-Паскалевую программу. Остальные регистры можно не сохранять и соответственно не восстанавливать.

Параметры могут передаваться по ссылке или по значению. Если параметр передается по ссылке, в стек заталкивается указатель, содержащий абсолютный адрес параметра; если по значению - в стек заталкивается сам параметр, точнее - его значение. Параметры, объявленные в заголовке с предшествующим словом VAR, всегда передаются по ссылке. Параметры- значения могут передаваться по ссылке или по значению, в зависимости от длины внутреннего представления соответствующею параметра. В общем случае используется следующее правило: если длина внутреннего представления параметра-значения составляет 1, 2 или 4 байта, само значение параметра заталкивается в стек. Точно так же через стек передаются и все вещественные данные длиной в 4, 6, 8 и 10 байт (в версии 4.0 эти данные передаются через стек сопроцессора 8087/80287, в версии 5.0 - через стек центрального процессора 8086/80286). Во всех остальных случаях, если длина внутреннего представления больше 4 байт, соответствующий параметр передается по ссылке.

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

• длиной в 1 байт - в регистре АL;
• длиной в 2 байта - в регистре АХ;
• длиной в 4 байта - в регистрах DХ:АХ (старшее слово в DХ);
• тип REAL (б байт) - в регистрах DХ:ВХ:АХ;
• типы SINGLE, DOUBLE, EXTENDED и COMP - через стек сопроцессора 8087/80287;
• указатели - в регистрах DХ:АХ (сегмент в DХ);
• строки возвращаются по ссылке: адрес начала строки помещается в DХ:АХ (сегмент в DХ).

Все ассемблерные процедуры должны размещаться в сегменте с именем СОDЕ, а имена процедур и функций должны быть объявлены директивой PUBLIC. Локальные переменные необходимо размещать в сегменте с именем DАТА. Все имена, объявленные в интерфейсной части модулей Турбо-Паскалевой программы, становятся доступны ассемблерной процедуре (функции) после их объявления директивой ЕХТRN.

2. Использование встроенных машинных кодов

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

В качестве элемента данных может использоваться целая константа, идентификатор (переменной, константы или функции) или ссылка на счетчик адреса "*". Каждый элемент данных вызывает генерацию 1 или 2 байт кода программы. Значение этого кода получается сложением или вычитанием элементов данных в соответствии с разделяющим их знаком. Значением идентификатора переменной, константы, функции является адрес соответствующего объекта, значением ссылки на счетчик адреса является тот адрес, по которому будет размещаться следующий байт кода.

Элемент кода будет генерировать 1 байт кода, если этот элемент состоит только из целых констант и значение результата не превышает 1 байта, т.е. находится в диапазоне от 0 до 255. Если значение превышает 255 или элемент кода содержит ссылку на счетчик адреса, генерируются 2 байта. Знаки ">" и "<" могут использоваться для отмены автоматического выбора размера генерируемого кода. Если элемент кода начинается со знака "<", в код заносится только один младший байт, даже если само значение занимает 2 байта. Наоборот, если элемент начинается со знака ">", в код заносится 2 байта (старший байт может оказаться нулевым).

Значением идентификатора, как уже говорилось, является смещение соответствующего объекта. Если переменная. глобальная, смещение задается относительно сегмента данных, хранящегося в регистре DS, если переменная локальная, - относительно сегмента стека (регистр SР). Базовым сегментом типизированной константы является сегмент кода (регистр СS).

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

Function InPort(Port: Word): Word;
Var
  pp: Word;
  cc: Char;
Begin
  pp:= port;
  in1ine( {assembler code: }
          $8b/$96/pp/ { MOV DX,pp[bp] }
          $EC/ { IN AX,DX }
          $88/$86/cc { MOV cc[bp],AX }
          );
  InPort:= ord(cc);
End;

Procedure OutPort(Port, Bt: Word);
Var
  pp: Word;
  cc: Char;
Begin
  pp:=port;
  cc:=chr(Bt);
  in1ine(
           $8a/$86/cc/ { MOV AX,cc[bp] }
           $8b/$96/pp/ { MOV DX,pp[bp] }
           $EE { OUT DX,AX }
           );
END;

Операторы INLINE могут произвольным образом смешиваться с другими операторами Турбо-Паскаля, однако при выходе из процедуры (функции) содержимое регистров BP, SP, DS и SS должно быть таким же, как и при входе в нее.

3. Обращение к функциям операционной системы

Турбо-Паскаль предоставляет программисту практически неограниченные возможности использования любых функций стандартной операционной системы PC DOS (MS DOS). При внимательном анализе материала этой книги Вы, очевидно, заметите, что большую часть составляет описание многочисленных библиотечных процедур и функций. Собственно язык Паскаль весьма прост и лаконичен, что, по мнению многих специалистов, и послужило одной из причин его широкого распространения. Значительная же часть библиотечных процедур и функций является по существу своеобразным интерфейсом между языковыми средствами Турбо-Паскаля и функциями операционной системы. Разумеется, можно только приветствовать усилия разработчиков Турбо-Паскаля по созданию мощных библиотек TURBO.TPL и GRAPH.TPU, однако ясно что таким способом невозможно запрограммировать все допустимые обращения к средствам ДОС. Вот почему в Турбо-Паскаль включены две процедуры, с помощью которых программист может сам сформировать вызов той или иной функции ДОС.

Следует учесть, что единственным механизмом обращения к функциям операционной системы является инициация программного прерывания. Прерывание - это особое состояние вычислительного процесса. В момент прерывания нарушается нормальный порядок выполнения команд программы, и управление передается специальной процедуре, которая входит в состав ДОС и называется процедурой обработки прерывания. Каждое прерывание характеризуется в рамках ДОС порядковым номером и связано со своей процедурой обработки. В архитектуре центрального процессора ПЭВМ предусмотрены прерывания двух типов: аппаратные и программные. Аппаратные прерывания создаются схемами контроля и управления ПЭВМ и сигнализируют операционной системе о переходе какого-либо устройства в новое состояние или о возникновении неисправности. Программные прерывания инициируются при выполнении одной из двух специальных команд (INТ или INТО) и служат для обращения к средствам ДОС.

Всего в ДОС имеется около 40 программных прерываний, каждое из которых может активизировать одну или несколько функций ДОС. Одно из прерываний - с номером 33 ($21 - шестнадцатиричное число) обеспечивает доступ к 85 функциям, а всего в ДОС имеется более 200 разнообразных функций.

Описываемые ниже процедуры входят в состав библиотечного модуля DOS и становятся доступными после объявления USES DOS. При возникновении программного прерывания в большинстве случаев необходимо передать процедуре обработки прерывания некоторые параметры, в которых конкретизируется запрос нужной функции. Эти параметры, а также выходная информация передаются от программы к процедуре и обратно через регистры центрального процессора. В модуле DOS для этих целей определен специальный тип:

Type
Registers = record
      Case integer of
      0:(AX, BX, CX, 0X, BP, SI, DI, DS, ES, Flags: word);
      1:(AL, AH, BL, BH, CL, CH, DL, DH: byte)
      end;

Как видим, этот тип имитирует регистры центрального процессора и дает возможность обращаться к ним как к 16-битным или 8-битным регистрам.

С помощью процедуры INTR инициируется программное прерывание с требуемым номером; формат обращения: INTR(< N >, < регистры >)
Здесь < N > - выражение типа ВYТЕ, означающее номер прерывания;
< регистры > - переменная типа REGISTERS, в которой процедуре обработки прерывания передается содержимое регистров и возвращается выходная информация.

Так, прерывание с номером 18 ($12) возвращает в регистре AX объем оперативной памяти ПЭВМ. Пример программы выводящей на экран сообщение об этом объеме:

Program IntrDem;
Uses DOS;
Var
  r: registers;
Begin
  Intr($12, r);
  writeln('Объем памяти = ', r.АХ, ' Кбайт')
END.

Процедура MSDOS.
Инициирует прерывание с номером 33 ($21);
формат обращения: MSDOS(< регистры >);
Здесь < регистры > - переменная типа REGISTERS, содержащая значения регистров на входе и выходе процедуры обработки прерывания.

Программное прерывание с номером 33 ($21) стоит особняком: как уже говорилось, оно дает доступ к большому количеству функций ДОС (этим прерыванием вызывается 85 функций). Рассматриваемая процедура полностью эквивалентна вызову процедуры INTR c номером прерывания 33. Программа примера выведет на экран версию операционной системы:

Program MsDosDemo;
Uses DOS;
Var
  R: registers;
Begin
  r.AH: = $30;
  MsDos(r);
  write1n ('Версия операционной системы: г.АL, '.', г.АН)
END.

4. Поддержка процедур обработки прерываний

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

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

Турбо-Паскалевая процедура обработки прерывания должна начинаться зарезервированным словом INTERRUPT (англ. прерывание), например:
Procedure IntProc(Flags, CS, IP, AX, BX, CX, DX, SI, DF, 0S, ES, BP: word); inerrupt;
begin
.......
end;

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

При входе в процедуру:
               push ax
               push bx
               push cx
               push dx
               push si
               push di
               push ds
               push es
               push bp
               mov bp,si
               sub sp,LocalSize
               mov ax,SEG DATA
               mov ds,ax

При выходе из процедуры:
               mov sp,bp
               pop bp
               pop es
               pop ds
               pop di
               pop si
               pop dx
               pop cx
               pop bx
               pop ax
               irep

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

Для связи с любыми процедурами прерываний, а следовательно и с процедурами, написанными программистом, используются векторы прерываний - четырехбайтные абсолютные адреса точек входа в эти процедуры. Векторы прерываний располагаются в младших адресах оперативной памяти, начиная с нулевого адреса: прерывание номер 0 - по адресу 0, номер 1 - по адресу 1 * 4 = 4, номер N - по адресу N*4.

С помощью следующих двух процедур программист может прочитать содержимое любого вектора или установить его новое значение.

Процедура GETINTVECT. Возвращает вектор прерывания с указанным номером;
формат обращения GETINTVECT(< N >, < вектор >> )
Здесь < N > - выражение типа ВYТЕ, содержащее номер прерываиия;
< вектор > -переменная типа РOINTER, в которой возвращается адрес точки входа в процедуру обработки прерывания.

Пример: программа выводит на экран содержимое всех ненулевых векторов прерываний.

Uses DOS;
var
  i: byte;
  p: pointer;
Begin
  for i:= 0 to 255 do
  Begin
    GetIntVec(i, p);
    if (Seg(p^) <> 0) or (Ofs(p^) <> 0) then
      write1n('N = ', i:3, 'Seg = ', Seg(p^):5, 'Ofs =', Ofs(p^):5);
  End;
End.

Процедура SETINTVEC. Устанавливает новое значение вектора прерывания;
формат обращения SETINTVEC(< N >>, < адрес >);
Здесь < адрес > - выражение типа РOINTER , в котором задается адрес точки входа в процедуру обработки прерывания.

При нормальном завершении Турбо-Паскалевой программы она выгружается из памяти, что делает невозможным разработку резидентных в памяти процедур обработки прерываний. Вы можете прекратить работу программы и оставить ее в памяти, если воспользуетесь процедурой КЕЕР.

Процедура КЕЕР. Завершает работу программы и оставляет ее в памяти;
формат обращения КЕЕР(< код >)
< код > - выражение типа WORD, в котором содержится код завершения программы.
Код завершения представляет собой фактически единственный механизм передачи сообщений от запущенной программы к программе, которая ее запустила. Он может быть проанализирован в вызывающей программе с помощью функции DOSEXITCODE.

Функция DOSEXITCODЕ. Возвращает значение типа WORD - код завершения подчиненной программы.

5. Запуск внешних программ

Из Турбо-Паскалевой программы можно запустить любую другую готовую к работе программу. Для этого используется процедура ЕХЕС из библиотечного модуля DOS;
формат обращения ЕХЕС(< имя >, < параметры >);
Здесь < имя > - выражение типа STRING, в котором задается имя файла с вызываемой программой;
< параметры > - выражение типа STRING, в котором передаются параметры вызова.
Имени запускаемой программы должен предшествовать путь к файлу. Параметры передаются запускаемой программе в виде текстовой строки и могут быть проанализированы ею с помощью двух следующих функций.

Функция PARAMCOUNT. Возвращает общее количество параметров вызова программы (значение типа WORD). Обращение PARAMCOUNT
Параметры вызова обычно следуют в командной строке ДОС сразу за именем вызываемой программы и отделяются от этого имени и друг от друга пробелами, например:
С:\ТURBO MyProg.Рas
C:\SIAM A:\Sistem.Sia
Здесь MyProg.Pas и A:\Sistem.Sia - параметры, передаваемые программам TURBO и SIAM.
При вызове программы непосредственно из среды Турбо-Паскаля ей можно передать параметры с помощью опции OPTIONS/PARAMETERS

Функция PARAMSTR Возвращает значение типа STRING, соответствующее нужному параметру вызова, формат обращения PARAMSTR(< N >)
Здесь < N > - выражение типа WORD, задающее порядковый номер параметра.

Использование процедуры ЕХЕС имеет ряд специфических особенностей. Прежде всего необходимо отметить, что сама Турбо- Паскалевая вызывающая программа остается резидентной в памяти, поэтому она не должна занимать всю оперативную память. Объем выделяемой программе памяти регулируется опцией OPTIONS/COMPILER/MEMORY SIZE или директивой компилятора {$M . . }. По умолчанию параметры этой опции таковы (0 и 655360 байт соответственно), что Турбо-Паскалевая программа занимает весь доступный объем памяти и вызываемая программа не будет загружена. Полезно включить в текст вызывающей программы директиву компилятора, в которой изменяются принятые по умолчанию размеры памяти. Это можно сделать, например, так:
{$М 2048, 0, 0}
Такая директива ограничивает используемую программой область стека объемом 2 Кбайт и исключает возможность использования в ней динамической памяти. Разумеется, Вы можете установить и другие значения параметров в этой директиве.

Далее, специфические особенности исполнения Турбо-Паскалевых программ требуют изменения стандартных значений некоторых векторов прерываний. К ним относятся векторы со следующими шестнадцатеричными номерами: $00, $02, $18, $23, $24, $34, $35, $36, $37, $38, $39, $3А, $3В, $3С, $3D, $3Е, $3F, $75.

Начальные значения этих векторов сохраняются в восемнадцати переменных с именами SAVEINTXX из библиотечного модуля SYSTEM, где ХХ -шестнадцатеричный номер прерывания. Поэтому непосредственно перед запуском внешней программы и сразу после возврата из нее рекомендуется вызвать библиотечную процедуру без параметров SWAPVECTORS, которая обменивает содержимое векторов прерывания и перечисленных переменных.

Пример: программа воспринимает с клавиатуры любую команду ДОС, затем вызывает командный процессор СОММАND.СОМ операционной системы и передает ему эту команду.
Program ExecDemo;
 {$M 1024, 0, 0}
Uses DOS;
Var
 st: string [79];
Begin
 Write(‘Введите команду DOS:’);
 Readln(st);
 if st <> '' then
 Begin
  st:= 'C:\'+st,;
  SwapVectors;
  Exec(GetEnv('COMSPEC'), st);
  SwapVectors;
 End;
End.

Обратите внимание: для указания файла СОММАND.СОМ и пути к нему использовано обращение к библиотечной функции GETENV, с помощью которой можно получить параметры настройки операционной системы. В частности, параметр СОМSPЕС определяет спецификацию файла, содержащего командный процессор.

С помощью следующей несложной программы можно вывести на экран ПЭВМ список всех параметров настройки ДОС.

Program EnvParDemo;
Uses DOS;
Var
 i: integer;
Begin
 For i:= 0 To EnvCount Do
  Writeln(EnvStr(i));
End.

Функция ENVCOUNT. Возвращает значение типа INTEGER, в котором содержится общее количество установленных в ДОС параметров. Обращение ENVCOUNT.

Функция ENVSTR. Возвращает значение типа STRING, содержащее имя и значение нужного параметра настройки операционной системы; формат обращения ENVSTR(< N >)

Здесь < N > - выражение типа INTEGER, в котором указывается номер параметра.
Эта функция возвращает строку вида NАМЕ = VАLUЕ, где NАМЕ - имя, а VALUE - значение соответствующего параметра настройки.

Функция GETENV возвращает значение типа STRING, в котором содержится параметр настройки ДОС; формат обращения GETENV (< имя >)

Здесь < имя > - выражение типа STRING, определяющее имя параметра.
Эта функция имеет параметр обращения NАМЕ и возвращает значение VALUE (см. функцию ENVSTR).