16.6.1. Программирование субпроцессов

16.6.1.1. Процедура SwapVectors. При запуске среды Турбо Паскаль или созданного в ней выполнимого файла первым делом происходит смена ряда системных векторов прерываний на векторы отладчика среды и системной библиотеки. Однако адреса системных векторов не теряются, а запоминаются в переменных типа Pointer с именами SaveIntNN, где NN — номер прерывания (эти переменные являются предопределенными и привносятся вместе с системной библиотекой).

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

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

Обычно эта процедура используется как «обрамляющая» для вызова Exec и в тех случаях, когда надо вернуться на время к исходным векторам, т.е. к тем, что были до запуска программы. О

- 380 -

номерах сохраняемых прерываний можно справиться в интерактивной подсказке среды программирования по модулю System.

16.6.1.2. Процедура Ехес( ExeFile, ComLine : String ). Эта процедура служит для запуска субпроцесса. Программа, в которой используются вызовы процедуры Exec, должна иметь в своем начале директиву распределения памяти {$М...}. Кроме того, рекомендуется до вызова Exec и сразу после него вставлять процедуру SwapVectors.

В процедуру передается два строковых аргумента: ExeFile — имя файла или полное имя файла (в обоих случаях обязательно указывать расширение имени) — это просто имя того файла, который нужно «запустить» из программы; ComLine — строка из аргументов, которые передаются запускаемому файлу.

Рассмотрим пример. Пусть в командной строке MS-DOS дается команда

С:> format a: /s

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

Ехес( 'format.com', 'a: /s' );

или даже

Ехес( 'c:dosformat.com', 'a: /s' );

если файл format.com лежит не в текущем каталоге, а в C:DOS.

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

Второй пример: в строке MS-DOS слияние файлов задается командой

С:> copy a.txt + b.txt c.txt

Но COPY — встроенная команда командного процессора и не является запускаемым файлом. Чтобы реализовать ее как субпроцесс, необходимо запускать командный процессор COMMAND.COM и передавать ему текст команды в виде параметров:

Ехес( 'connmand.com', '/с copy a.txt+b.txt c.txt' );

Важно не забывать включить в командную строку первым по счету ключ /c для командного процессора. Если забыть это сделать, то получится «выход в DOS», и вернуться из субпроцесса можно будет только через подачу команды EXIT с клавиатуры. Ключ /р тоже не годится для субпроцесса, поскольку заставляет выполниться файл AUTOEXEC.BAT, что вряд ли к месту при запуске субпроцесса. А

- 381 -

ключ /c выполнит команды из строки и автоматически завершит субпроцесс.

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

Ехес( GetEnv( 'COMSPEC' ), '' );

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

Ехес( GetEnv( 'COMSPEC' ), '/с format a: /s' );

Обращаем внимание, что здесь расширение '.СОМ' у команды format уже не обязательно. Подобный запуск имеет свои особенности. Первая — это незначительный перерасход памяти, и им можно пренебречь. Вторая особенность важнее: если запускается субпроцесс, то можно при помощи функции DosExitCode проанализировать, чем и как он закончился. Написав, например, в программе

Exec( 'subproc.exe', Parameters );

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

Exec( GetEnv( 'COMSPEC' ), '/с subproc '+Parameters );

скорее всего даст нормальное завершение субпроцесса, даже если subproc.exe сломает дисковод, сожжет монитор и завершится фатальной ошибкой. Просто в этом случае будет рассматриваться работа самого процессора COMMAND.COM, а не того субпроцесса, который он запускает и выполняет. А процессор редко дает сбои. Следует помнить об этом внутреннем различии, хотя внешний эффект будет неразличим (если, конечно, речь идет не о сжигании мониторов!).

О функции DosExitCode речь еще пойдет ниже. Кроме нее, можно анализировать ход выполнения субпроцесса через системную переменную модуля DOS DosError. После выполнения вызова Exec переменная DosError может содержать значения:

0 — все в порядке, нормальное выполнение;

2 — не найден файл-субпроцесс;

