http://sulfurzona.com/
News
Service
Magazine
Software (Battle City Game, Wallpaper manager, Superpad, VG-NOW, Puzzle Game, Netler Internet Browser, ..)
Dune Game (Dune III, Dune IV, Cheats, Forum, ..)
Games free
Turbo Pascal (Assembler, Docs, Sources, Debbugers, ..)
Books (Docs for developers)
Forum
Guest book
Компьютерная диагностика двигателя автомобиля (адаптер К-линии)Компьютерная диагностика двигателя автомобиля (адаптер К-линии)
 
 
 

Паскаль для новичков (часть 30)

 
Спрашивали? Отвечаю…

Конфигурирование программы

 
Паскаль для новичковВ моей переписке с читателями промелькнул читательский вопрос на тему сохранения настроек программы в файле конфигурации *.INI. При этом читателю явно было лень самому обмозговать эту совсем несложную задачу.
  
Ну что же, попробуем самостоятельно написать модуль, который позволял бы нам выполнять следующие действия:
а) составлять текстовый файл конфигурации;
б) организовывать в одном файле несколько секций параметров;
в) сохранять в каждой секции несколько параметров, при чём, чтобы поддерживались такие типы данных как строковые, дискретные и булевские;
г) устанавливать для параметров значения по умолчанию, если загрузить значения из файла конфигурации не представляется возможным;
д) минимальные требования к памяти.
  
Как добравшийся до трибуны ;O) предлагаю решить задачу путём написания двух типов объектов. Один объект должен решать задачу загрузки конфигурации из файла, а второй объект – задачу сохранения оной в файл.
  
Для начала дадим нашему модулю название
 
Unit INIFiles;
 
Interface
 
и в интерфейсной части укажем максимально допустимый размер MaxArr для буфера загрузки файла конфигурации
 
const MaxArr = 32767;
 
   Далее опишем тип TArrChar для буфера загрузки текста и тип-указатель на него, а также объявим объекты TReadINIFile и TWriteINIFile
 
type
     TArrChar = array [0..MaxArr] of char;
     PArrChar = ^TArrChar;
 
     TReadINIFile = object
      constructor Create( FileName : string );
      destructor Free;
      function ReadStr( Section, Ident, Default : string ): string;
      function ReadInt( Section, Ident : string; Default : Longint ): Longint;
      function ReadBool( Section, Ident : string; Default : boolean ): boolean;
     private
       FBuf : PArrChar;
       FSize : word;
      function FindSection( Str : string ): integer;
      function FindIdent( Ident : string; First : integer ): string;
      function GetValue( Str : string ): string;
     end;
 
     TWriteINIFile = object
      constructor Create( FileName : string );
      destructor Free;
      procedure OpenSection( Section : string );
      procedure WriteStr( Ident, Value : string );
      procedure WriteInt( Ident : string; Value : Longint );
      procedure WriteBool( Ident : string; Value : boolean );
     private
       F : Text;
     end;
 
implementation
 

Чтение конфигурации

 
Приступим к написанию первого объекта.
  
В первую очередь следует организовать конструктор TReadINIFile.Create, при чём сразу инициализируем указатель буфера FBuf в значение Nil, что будет в дальнейшем означать, что загрузить значения параметров не получилось и следует использовать значения по умолчанию, и размер буфера FSize в нуль. Выполняем попытку, открыть файл с именем FileName. В случае неудачи завершаем выполнение конструктора. При успехе определяем размер файла, и если его размер превышает максимально допустимый размер буфера, то следует выделить не больше заданного максимума MaxArr, иначе пытаемся выделить столько памяти, сколько занимает файл. Проводим ревизию доступной памяти и если её не хватает, то завершаем конструктор.
 
