Device Manage for OS/2

DevCon for OS/2 - Developer Connection

Operating systems:
ArcaOS, eComStation, IBM OS/2 Warp
Мифы о eComStation 

(Unsorted)  
 
 
Compilers  
 
 
Tools  
 
 
User Interface  
 
 
REXX  
 
 
Drivers/kernel  
 
 

 

 

Конфигурация: используйте XObject

Вступление:

Эта статья посвящена некоторым аспектам работы с файлами конфигурации (ini-файлам, профилям, registry и прочим). На фоне стандартных (распространенных) методов организации файлов настроек (конфигурации) описывается разработанный для конкретной задачи (Буровой тренажер под OS/2), но имеющий вполне универсальное значение язык описания информационных (неисполняемых) объектов, а также средства (API) использования его в среде OS/2.

Всем известны самые примитивные средства описания в виде пар "параметр=значение", столь любимые программистами в среде различных *ix (автор конечно же не поднимет руку на охаивание синтаксиса правил разбора в sendmail.cf - сами знаете за что), от которых не слишком далеко ушли структуры (синтаксис) файлов config.sys и autoexec.bat, возможное содержание которых достаточно хорошо всем знакомо. Ниже, в качестве примера, приведен кусок моего config.sys файла:

SET EPMPATH=C:\OS2\APPS;C:\opendoc\BIN;
PROTECTONLY=NO
SHELL=C:\OS2\MDOS\COMMAND.COM C:\OS2\MDOS
FCBS=16,8
RMSIZE=640
DEVICE=C:\OS2\MDOS\VEMM.SYS
DOS=LOW,NOUMB
DEVICE=C:\OS2\MDOS\VXMS.SYS /UMB
DEVICE=C:\OS2\MDOS\VDPMI.SYS
DEVICE=C:\OS2\MDOS\VDPX.SYS
DEVICE=C:\OS2\MDOS\VWIN.SYS
DEVICE=C:\OS2\MDOS\VW32S.SYS

Критиковать такой синтаксис несложно - он не позволяет синтаксически отделять описания разных объектов в одном файле. Зато его использование примитивно реализуется программно и во многих случаях оправдано. Но не во всех случаях возможно.

Незначительное расширение такой синтаксис получил в ini-файлах от Windows X.XX (например 3.11) за счет введения секций. Именованные секции позволяют структурировать описания и формулировать запросы на элементы описания уже по двум параметрам "имя секции"-"имя параметра", что гораздо удобнее. Вот пример из файла SYSTEM.INI для WIN-OS2 сессии:

[keyboard]
subtype=
type=4
keyboard.dll=kbdru.dll
oemansi.bin=xlat866.bin
typeofswitch=2
secondkeyb.dll=kbdusx.dll

[boot.description]
keyboard.typ=Enhanced 101 or 102 key US and Non US keyboards
mouse.drv=Microsoft, or IBM PS/2
network.drv=No Network Installed
language.dll=Russian
system.drv=MS-DOS System
codepage=866
woafont.fon=Russian (866)

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

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

Следующим шагом в развитии идеи скриптов-описателей стали registry от Windows9X и далее, а также ini-файлы в OS/2. Привести их примеры не представляется возможным в силу их непечатности. Отметим следующее - они подразумевают древовидную структуру элементов (развитие идеи вложенности) и позволяют хранить объекты любого типа за счет введения бинарных объектов трактуемых теми, кто их использует.

Для критики существующих реализаций подобного ресурса (в OS/2 он реализован в WINPRF.DLL) необходимо уточнить требования к синтаксису файлов конфигурации вообще.

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

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

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

  • WPS-объектов каждого отдельного объекта сценария (элементы оборудования, геологии и прочее);
  • WPS-объектов для сценариев каждого типа технологических задач (отличных составом оборудования, а значит и диалогами его описания);
  • программного модуля вытягивания из сценария конкретных данных и передачи в виде заполненной структуры в задачу-имитатор.

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

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

