Написание библиотек для REXX на Virtual Pascal/2

Автор: Виктор Кустов AKA VikingX

Источник: RDM/2

Все началось с того, что я решил написать сторож процессов на рексе - скриптик, который следил бы за определенными процессами, и если один из этих процессов умрет, то запускал бы его заново. Потратив минут 20, я набросал скрипт, который список процессов загонял в queue со стандартного вывода ps.exe. Но это показалось не очень хорошим решением и родилась идея написать DLL-ку с реализацией функции получения списка процессов.

 В VP/2 оказался пример, реализующий библиотеку для REXX (в examples\rexx). Там описана функция VPTouch, входными параметрами для которой являются одна или несколько масок (или имен файлов), результатом являются действия (изменение timestamp файла(ов)) и код возврата (текстовая переменная).

Что полезного можно почерпнуть из этого примера? Ну во-первых заголовок функции:

  Function VPTouch( FuncName  : PChar;

                    ArgC      : ULong;

                    Args      : pRxString;

                    QueueName : pChar;

                    Var Ret   : RxString ) : ULong; export;

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

FuncName - имя вызываемой функции.
ArgC - количество передаваемых аргументов.
Args - сами аргументы (в примере очень хорошо показано, как с ними обращаться, их может быть много ;)
QueueName - имя очереди (непонятно, как ее использовать, но по всей видимости результат выполнения функции может помещаться в очереди, и/или читаться из нее).
Ret - результат работы функции (при вызове var = RxMyFunc( a, b, ... ) он будет помещен в var).
И код возврата типа ULong.
Обратите внимание! Если вы не вернете 0 при удачном выполнении функции, то REXX ругнется на ошибку выполнения.

Как видно из примера функция возвращает только одну простую строку в качестве результата ( Var Ret : RxString ). Возвращать список процессов в одной строке неудобно, поэтому лучше использовать другой вариант. Для этого можно использовать запись в файл, очередь и еще тот замечательный способ, который использует, к примеру SysFileTree - стем. Отлично, в Watcom 10.0 как раз есть пример DLL-ки SysUtils, которая содержит эту функцию (WATCOM\SAMPLES\TOOLKT2X\REXX\REXXUTIL\). Оттуда мы и выясняем, что такая переменная создается и обрабатывается функцией RexxVariablePool (см. VP\SOURCE\RTL\OS2REXX.PAS):

  function RexxVariablePool(var Pool: ShvBlock): ApiRet; { Указатель на список SHVBLOCK-ов}

Параметром для нее служит переменная типа:

  PShvBlock = ^ShvBlock;

  ShvBlock = record

     shvnext:     PShvBlock;  { pointer to the next block    }

     shvname:     RxString;   { Pointer to the name buffer   }

     shvvalue:    RxString;   { Pointer to the value buffer  }

     shvnamelen:  ULong;      { Length of the name value     }

     shvvaluelen: ULong;      { Length of the fetch value    }

     shvcode:     Byte;       { Function code for this block }

     shvret:      Byte;       { Individual Return Code Flags }

  end;

Ее нужно соответствующим образом заполнить и вызвать RexxVariablePool. Что я и сделал в предлагаемом примере - библиотеке для работы с процессамми. В этой записи следует обратить на поле shvcode, которое может принимать значения:

  rxshv_Set     = $00;  { Set var from given value     }

  rxshv_Fetch   = $01;  { Copy value of var to buffer  }

  rxshv_DropV   = $02;  { Drop variable                }

  rxshv_SySet   = $03;  { Symbolic name Set variable   }

  rxshv_SyFet   = $04;  { Symbolic name Fetch variable }

  rxshv_SyDro   = $05;  { Symbolic name Drop variable  }

  rxshv_NextV   = $06;  { Fetch "next" variable        }

  rxshv_Priv    = $07;  { Fetch private information    }

  rxshv_Exit    = $08;  { Set function exit value      }

Это команды функции RexxVariablePool. Из них более или менее понятны первые три (задать значение, считать значение, уничтожить переменную - именованый буфер) и $06 - считать следующее по цепочке значение. Таким образом, стем (а точнее именованый буфер) представляет собой цепочку таких блоков. Идентификатором такого буфера является его имя (shvname.strptr). Из каких-то соображений длина и имени стема и его значения повторяется дважды - один раз в составе переменной типа RxString, второй раз в соответствующем поле shvnamelen и shvvaluelen.

Собственно ничего сложного в написании подобной DLL нет, но пример с именованым буфером в Виртуале отсутствует, равно как и подробная документация по написанию DLL для REXX. Существуют также неочевидные моменты - везде в REXX используются null-terminated строки, поэтому нужен глаз да глаз ;), тем более что отлаживать такую библиотеку сложно, так как вызывается она не из EXE-ника...

Теперь о самой библиотеке. Она реализует одну-единственную функцию - ProcList, формат вызова которой:

    call RxFuncAdd 'PrcLoadFuncs', 'RXPROCSS', 'PrcLoadFuncs'

    call PrcLoadFuncs

    call ProcList 'PROCESS' /* самвызов */

    say 'Процессов 'PROCESS.0 /* там хранится число процессов*/

    do i=1 to PROCESS.0

    say PROCESS.i /* первое слово - PID процесса, второе его название*/

    end

Функцию определения имени процесса по PID и наоборот легко реализовать на самом REXX на основе полученного списка.

В DLL реализована функция PrcLoadFuncs, назначение которой - подключить все функции данной библиотеки. На данный момент таковая только одна, поэтому наличие этой функции необязательно. Если ее закомментировать, то подключение из рекс-скрипта будет выглядеть так:

  call RxFuncAdd 'ProcList', 'RXPROCSS', 'ProcList'

Программа использует недокументированную функцию DosQuerySysState, за информацию о ней спасибо DrChaos, iN8Malice, zuko, Euxx. zuko - особенно, так как он прислал исходник прямо на виртуале, избавив от необходимости медитации над сишными исходниками. OS2OK - только по его настоянию стал писать эту статью. Большое спасибо joseph за тестирование скриптов и sunlover за помощь в подготовке статьи. Ни в коем случае не претендую на авторство или оригинальность - это чистая компиляция уже проделанной работы.

Что можно усовершенствовать/открыть? Обработку ошибок добавить, у меня ее просто нет. Разобраться как с очередью из функции работать. Команды для RexxVariablePool поизучать. Научиться подключать рексовые длл-ки к паскалевским программам (т.е. обратная задача. Во-первых интересно, во-вторых в отладке поможет).

watchdog1.zip - программа-сторож процессов на рексе. Если какой процесс из списка охраняемых умрет, то она его рестартует. Список процессов получает перенаправлением вывода ps.exe в очередь.
watchdog13.zip - то же, но более продвинутый вариант. Конфиг расширен, процессы могут стартовать или детачится из их рабочих каталогов, и не сами екзешники, а батники, к примеру. Список процессов получает посредством библиотеки, использующей недокументированную функцию OS/2 API.
rxprclst.zip - исходные тексты библиотеки и простой пример ее использования.