8 — не хватает памяти для запуска;

10 — несоответствие среды DOS;

11 — ошибка в формате команд.

- 382 -

Появление значения DosError, равного 8, говорит о том, что надо повысить значение максимального размера кучи в директиве компилятора {$М ... }.

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

16.6.1.3. Функция DosExitCode : Word. Эта функция анализирует завершение субпроцесса. В возвращаемом значении типа Word скомбинированы два значения. Старший байт содержит одно из значений, приведенных в табл. 16.8.

Таблица 16.8

Hi

Значение кода

0

Нормальное завершение и возврат управления

1

Субпроцесс был прерван нажатием Ctrl+Break (по прерыванию 23Н)

2

Субпроцесс был прерван из-за ошибки какого-либо устройства

3

Субпроцесс завершился процедурой Keep и остался резидентным

Младший байт содержит код завершения программы-субпроцесса, переданный через процедуру завершения: Halt(n) или Keep(n), где n — код окончания. Если таких команд в программе не было, то код завершения будет равен 0.

На рис. 16.17 приведен пример, объединяющий процедуры и функции организации субпроцессов.

| { $М 1512, 0, 0 ресурсы для запускающей программы }

| USES DOS, CRT;

| {Функция запускает файл ExeFile с параметрами Parameters и возвращает логическое значение True, если запуск был удачен. Коды завершения субпроцесса возвращаются в переменных ErrorLevel и ExitHiByte.}

Рис. 16.17

- 383 -

| FUNCTION Execute( ExeFile, Parameters : String;

| VAR ErrorLevel, ExitHiByte : Byte ) : Boolean;

| VAR

| Wrd : Word; { промежуточная переменная }

| BEGIN

| SwapVectors; { установка векторов DOS }

| Exec(ExeFile, Parameters); { сам запуск субпроцесса }

| SwapVectors; { возврат векторов TURBO }

| Wrd := DosExitCode; { запомним код завершения }

| ErrorLevel := Lo( Wrd ); { код выхода из процесса }

| ExitHiByte := Hi( Wrd ); { код способа выхода }

| Execute := False; { пусть сначала будет так }

| case DosError of { анализ вызова Exec }

| 0 : begin { все в порядке }

| Execute := True; { Меняем значение функции }

| Exit { и выходим из нее }

| end;

| 2 : WriteLn(#10'He найден файл ', ExeFile );

| 8 : WriteLn(#10'He хватает памяти для субпроцесса )

| else WriteLn(#10'Ошибка DOS номер ', DosError )

| end {case}

| END;

| VAR { ===== ПРИМЕР ВЫЗОВОВ ==== }

| Er, Ex : Byte;

| Ch : Char;

| BEGIN

| ClrScr; { очистка экрана }

| CheckBreak := True; '

| Repeat { вечный цикл }

| WriteLn( 'Нажмите :' );

| WriteLn{ ' ':15, '[D] - для выхода в DOS' );

| WriteLn( '':15, '[S] - для запуска субпроцесса' );

| WriteLn( '':15, '[Q] - для завершения работы' );

| repeat

| Ch := UpCase( ReadKey ) { Выборочный опрос }

| until ( Ch in [ 'D','S','Q'] ); { клавиатуры. }

| case Ch of { Действия : }

| 'D' : begin { 1.Выход в MS-DOS. }

| HighVideo;

| Write( #10'Для возврата введите EXIT...' );

| LowVideo;

| if Execute(GetEnv('COMSPEC'),' ',Er,Ex) then;

| end;

Рис. 16.17

- 384 -

| 'S' : begin { 2. Запуск файла. }

| if not Execute('outer.exe',' ', Er, Ex ) then

| Halt; { запуск неудачен }

| if Ex = 1 then { Вы нажали ^Break:}

| WriteLn(#10'Процесс прерван с консоли.' );

| end;

| 'Q' : Exit { 3. Выход из программы. }

| end; {case}

| until False { условие вечного цикла }

| END.

Рис. 16.17 (окончание)