Все это подвигло нас на формулировку следующих требований к концепции конфигурирования:

  • для экономии усилий пользователя скрипт должен иметь определение и реализацию (ОО подход);
  • для удобства пользования и экономии (возможности затуманивания мозгов и заметания следов) файлы конфигурации должны иметь как текстовое, так и бинарное представление, что не должно влиять на методы их использования;
  • конфигурация сама должна создавать диалог своего сопровождения или изменяться "голыми руками" в редакторе;
  • конфигурация сама должна себя печатать;
  • конфигурация должна оперировать данными хранящимся в других файлах.

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

Неформальное описание:

Любой файл конфигурации рассматривается как набор полей некоторых типов. Типы могут быть простыми и составными. Таким образом сам файл конфигурации является объектом некоего составного типа (класса).

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

Литералы используются такие-же как в C++ : \xx, \\,\n,\t, etc, а также специальные литералы %.

Комментарии обозначаются сочетанием символов //, /*,*/ и правила их использования аналогичны С++. 

Система оперирует следующими объектами:
классификаторы transient берется из класса как значение по умолчанию; может быть изменен в каждом представителе
базовые типы данных int
double
string
group
целое (4 байта)
вещественное (8 байт)
строка символов (не обязательно ограничивается \0)
массив произвольных объектов - позволяет доступ по номеру элемента
базовые типы данных для визуализации и печати IntField
DoubleField
StringField
TextField
RadioToggleField
CheckBoxField
ComboTogleField
ReferenceField
PasswordField
класс редактируемого и печатаемого int
класс редактируемого и печатаемого double
класс редактируемого и печатаемого string
класс редактируемого и печатаемого текстового окна (textarea)
класс выбора в диалоге с помощью RadioButton
класс выбора в диалоге с помощью CheckBox
класс выбора из списка в диалоге с помощью ComboBox
класс-ссылка - в диалоге редактируется с помощью drag&drop
класс для ввода и хранения пароля,  закодированного по  GOST 21847-89
Списочный тип ObjectListField класс накопления ссылочных элементов произвольного типа
namedgroup основан на базе group, используется длп описания диалога и печати

Далее приведены их описания в предлагаемом синтакисе :


IntField = {
string title = "" // название выводимое при печати и редактировании в диалоге
int value = 0 // значение поля
int min = 0 // контролируемое в диалоге мнимальное значение целого value
int max = 0 // контролируемое в диалоге максимальное значение целого value
int delta = 0 // шаг автоизменения при редактировании в диалоге
}

DoubleField = {
string title = "" // название выводимое при печати и редактировании в диалоге
double value = 0 // значение поля
double min = 0 // контролируемое в диалоге мнимальное значение вещественного value
double max = 0 // контролируемое в диалоге максимальное значение вещественного value
double coef = 0 // коэффициент перевода (для случая редактирования в одних единицах
// измерения, а использования в других)
double delta = 0 // шаг автоизменения при редактировании в диалоге
int decimals = 0 // количество выводимых при печати и редактировании знаков
// после десятичной точки
}

StringField = {
int max = 0 // максимальное количество символов в строке
string title = "" // название выводимое при печати и редактировании в диалоге
string value = "" // если строка ограничена одинарными кавычками '...' ограничитель строки
// (\0 не ставится)
}

TextField = { // аналогичен StringField, но представляет собой textarea (MLE)
int max = 0
string title = ""
string value = ""
}

RadioToggleField = { // название говорит само за себя - реализация диалогового элемента
string title = ""
int value = 0 // содержит номер выбранной строки
group elements = { } // набор строк типа string
}

CheckBoxField = { // название говорит само за себя - реализация диалогового элемента
string title = ""
int value = 0 // содержит 1 если элемент выбран
}


 ComboToggleField = { // аналогично предыдущему для объекта ComboBox
string title = ""
int value = 0
group elements = { }
}

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


ReferenceField = { // Объект-ссылка на другой объект. В диалоге реализует drag&drop
string title = ""
string class = "" // имя класса объекты которого могут заполнять данное поля
// (их может быть много - class0|ckass1|...|classN)
string value = "" // имя полное файла содержащего объект
int value2 = 0 // WPS-objectid

}


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

