• Уважаемые гости и новички, приветствуем Вас на нашем форуме
    Здесь вы можете найти ответы практически на все свои вопросы о серии игр «Готика» (в том числе различных модах на нее), «Ведьмак», «Ризен», «Древние свитки», «Эра дракона» и о многих других играх. Можете также узнать свежие новости о разработке новых проектов, восхититься творчеством наших форумчан, либо самим показать, что вы умеете. Ну и наконец, можете обсудить общие увлечения или просто весело пообщаться с посетителями «Таверны».

    Чтобы получить возможность писать на форуме, оставьте сообщение в этой теме.
    Удачи!

Создаем архиватор...

siryksv

Участник форума
Регистрация
5 Окт 2009
Сообщения
567
Благодарности
1
Баллы
225
В данной теме я покажу, как создать неплохой архиватор с помощью подручных средств. В роли подручных средств я буду использовать Delphi 7, нужна именно 7-я версия. Начиная с Delphi 7, в комплект поставки вместе с исходными кодами входит библиотека ZLib, которая предоставляет достаточно неплохой алгоритм сжатия данных, основанный на методах LZW-компрессии (алгоритмы Зива-Лэмпела). Для использования библиотеки надо подключить модуль zLib в список использованных модулей (uses).

Где это можно применить?
Лично мне это было полезным при создании инсталляторов (для экономии места и уменьшения размера инсталлятора), а также в некоторых сетевых программах, где возникала необходимость передавать файлы. Для уменьшения размера файлов и, соответственно, экономии трафика, я сжимал перед тем, как передавать файл. А на другой стороне распаковывал.

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

Для понимания сути происходящих процессов, нам понадобятся некоторые предварительные сведения о потоках данных в Delphi. К изложению этих сведений мы и приступаем.

Потоки в Delphi.
В Delphi существует абстрактный класс TStream. Он является очень полезным, так как предоставляет унифицированный доступ к понятию потока чего-либо, каких-то данных. Потоки являются удобным средством для работы с данными самого различного вида. Любой поток может открываться, читать и писать данные, изменять текущее положение указателя, и закрываться. Что значит "изменять положение указателя"? Дело в том, что при работе с TStream, носитель данных представляется в виде такой себе "магнитофонной ленты", и чтобы прочитать определенные данные, надо эту ленту перемотать. Однако вместо перемотки ленты удобнее ввести некоторый "указатель", который будет показывать текущую позицию на носителе, как бы положение "считывающей головки". Такая техника доступа к данным называется последовательной. Другие техники (произвольный доступ, например) нам в данном моем примере не понадобятся.
Итак, класс TStream – это поток абстрактный, и не привязан к конкретным видам данных. В то же время, и ребенку ясно, что, к примеру, для доступа к данным, находящимся в памяти, и находящимся на диске, нужны различные средства. Поэтому в Delphi существуют различные потомки класса TStream, которые уже ведут работу из данными конкретного происхождения. Наиболее часто используемыми являются классы TFileStream, TMemoryStream, TResourceStream, TStringStream, которые предоставляют средства работы из файлами, данными в памяти, ресурсами и строками соответственно.

Класс TFileStream
Класс TFileStream позволяет создать поток для работы с файлами. При этом поток работает с файлом без учета типа хранящихся в нем данных. Полное имя файла задается в параметре FileName при создании потока в конструкторе класса:
Код:
constructor Create(const FileName: string; Mode: Word);
Параметр Mode определяет режим работы с файлом. Он составляется из флагов режима открытия:
fmCreate — файл создается;
fmOpenRead — файл открывается для чтения;
fmopenwrite — файл открывается для записи;
fmOpenReadWrite — файл открывается для чтения и записи.

И флагов режима совместного использования:
fmShareExciusive — файл недоступен для открытия другими приложениями;
fmShareDenyWrite — другие приложения могут читать данные из файла;
fmShareDenyRead — другие приложения могут писать данные в файл;
fmShareDenyNone — другие приложения могут производить с файлом любые операции.

Для чтения и записи из потока используются методы Read и Write
Есть интересный метод в классе, унаследованный еще от предка TStream – функция CopyFrom:
Код:
function CopyFrom(Source: TStream; Count: Int64): Int64;
Тут Source – исходный поток, откуда копируется, а Count - количество байт, которые нужно скопировать. Если Count поставить равным 0, то будет скопирован весь поток, начиная с нулевой позиции.

Класс TMemoryStream
Класс TMemoryStream обеспечивает сохранение данных в адресном пространстве. При этом методы доступа к этим данным остаются теми же, что и при работе с файловыми потоками. Это позволяет использовать адресное пространство для хранения промежуточных результатов работы приложения, а также при помощи стандартных методов осуществлять обмен данными между памятью и другими физическими носителями.
Чтение/запись данных в память выполняется привычными методами Read и Write.
Также запись данных в память может осуществляться методами:
Код:
procedure LoadFromFile(const FileName: string); // из файла;
 procedure LoadFromStream(Stream: TStream) ; // из другого потока.
Дополнительно можно использовать методы записи данных в файл или поток:
Код:
procedure SaveToFile(const FileName: string);
procedure SaveToStream(Stream: TStream).
Остальные типы потоков нам не понадобятся. Хотя пример использования TResourceStream я уже показывал в теме про построение инсталляторов.

Архивация
Техника работы из библиотекой zLib основана на потоках. Библиотека экспортирует класс TCustomZLibStream - потомок класса TStream. Класс TCustomZLibStream в свою очередь является родителем для TCompressionStream и TDecompressionStream. Работа с этими классами стандартна, и внимания заслуживают только их конструкторы.