Если памяти достаточно, то выделяем её под буфер FBuf и загружаем содержимое из файла. При ошибке загрузки освобождаем буфер памяти и инициализируем FBuf значением Nil. При успехе закрываем файл. Надо сказать, что при возникновении ошибочной ситуации я применил обыкновенный выход из процедуры, в данном случае конструктора, посредством выполнения команды Exit вместо Fail (для объектов) для того, чтобы экземпляр объекта несмотря ни на что был создан успешно. Ну, подумаешь, что файл конфигурации не читается, памяти не хватает – пустяки, потому что предусмотрен механизм задания значений параметров по умолчанию. А значит матч состоится в любую погоду ;O) С этим всё.
 
constructor TReadINIFile.Create( FileName : string );
var F : File;
       d : word;
begin
FBuf := nil;
FSize := 0;
Assign( F, FileName ); {$I-}
Reset( F, 1 ); {$I+}
if IoResult <> 0 then exit;
FSize := FileSize( F );
if FSize > MaxArr then FSize := MaxArr;
if MaxAvail < FSize then exit;
GetMem( FBuf, FSize );
BlockRead( F, FBuf^, FSize, d );
Close( F );
if d <> FSize then begin
   FreeMem( FBuf, FSize );
   FBuf := nil;
   FSize := 0;
   end;
end;
 
Для завершения работы с объектом следует в деструкторе предусмотреть освобождение полученной памяти, если указатель FBuf содержит реальный указатель.
 
destructor TReadINIFile.Free;
begin
if FBuf <> nil then FreeMem( FBuf, FSize );
end;
 
Теперь опишем метод поиска секции с названием Str в буфере, при чём заглавие секции должно быть заключено в квадратные скобки. В случае успеха метод должен возвращать позицию первого символа заглавия секции в файле, иначе следует возвратить значение меньше нуля. Для этого проводим поиск первого символа заглавия секции в буфере, и если поиск удался, то вызовом функции Compare сравниваем все последующие символы на равенство с заданным заглавием секции в Str. Если все символы сходятся, то завершаем метод, иначе продолжаем поиск далее.
 
function TReadINIFile.FindSection( Str : string ): integer;
var j : integer;
 
 function Compare : boolean;
 var k, Long : integer;
 begin
 Long := length( Str );
 k := 1;
 while (FBuf^[j+k-1]=Str[k]) and(k<=Long) do Inc(k);
 Compare := k > Long;
 end;
 
begin
FindSection := -1;
if FBuf = nil then exit;
if length( Str ) = 0 then exit;
Str := '[' + Str + ']';
j := 0;
while j < FSize do begin
          if FBuf^[j] = Str[1] then
              if Compare then begin
                 FindSection := j;
                 exit;
                 end;
          Inc( j );
          end;
end;
 
Ничего, что я так подробно? Ну тогда я продолжаю ;O)
  
Напишем метод поиска параметра с именем Ident относительно текущего конца заглавия секции First в файле. В случае успеха метод возвращает строку с параметром и его значением, отделённым знаком ‘=’ (равно), иначе пустую строку. Итак, ищем в буфере первый символ названия параметра. Ищем, пока не наткнёмся на конец буфера, либо на начало заглавия следующей секции в виде открывающейся квадратной скобки. Если первый символ найден, то проводим его сравнение функцией Compare, при чём, если сравнение удачно сошлось, и значит, найден искомый параметр, то копируем всю строку целиком до её окончания, то есть до символа #$0D не включительно.
 