ObjectListField list = { // пример списка объектов ReferenceField заданного класса
   transient string title = ""
   transient group factory = {
      ReferenceField rs = {
         transient string title = ""
         transient group menu = {
            "Открыть"
            "Очистить"
         }
        transient string class = ""
        string value = ''
        int value2 = 0
     }
   }
   transient group buttons = {
      "Добавить"
      "Удалить"
   }
   group elements = {
   }
}



При выводе диалога ObjectListField в списке выводятся значения элемента string title описания объектов, либо, значение элемента name|value,  если в описание ссылочного объекта  введен элемент StringField name.

Доступ к паролю, хранимому в PasswordField, обеспечивает экспортируемая функция:
rc=getPasswordFieldValue(*XObject,password,sizeof(password));

В качестве чисто практического (для описания изображения пород), реализован класс масски заливки:


ColorMaskField = {
   string title = ""
   int fcolor = 0      // цвет переднего плана
   int bcolor = 0      // цвет фона
 int width = 16 // ширина в точках
int height = 16 // высота в точках
  string value = '' // маска заливки 8*8 }

Также можно заметить, что существуют и предустановленные данные:

  • title - по умолчанию несет строку-название элемента, которая используется в диалоге и при печати;
  • status - для записи строки SUBTITLE  страницы диалога
  • value - по умолчанию несет значение элемента;
  • elements - используется при описании диалога и печати;
  • pages - описывает страницу диалога;
  • sections - описывает секцию печати скрипта.

На основе этих простых и сложных типов можно создавать свои балозые классы описаний.
Так, например, выглядит класс-описание забойного двигателя:

DEngine drill={
   string title="Забойный двигатель"

   DoubleField diam_zd    ={ title="Диаметр заб.двигателя" coef=0.001 decimals=1 delta=0.1 min=30 max=400}
   DoubleField leng_zd    ={ title="Длина заб.двигателя" coef=1 decimals=2 delta=0.01 max=50}
   DoubleField q_zd       ={ title="Расход заб.двигателя" coef=1000 decimals=1 delta=0.1 min=30 max=400}
   DoubleField dens_zd    ={ title="Плотность жидкости" coef=1000 decimals=2 delta=0.01  min=0.8 max=2.5}
   DoubleField loss_px_zd ={ title="Потери давления на холостом ходу" coef=98100 min=1 max=200}
   DoubleField freq_nx_zd ={ title="Частота вращения на холостом ходу" coef=0.01666 min=10 max=999}
   DoubleField loss_pt_zd ={ title="Потери давления при торможении" coef=98100 min=1 max=200}
   DoubleField moment_t_zd={ title="Тормозной момент" coef=10 min=1 max=1500}
   ComboToggleField drop   ={
       string title ="Тип двигателя"
       group elements = {
           string = "турбобур с постоянной линией"
           string = "турбобур с падающей линией"
           string = "объемный двигатель"
           }
       }
}

А так выглядит описание конкретного забойного двигателя (/* комментарий */ в первой строке необходим для интерпретации текста как XObject):

/* XObject */
DEngine = {
diam_zd = {value = 30.0000}
leng_zd = {value = 0.0000}
q_zd = {value = 30.0000}
dens_zd = {value = 0.8000}
loss_px_zd = {value = 1.0000}
freq_nx_zd = {value = 10.0000}
loss_pt_zd = {value = 1.0000}
moment_t_zd = {value = 1.0000}
drop = {value = 0}
}

Данный синтаксис показывает возможность изменения в каждом конкретном объекте и вспомогательных полей: min, max и прочих.

Как восклицала Алиса Кэрола - "Кому нужна книжка без картинок и диалогов!"
Обратимся к заявленной самодиалоговости и документированности, т.е визуализации и печати.
Создан, как говорилось ранее, WPS-объект интерпретирующий специально организованные части описания классов как страницы своих свойств (property). Для этого в описание класса вносятся специальные описания - group с именами pages ( для диалога) и sections (для печати).
Вот как выглядит описывающая диалог часть класса :

group pages = {
namedgroup = { // описание страницы диалога
title = "%MajorTab" // символ % означает MajorTab
elements = { string = "имя 1" // элемент класса string = "имя 2" ... string = "имя N" } } namedgroup = { // вторая страница диалога title = "MinorTab страницы" elements = { string = "имя 1" string = "имя 2" ... string = "имя N" } } ... // другие страницы диалога }

Вот как выглядит (и что порождает) часть описания класса забойного двигателя (DEngine) относящаяся к диалогу:

DEngine drill={
  ...
    group pages = {
       namedgroup = {
          string title = "%Забойный двигатель"
          group elements = {
             string string = "drop"
             string string = "diam_zd"
             string string = "leng_zd"
             string string = "q_zd"
             string string = "dens_zd"
             string string = "loss_px_zd"
             string string = "freq_nx_zd"
             string string = "loss_pt_zd"
             string string = "moment_t_zd"
             }
          }
     }
 }

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

Для описания печати объекта используется аналогично организованная group sections и вот, что получается при печати:

DEngine drill={
  ...
   group sections={
    namedgroup={
       title = "Забойный двигатель"
       elements={
          string ="drop"
          string ="diam_zd"
          string ="leng_zd"
          string ="q_zd"
          string ="dens_zd"
          string ="loss_px_zd"
          string ="freq_nx_zd"
          string ="loss_pt_zd"
          string ="moment_t_zd"
          }
       }
    }
 }

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


Как все это использовать:

Для использования предлагаемой системы работы с конфигурациями необходимо в корне или каталоге указанном переменной окружения AMT_PATH создать файл config.ini следующего содержания (на примере тренажера):

/* AMT */
group Config = {
        string customer = "ЗАО АМТ"
        int loglevel = 0              // -1 полный отладочный вывод в файл \xobject.log
                                      //  0 выводить только сообщения обошибках
                                      //  1 выводить предупреждения исообщения об ошибках
        group classes = {
                string = "classes.ini"  // описание общих классов (обпзательно)
                string = "common.ini"   // описание другой группы общих классов
                string = "DSTclass.ini" // описание классов бурового тренажера
                string = "KRSclass.ini" // описание классов тренажера для капитального ремонта скважин
        }
}

Этот файл используется WPS-объектом AMTWPSXObject реализующем диалоги и печать объектов конфигурации. Этот объект ассоциирован с расширением AMT.  
В доступный по переменной окружения LIBPATH следует скопировать все динамические библиотеки подержки XObject и провести регистрацию  с помощью  следующего скрипта на Rexx:

/*********************************************************************/
/* XObject registration utility*/
/*********************************************************************/
Call RxFuncAdd 'SysLoadFuncs', 'REXXUTIL', 'SysLoadFuncs'
Call SysLoadFuncs
Say 'AMT XObjects to be registered'
rc=SysRegisterObjectClass('AMTWpsObject', 'amtwpso')
if rc then say 'AMTWpsObject registered'
else say 'AMTWpsObject cannot be registered'

Программирование с помощью API XObject:
При программировании следует самому прочитать описания используемых классов - например весь config.ini и все перечисленные в нем файлы или только нужные задаче.Следующий текст дает пример считывания всех библиотек классов описанных в config.ini:

#include "common.h"

XObject  *o_config;  // для сценария

// функция инициализирует подсистему предустановленных классов XObject
// и возвращает кол-во классов в библиотеках
int InitXClasses(void)
{
char buf[256]="", *str;
char *amtPath = getenv(AMT_PATH);
int num=0;

   XOBJECT_INITIALIZE

// загрузка библиотек классов
   buf[0] = 0;
   if (amtPath) strcat(buf, amtPath);
   strcat(buf, "\\");
   strcat(buf, AMT_CONFIG);
   o_config = XObject::loadObject(buf);
   if (!o_config)
      return 1;

   o_config = o_config->getGroupElement("classes");
   for (int i = 0; igetGroupSize(); i++ )
       {
       str = o_config->getGroupElement(i)->getStringValue();
       if (str) 
          {
          buf[0] = 0;
          if (amtPath)
             strcat(buf, amtPath);
          strcat(buf, "\\");
          strcat(buf, str);
          num += XObject::loadClasses(buf);
          }
       }
return num; }

Cледующие строки дают представление о получении значений некоторых элементов класса сценария для тренажера:


{
...
 o_main=XObject::loadObject(file_name);  // загрузка файла конкретного сценария
   if (!o_main)
      return 1;   // ошибка чтения сценария - переход к тестовому режиму ... o_sub = o_main->getGroupElement("title"); Name=strdup(o_sub->getStringValue()); o_sub = o_main->getGroupElement("model_type"); model_type=o_sub->getIntValue(); // тип (номер) модели данного тренажера o_sub = o_main->getGroupElement2("mode|value"); // здесь используется составное имя mode=o_sub->getIntValue(); // тип (номер) модели данного тренажера // если элемент является ссылкой, можно воспользоваться методом GetValue(name), // который сам и загрузит скрипт ссылки o_ref=o_main->getValue("derrik"); // загрузить описание буровой вышки // или таким методом // o_ref=XObject::loadObject(o_main->getGroupElement2("derrik|value")->getStringValue(); // вычислить значение параметра g_kv в системе СИ g_kv=o_ref->getGroupElement2("g_kv|value")->getDoubleValue() * o_ref->getGroupElement2("g_kv|coef")->getDoubleValue(); }
Это был пример чтения конфигурации.

Доступ к элементам конфигурации (параметрам) может производится по именам полей класса (name), по составным именам ("name|subname"), а также по их порядковым номерам в group. В приведенном выше описании забойного двигателя величина параметра может браться по имени <%1|value>, т.к. diam_zd имеет порядковый номер один в классе.

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

Для заинтересовавшихся приведем описание public объектов класса XObject

class _Export XObject {
public:
static void setLogLevel(int);
static int log(char*, ...);
static int log(int, char*, ...);
static int logObject(XObject*, char* = NULL);
static void logClasses();

static void initClass();
static void unInitClass();
static int loadFile(char*, char**, int*);
static int saveFile(char*, char*, int);
static XObject* getInstance(char*, char*, int = FLAGS_NORMAL, int =-1);
static XObject* getInstanceText(char*, int*, int =-1);
static XObject* getInstanceBin(char*, int*, int = -1);
static int loadClasses(char*);
static XObject* loadObject(char*);
static int saveObject(char*, XObject*);
...
public:
static int getClassNum();
static char* getClassName(int);
...
public:
virtual ~XObject();
int isVoid();
int isInteger();
int isDouble();
int isString();
int isComplex();
int isGroup();
int isObject();
int isObject(char*);
int isBasic();

virtual XObject* clone();
int initFrom(XObject*);

char* getName();
int setName(char* n);
char* getClassName();
int getClassVersion();

int getFlags();
int setFlags(int);
void flagSet(int);
void flagClear(int);
int flagIsSet(int);

int getSizeBin();
int getSizeText();

int getIntValue();
int setIntValue(int);

double getDoubleValue();
int setDoubleValue(double);

char* getStringValue();
int getStringSize();
int getStringLength();
int setStringSize(int);
int setStringValue(int, char*);
int setStringValue(char*);

int getGroupSize();
XObject* getGroupElement(int);
XObject* getGroupElement(char*);
XObject* getGroupElement2(char*);
int addGroupElement(XObject*);
int removeGroupElement(int);
int removeGroup();

int loadBinFile(char*);
int readText(char*, int);
int readBin(char*, int);
int writeBin(char*, int);
int writeText(char*, int, int = 0);

virtual XObject* getValue();
virtual int show(int);
virtual int print(int);
virtual int html(int);
// misc
static char* strPrintInteger(char*, int, int);
static char* strPrintDouble(char*, double, int, int);
static char* strPrintString(char*, char*, int, int*);
static char* strPrintStringEsc(char*, char*, int, int*);
...
};

Если вам понравилась описанная идея, вы можете скачать комплект для разработчика здесь.
Если вы хотите поспорить или просто поговорить с автором, пишите сюда: Joseph Shrago -- e:mail : LFer at rambler dot ru

 


 

(C) OS2.GURU 2001-2024