Класс TCompressionStream для упаковки данных имеет конструктор
Код:
constructor Create(CompressionLevel: TCompressionLevel; Dest: TStream)
Тут перечисление
Код:
TCompressionLevel = (clNone, clFastest, clDefault, clMax)
– степень сжатия, а Dest – поток назначения, куда будут записываться заархивированные данные.

Класс TDecompressionStream для распаковки имеет конструктор
Код:
constructor Create(Source: TStream);
Тут Source – потока назначения, куда будут записываться распакованные данные.

Классы TCompressionStream и TDecompressionStream работают "на лету" в том смысле, что упаковка/распаковка и запись в принимающий поток осуществляется сразу по мере того как данные поступают на вход класса (для TCompressionStream) или считываются из него (для TDecompressionStream).

Замечание: Для того, чтобы во время работы программы не создавалось впечатление "подвисания" программы, я процедуры архивации и распаковки засунул в отдельные подпроцессы – нити.
Я не использовал встроенные средства в Delphi для работы с нитями, а воспользовался своими любимыми API-функциями. Для создания нити используется CreateThread:
Код:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, // дескриптор защиты
SIZE_T dwStackSize,                       // начальный размер стека
LPTHREAD_START_ROUTINE lpStartAddress,    // функция потока
LPVOID lpParameter,                       // указатель переменной, что может передаваться в поток
DWORD dwCreationFlags,                    // опции создания
LPDWORD lpThreadId                        // идентификатор потока
);
Это определение я скопировал из MSDN, думаю, оно вполне понятно. Для нас важны параметры lpStartAddress, и dwCreationFlags. Параметр lpStartAddress – это указатель на функцию, которая будет выполняться в потоке. Параметр dwCreationFlags – определяет условия создания потока. Если установить сюда значение CREATE_SUSPENDED, то создается поток в состоянии ожидания и не запускается до тех пор, пока не будет вызвана функция ResumeThread. Если это значение нулевое, поток запускается немедленно после создания. В это время, никакие другие значения не поддерживаются. Функция CreateThread вовращает дескриптор потока, который мы можем использовать для различных целей (например, поменять приоритет или "прибить" поток функцией TerminateThread).

Переходим к практике.
Вот собственно, функция архивации:
Код:
function TForm1.SaveToZUP(filein,fileout:string;compression:TCompressionLevel):boolean;
var //filein,fileout - входной и выходной файлы;
    //compression - степень сжатия
  ms:TMemoryStream; //вспомогательный поток, куда будет загружен входной filein
  OutFile:TStream;  //выходной поток, сюда поток архивации записывает сжатые данные
  ZStream: TCustomZLibStream; //поток архивации (взял абстрактный)
  filename:string[255]; //имя входного файла, его запишем в заголовок архива
begin
 try
   ms:=TMemoryStream.Create;  //создание потока
   ms.LoadFromFile(filein); //грузим входной файл в поток
   OutFile:=TFileStream.Create(Fileout,fmCreate); //создадим пустой файл архива
   filename:=ExtractFileName(filein);  //выделим имя
   OutFile.Write(filename,sizeof(filename)); //запишем в заголовок архива имя
    try //создание архивирующего потока, запись данных будет идти в OutFile
      ZStream:=TCompressionStream.Create(compression,OutFile);
       try
         ZStream.CopyFrom(ms,0); //копируем весь входной файл в архивирующий поток
                                 //параллельно идет запись в OutFile
       finally
         ZStream.Free;
       end;
     finally
       ms.free;
       OutFile.Free;
       result:=true;
    end;
 except result:=false;
 end;
end;
А вот функция распаковки:
Код:
function TForm1.LoadFromZUP(filein:string):boolean; 
var
   S,fileout: string[255]; //вспомогательные для хранения имени и полного пути
   Count: Integer;
   InFile:TStream;OutFile:TMemoryStream; //входной и выходной потоки
   ZStream: TCustomZLibStream; //поток деархивации (взял абстрактный)
   Buffer: array[0..cBufferSize-1] of Byte; //временной буфер
begin
 try
   InFile:= TFileStream.Create(filein, fmOpenRead); //открыли на чтение
   InFile.Read(S,sizeof(S)); //прочитаем имя файла из заголовка архива
   try
     OutFile:= TMemoryStream.Create; //создадим выходной поток
     try
       ZStream:=TDecompressionStream.Create(InFile); //поток распаковки файла, представленного InFile
       try
         while True do
           begin
             Count:=ZStream.Read(Buffer,cBufferSize); //читаем распакованные данные в Buffer
             if Count <> 0 then OutFile.WriteBuffer(Buffer, Count) //запись в выходной поток
             else Break;
           end;
        finally ZStream.Free;
        end;
      finally
      end;
   finally
     InFile.Free;
     try
      fileout:=ExtractFilePath(filein)+S; //полный путь к разархивированному файлу
      OutFile.SaveToFile(fileout); //сохраним наш выходной поток в файл
     finally
      OutFile.Free;
     end;
  end;
 except result:=false;
 end;
 result:=true;
end;
Скрин программы, а также исходники и сама программа находятся во вложениях.
 

Вложения

  • ArchScreen.JPG
    ArchScreen.JPG
    29,5 KB · Просмотры: 1.165
  • Arch.zip
    180,8 KB · Просмотры: 900
  • ArchSrc.zip
    5,9 KB · Просмотры: 859
Сверху Снизу