Руководство программиста
Глава 2. Создание моделей блоков
§2.8. Сохранение и загрузка параметров блока
§2.8.5. Сохранение параметров блока в формате INI-файла
Рассматривается вспомогательный объект RDS, позволяющий организовать хранение личных данных блока в текстовом формате, похожем на стандартные INI-файлы Windows («имя=значение»). В один из предыдущих примеров добавляются процедуры сохранения и загрузки параметров с использованием этого объекта.
В §2.8.1 приводились причины, по которым текстовый формат хранения параметров блока предпочтительнее двоичного: текстовый формат более универсален, при его использовании менее вероятны проблемы с совместимостью разных версий моделей, данные в текстовом формате могут быть, при необходимости, просмотрены и отредактированы пользователем без использования RDS. Однако, поддержка полноценного текстового формата требует от программиста достаточно больших усилий, даже при использовании вспомогательного объекта для разбора текста. Требуется организовать цикл для считывания текста по словам, сравнивать считанные слова с ключевыми тем или иным способом и т.д. При желании, разработчик модели блока может упростить себе жизнь, используя для записи параметров блока не пары «ключевое_слово значение», а формируя текст в формате INI-файлов Windows, то есть в виде отдельных строк «ключевое_слово=значение», разбитых на секции, названия которых указываются в квадратных скобках. В RDS есть вспомогательный объект для работы с таким форматом данных, причем его можно использовать как при формировании текста с параметрами, так и при его разборе, что позволит сделать функции записи и чтения параметров очень похожими. Надо заметить, что скорость работы у него несколько ниже, чем у описанного в §2.8.4 объекта для поиска ключевых слов, и это нужно учитывать при создании моделей блоков с большим числом параметров.
При рассмотрении записи параметров в двоичном формате мы использовали в качестве примера модель Test1. Хотя эта модель и не выполняет никаких полезных действий, для иллюстрации различных способов сохранения параметров она довольно удобна, поскольку у нее есть личная область данных и два параметра разных типов. Добавим в нее поддержку текстового формата хранения параметров с использованием нового вспомогательного объекта, выделив для удобства загрузку и сохранение параметров в отдельные функции-члены класса TTest1Data. Описание класса личной области данных блока с двумя новыми функциями будет выглядеть следующим образом (изменения выделены цветом):
//====== Класс личной области данных ====== class TTest1Data { public: int IParam; // Целый параметр double DParam; // Вещественный параметр void SaveText(void); // Функция записи параметров void LoadText(char *text); // Функция загрузки параметров int Setup(void); // Функция настройки параметров TTest1Data(void) // Конструктор класса { IParam=0; DParam=0.0; rdsMessageBox("Область создана","TTest1Data",MB_OK); }; ~TTest1Data() // Деструктор класса { rdsMessageBox("Область удалена","TTest1Data",MB_OK);}; }; //=========================================
Модель блока будет вызывать функции SaveText и LoadText при сохранении и загрузке параметров точно так же, как и модели в предыдущих примерах. В оператор switch внутри функции модели необходимо добавить два новых оператора case:
// Запись параметров в текстовом формате case RDS_BFM_SAVETXT: data=(TTest1Data*)(BlockData->BlockData); data->SaveText(); break; // Загрузка параметров в текстовом формате case RDS_BFM_LOADTXT: data=(TTest1Data*)(BlockData->BlockData); data->LoadText((char*)ExtParam); break;
В функции SaveText мы будем формировать текст с параметрами блока при помощи вспомогательного объекта для работы с INI-файлами:
// Функция сохранения параметров void TTest1Data::SaveText(void) { RDS_HOBJECT ini; // Вспомогательный объект // Создание объекта для работы с образом INI-файла ini=rdsINICreateTextHolder(TRUE); // Создание новой секции "General" rdsSetObjectStr(ini,RDS_HINI_CREATESECTION,0,"General"); // Запись двух параметров блока rdsINIWriteInt(ini,"IParam",IParam); rdsINIWriteDouble(ini,"DParam",DParam); // Передача сформированного в объекте текста в RDS для записи rdsCommandObject(ini,RDS_HINI_SAVEBLOCKTEXT); // Удаление объекта rdsDeleteObject(ini); } //=========================================
Вспомогательный объект, работающий с образом INI-файла в памяти, создается сервисной функцией rdsINICreateTextHolder. Единственный логический параметр функции указывает на необходимость учитывать (FALSE) или игнорировать (TRUE) регистр символов в именах параметров и секций файла. В данном случае регистр нам не важен, поэтому в функцию передается значение TRUE. Идентификатор созданного объекта присваивается переменной ini.
Сразу после создания объекта он не содержит ни одной секции. Несмотря на то, что в данном примере в блоке всего два параметра, и в разбиении их на секции нет необходимости, нам все равно придется записывать параметры в какую-либо секцию образа INI-файла – таковы требования формата. Будем хранить оба параметра в секции с названием «General». Прежде чем записывать значения параметров блока, необходимо создать эту секцию при помощи универсальной функции передачи строки объекту rdsSetObjectStr с параметром RDS_HINI_CREATESECTION. Начиная с этого момента, все команды записи и чтения параметров будут работать с этой секцией.
Теперь, когда в образе INI-файла создана секция, можно записывать в нее параметры блока. Для этого используются сервисные функции rdsINIWriteInt (для целого параметра) и rdsINIWriteDouble (для вещественного), в которые, помимо идентификатора объекта, передается имя параметра и его значение. В данном случае имена параметров в тексте будут совпадать с именами параметров в классе блока: «IParam» для IParam и «DParam» для DParam. Имя секции в эти функции не передается, они всегда работают с текущей секцией образа файла. В данном случае текущей будет последняя созданная секция, то есть «General».
Теперь, когда образ INI-файла сформирован в объекте, необходимо передать его в RDS для записи вместе с другими параметрами блока. Самый простой способ сделать это – вызвать функцию rdsCommandObject с параметром RDS_HINI_SAVEBLOCKTEXT. После этого вспомогательный объект больше не нужен – его следует уничтожить при помощи функции rdsDeleteObject.
При сохранении блока в текстовом формате для блока с измененной моделью будет записан текст следующего вида (результат работы функции SaveText выделен цветом):
dllblock name "Block1"
begin
pos 10 10
layer id 1
vars
begin
signal name "Start" in menu run default 0
signal name "Ready" out menu default 0
end
dll file "p2_8_5.32.dll" func "Test1@12" file64 "p2_8_5.64.dll" func64 "Test1" setup "" auto
dlldata text
[General]
IParam=1
DParam=5.5
enddlldata
end
Как обычно, текст, сформированный моделью блока, размещается между строками «dlldata text» и «enddlldata».
Функция SaveText в этом примере получилась сложнее аналогичных функций из прошлых примеров, в которых не использовались вспомогательные объекты. В принципе, эту функцию тоже можно было бы переписать так, чтобы точно такой же текст формировался без участия объекта. В этом случае функция примет следующий вид:
// Функция сохранения параметров – упрощенный вариант void TTest1Data::SaveText(void) { char buffer[1024]; // Буфер для формирования текста // Формирование текста в буфере при помощи функции sprintf sprintf(buffer, "[General]\nIParam=%d\nDParam=%lf", IParam,DParam); // Передача сформированного текста в RDS rdsWriteBlockDataText(buffer,FALSE); } //=========================================
Функция получится значительно короче, однако, это будет ее единственным достоинством. При увеличении числа параметров блока строка формата функции sprintf очень скоро разрастется до огромных размеров, что существенно затруднит отладку и внесение изменений в нее. Чтобы найти спецификатор формата для какого-либо параметра блока, придется определять его номер в списке аргументов sprintf, а затем отсчитывать в форматной строке такое же число спецификаторов. Строка формата не отличается хорошей читаемостью, поэтому здесь легко допустить ошибку, что приведет к тому, что параметры блока будут записываться в текст не на своих местах или в неправильном формате. Кроме того, необходимо постоянно следить за тем, чтобы формируемый текст гарантированно уместился в буфер, независимо от фактических значений параметров. Можно, конечно, записывать в буфер по одному значению параметра за раз, и каждый раз после этого вызывать rdsWriteBlockDataText. Это позволит использовать меньший буфер и улучшит читаемость текста, однако, при этом потеряется единственное достоинство упрощенной функции SaveText – ее длина станет больше, чем у исходной версии, использующей вспомогательный объект. По этой причине первый вариант SaveText предпочтительнее: объект сам следит за необходимым размером своих внутренних буферов.
Рассмотрим теперь функцию LoadText, которая будет извлекать значения параметров блока из текста в формате INI-файла:
// Функция загрузки параметров void TTest1Data::LoadText(char *text) { RDS_HOBJECT ini; // Вспомогательный объект // Создание объекта для работы с образом INI-файла ini=rdsINICreateTextHolder(TRUE); // Передача в объект текста, полученного из RDS rdsSetObjectStr(ini,RDS_HINI_SETTEXT,0,text); // Установить текущую секцию if(rdsINIOpenSection(ini,"General")) { // Такая секция есть в тексте – считать из нее параметры IParam=rdsINIReadInt(ini,"IParam",0); DParam=rdsINIReadDouble(ini,"DParam",0.0); } // Удаление объекта rdsDeleteObject(ini); } //=========================================
Прежде всего, как и в SaveText, в этой функции вызывается rdsINICreateTextHolder для создания вспомогательного объекта. Затем в этот объект передается текст параметров блока, полученный из RDS. Для этого используется вызов rdsSetObjectStr с параметром RDS_HINI_SETTEXT. В этот момент объект разбирает переданный ему текст на секции, имена параметров и их значения, и переводит его во внутренний формат для ускорения поиска. Теперь можно получить из объекта значения параметров блока, но прежде необходимо указать, с какой секцией текста мы собираемся работать. Установка текущей секции производится с помощью функции rdsINIOpenSection, в которую, кроме идентификатора объекта, передается имя секции (в данном случае, «General»). После этого все команды чтения параметров (как и команды записи, хотя мы и не используем их в этой функции) будут обращаться к указанной секции. Функция возвращает TRUE, если такая секция есть в тексте, и FALSE, если ее нет. Читать параметры блока имеет смысл только в том случае, если секция существует, поэтому вызов rdsINIOpenSection производится в условии оператора if. Если функция вернула TRUE, значения параметров блока извлекаются из текущей секции при помощи функций rdsINIReadInt и rdsINIReadDouble, в которые передается идентификатор объекта, имя параметра и значение по умолчанию, которое будет возвращено в случае отсутствия в секции параметра с таким именем. В конце функции созданный вспомогательный объект уничтожается при помощи rdsDeleteObject.
Можно заметить, что функция LoadText по структуре похожа на SaveText. Обе они начинаются созданием объекта и заканчиваются его уничтожением, созданию секции в SaveText соответствует установка текущей секции в LoadText, вызовам rdsINIWrite… при записи параметров соответствуют аналогичные вызовы rdsINIRead… для их чтения. Таким образом, написав и отладив функцию записи параметров, можно легко сделать на ее основе функцию чтения, заменив одни вызовы на другие с практически идентичными параметрами, и вставив в начало функции загрузки команду передачи текста в объект вместо команды передачи текста из объекта в RDS в конце функции записи. Из-за похожести этих функций значительно упрощается добавление в блок новых хранимых параметров: команды их чтения и записи будут вставляться примерно в одни и те же места двух похожих функций.
Функция загрузки параметров, работающая с форматом INI-файла, устроена значительно проще функции, поддерживающей разбор произвольного текста – в ней не нужно организовывать цикл для пословного чтения. Если при произвольном разборе считанное из текста слово ищется в массиве ключевых слов, то в этом формате, наоборот, указанное ключевое слово (имя параметра) ищется в тексте. Из-за этого вспомогательный объект, работающий с образом INI-файла, требует для работы гораздо больше памяти, чем объект для поиска ключевых слов. В отличие от последнего, хранящего в памяти сравнительно небольшой массив с ключевыми словами и их целыми идентификаторами, он переводит во внутренний формат весь предоставленный ему текст, разбивая его на секции и выделяя имена параметров. Это несколько замедляет работу, но без этого объекту пришлось бы каждый раз просматривать весь текст в поисках нужной секции и нужного параметра, что привело бы к гораздо большему снижению скорости. Другой недостаток этого формата – его жесткость. В нем нельзя организовывать иерархические конструкции, вроде блоков «begin…end», которые широко используются в текстовом формате RDS и могут быть реализованы при разборе произвольного текста (хотя, следует отметить, что для этого придется затратить некоторые усилия – встроенного механизма для поддержки такой иерархии в объекте нет). Однако, для большинства блоков такой формат вполне подходит.