function TReadINIFile.FindIdent( Ident : string; First : integer ): string;
var j : integer;
       idn : string;
 
 function Compare: string;
 var k, Long : integer;
        s : string;
 begin
 s := '';
 Long := length( Ident );
 k := 1;
 while (FBuf^[j+k-1]=Ident[k]) and (k<=Long) do Inc(k);
 if k = Long + 1 then begin
    k := 1;
 
 
 
 
    while (FBuf^[j+k-1]<>#$0D) and (k
              s[k] := FBuf^[j+k-1];
              Inc( k );
              end;
    s[0] := char(k-1);
    end;
 Compare := s;
 end;
 
begin
FindIdent := '';
if FBuf = nil then exit;
if length( Ident ) = 0 then exit;
Ident := Ident + '=';
j := First;
while (j
          if FBuf^[j] = Ident[1] then begin
             idn := Compare;
             if idn <> '' then begin
                FindIdent := idn;
                exit;
                end;
             end;
          Inc( j );
          end;
end;
 
Пришла очередь метода расшифровки значения параметра, которая сводится к поиску символа-разделителя ‘=’ и возвращению фрагмента строки, расположенного за ним. Собственно это и будет тот фрагмент, который будет содержать символьное представление значения искомого параметра. Если при расшифровке параметра что-то не “срослось”, то метод возвращает пустую строку.
 
function TReadINIFile.GetValue( Str : string ) : string;
var k : integer;
begin
GetValue := '';
k := pos( '=', Str );
if k = 0 then exit;
GetValue := copy( Str, k + 1, Length( Str ));
end;
 
Вот теперь можно читать строковые параметры при помощи следующего метода. Метод требует указания названия секции Section и названия искомого параметра Ident и значения по умолчанию Default на всякий случай. В начале работы метода, как раз на тот случай, инициализируем возвращаемый результат значением по умолчанию. Если буфер ничего не содержит, то и искать нечего – выходим. Если буфер не пуст, то вызовом FindSection( Section ) ищем секцию, и получаем её начало Sec в файле. Если секция не найдена, то выходим, иначе вызовом FindIdent ищем строку с параметром относительно конца заглавия секции. Если и тут промашка, то выходим, иначе вызовом GetValue пытаемся дешифровать строку с параметром и извлечь фрагмент строки со значением в переменную idn. Если строка пуста, то сыпем пепел на голову, опускаем руки, и ретируемся ;O), иначе возвращаем заветную строку.
 
function TReadINIFile.ReadStr( Section, Ident, Default : string ): string;
var sec : integer;
       idn : string;
begin
ReadStr := Default;
if FBuf = nil then exit;
sec := FindSection( Section );
if sec < 0 then exit;
idn:=FindIdent(Ident,sec+Length('['+Section+']'));
if idn = '' then exit;
idn := GetValue( idn );
if idn = '' then exit;
ReadStr := idn;
end;
 
Аналогично строится метод получения дискретного значения параметра Ident в секции Section со значением по умолчанию Default с одним лишь отличием, что полученное строчное выражение следует дешифровать стандартной процедурой Val.
 
function TReadINIFile.ReadInt( Section, Ident : string; Default : Longint ): Longint;
var sec, code : integer;
       idn : string;
       value : Longint;
begin
ReadInt := Default;
if FBuf = nil then exit;
sec := FindSection( Section );
if sec < 0 then exit;
idn:=FindIdent(Ident,sec+Length('['+Section+']'));
if idn = '' then exit;
idn := GetValue( idn );
if idn = '' then exit;
Val( idn, value, code );
if code <> 0 then exit;
ReadInt := value;
end;
 
И последний метод получения булевского параметра в комментариях не нуждается.
 
function TReadINIFile.ReadBool( Section, Ident : string; Default : boolean ): boolean;
var sec, code : integer;
       idn : string;
       value : Longint;
begin
ReadBool := Default;
if FBuf = nil then exit;
sec := FindSection( Section );
if sec < 0 then exit;
idn:=FindIdent(Ident,sec+Length('['+Section+']'));
if idn = '' then exit;
idn := GetValue( idn );
if idn = '' then exit;
Val( idn, value, code );
if code <> 0 then exit;
ReadBool := boolean( value );
end;
 

Запись конфигурации

 
Пришло время заняться объектом записи конфигурации. Конструктор будет открывать файл FileName для записи.
 
constructor TWriteINIFile.Create( FileName : string);
begin
Assign( F, FileName );
Rewrite( F );
end;
 
Деструктор просто закроет файл.
 
destructor TWriteINIFile.Free;
begin
Close( F );
end;
 
Метод открытия секции просто установит заглавие секции в файл.
 
procedure TWriteINIFile.OpenSection( Section : string );
begin
Writeln( F, '['+Section+']' );
end;
 
Методы записи параметров выглядят довольно недвусмысленно.
 
procedure TWriteINIFile.WriteStr( Ident, Value : string );
begin
Writeln( F, Ident + '=' + Value );
end;
 
procedure TWriteINIFile.WriteInt( Ident : string; Value : Longint );
begin
Writeln( F, Ident + '=', Value );
end;
 
procedure TWriteINIFile.WriteBool( Ident : string; Value : boolean );
begin
Writeln( F, Ident + '=', byte( Value ));
end;
 
end.
 

Практика

 
Самое время показать, как эту систему применять на практике. Предположим, что у нас есть файл конфигурации test.ini со следующим содержимым:
 
[Options1]
key1=parametr1
key2=parametr2
[Options2]
key3=parametr3
key4=parametr4
[Options3]
key3=41
key4=50
GodMode=1
MouseEnabled=0
 
Тогда следующая программа создаст экземпляр объекта для чтения конфигурации и выведет на экран значения параметров. При этом, из секции Options1 попытается прочесть параметр GodMode и не найдя его выдаст значение по умолчанию.
 
uses INIFiles;
 
var ReadINIFile : ^TReadINIFile;
       WriteINIFile : ^TWriteINIFile;
begin
{ чтение конфигурации }
 
New( ReadINIFile, Create( 'test.ini' ));
writeln(ReadINIFile^.ReadStr('Options1','key1','Default key1!'));
writeln(ReadINIFile^.ReadStr('Options1','key2','Default key2!'));
writeln(ReadINIFile^.ReadInt('Options3','key3',0));
writeln(ReadINIFile^.ReadInt('Options3','key4',0));
{читаем несуществующий параметр для тестирования}
writeln(ReadINIFile^.ReadBool('Options1','GodMode',false));
writeln(ReadINIFile^.ReadBool('Options3','GodMode',false));
writeln(ReadINIFile^.ReadBool('Options3','MouseEnabled',false));
Dispose( ReadINIFile, Free );
 
{ запись конфигурации }
 
New( WriteINIFile, Create( 'test2.ini' ));
WriteINIFile^.OpenSection('Options');
WriteINIFile^.WriteStr('UserName','Vlad');
WriteINIFile^.WriteStr('HostName','http://sulfurzona.com');
WriteINIFile^.WriteBool('GodMode',false);
WriteINIFile^.WriteBool('MouseEnabled',true);
WriteINIFile^.OpenSection('Keyboard1');
WriteINIFile^.WriteInt('KeyUp',31);
WriteINIFile^.WriteInt('KeyDown',41);
Dispose( WriteINIFile, Free );
end.
 
Часть программы для записи конфигурации создаст файл конфигурации test2.ini и запишет туда следующий текст:
 
[Options]
UserName=Vlad
HostName=http://sulfurzona.com
GodMode=0
MouseEnabled=1
[Keyboard1]
KeyUp=31
KeyDown=41
 
Как легко видеть, здесь нет ничего сложного.
 
Продолжение следует…
 
© Владислав Демьянишин
 
На нашем сайте можно не только бесплатно скачать игры, но и документацию и книги по программированию на MIDLetPascal, Turbo Pascal 6, Turbo Pascal 7, Borland Pascal, по программированию устройств Sound Blaster, Adlib, VESA BIOS, справочник Norton Guide и много другой полезной информации для программистов, включая примеры решения реальных задач по созданию резидентных программ. Предлагаю также посетить Марья искусница - сайт о рукоделии (http://mariya-iskusnica.ru).
 

Журнал > Программирование > Паскаль для новичков (Turbo Pascal, Assembler) > Паскаль для новичков (часть 30): Конфигурирование программы
 
 
 
62
 
ВКонтакте
Facebook
 
 
 
На главную страницу На предыдущую страницу На начало страницы
 
 
Украинский портАл Украина онлайн Рейтинг@Mail.ru Рейтинг Сайтов YandeG Rambler's Top100