July 20, 2011, 2:21 p.m.
Posted by soar

Inno Setup Archive Manager

Техническое задание

Довольно часто мне приходится собирать установочные пакеты и в этом деле нет равных InnoSetup. Я пробовал много подобных систем — NSIS, InstallShield, InstallAware — все они уступают или по качеству, или по функционалу. InstallAware наверное единственный, кто уверенно дышит в спину IS, но тонна неприятных багов данной системы (от вылетов IDE, до Runtime-проблем — так я был неприятно удивлён невозможностью копирования файлов более 2х Гб) заставили меня отказаться от её использования.

InnoSetup имеет множество плагинов, но в одном из последних пакетов передо мной встала необходимость упаковки архивов во время установки. И если модуль распаковки с использованием 7zip есть (к сожалению ссылки на сайт автора не нашёл), то упаковщиков я не нашёл. Не знаю, будет ли это интересно еще кому-нибудь, но тем не менее — встречайте, InnoSetup Archive Manager.

И, да, чуть не забыл — за реализацию API для 7z.dll огромное спасибо Henri Gourvest.

Так как возможности IS, а вернее Pascal Script в Inno Setup, весьма ограничены, местами пришлось городить огород, но в целом получилось очень даже ничего. Из-за отсутствия указателей (да, я знаю о возможности использования uint32, но данный способ вызывает у меня внутреннее беспокойство), все объекты хранятся в памяти библиотеки, поэтому использовать описанные функции необходимо именно в рекомендуемой последовательности.

Загрузка

Использование

API

SZNewArchive

procedure SZNewArchive(const archTypeName: PAnsiChar; const libDir: PAnsiChar);
external 'SZNewArchive@files:ArchMan.dll stdcall';

Данную процедуру необходимо вызывать непосредственно перед всеми остальными операциями. Здесь происходит выделение памяти под всю нашу кухню. Здесь:

  • archTypeName — тип будущего архива (7z, zip, rar). Первые два поддерживают дополнительные свойства, для rar-архивов не реализовано ничего, однако если кому-то будет нужно — допилить не проблема;
  • libDir — директория поиска библиотеки 7zip. Так как у пользователя он уже может быть установлен, рекомендуется явно передавать этот параметр. При передаче пустой строки — поиск будет произведён в PATH.

SZAddFile

procedure SZAddFile(const fileName: PAnsiChar; const filePath: PAnsiChar);
external 'SZAddFile@files:ArchMan.dll stdcall';

Данная процедура добавляет одиночный файл к будущему архиву. Здесь:

  • fileName — путь к файлу на диске (напр.: C:\somefile.dat);
  • filePath — путь и имя файла в архиве (напр.: somefolder\anotherfolder\somefile.dat).

SZAddFiles

procedure SZAddFiles(const dir: PAnsiChar; const path: PAnsiChar; const wildCard: PAnsiChar; isRecurse: Boolean);
external 'SZAddFiles@files:ArchMan.dll stdcall';

Добавление нескольких файлов к будущему архиву. Поддерживаются маски и вложенность каталогов. Здесь:

  • dir — директория-источник на диске (напр.: C:\somedir);
  • path — путь к файлам в архиве (напр.: somefolder/anotherfolder);
  • wildCard — маска добавления файлов (напр.: *);
  • isRecurse — добавлять ли файлы рекурсивно.

SZSetCompressionLevel

procedure SZSetCompressionLevel(level: Cardinal);
external 'SZSetCompressionLevel@files:ArchMan.dll stdcall';

Установка уровня сжатия. Для 7z и zip в качестве level могут выступать числа: 0, 1, 3, 5, 7, 9, где 9 — лучшее сжатие.

SZSetCompressionMethod

procedure SZSetCompressionMethod(const method: PAnsiChar);
external 'SZSetCompressionMethod@files:ArchMan.dll stdcall';

Установка метода сжатия. Здесь method может принимать следующие значения:

  • для zip: COPY, DEFLATE, DEFLATE64, BZIP2;
  • для 7z: COPY, LZMA, LZMA2, BZIP2, PPMD, DEFLATE, DEFLATE64.

SZSetProgressHandles

procedure SZSetProgressHandles(inWindowHandle: THandle; inProgressBarHandle: THandle);
external 'SZSetProgressHandles@files:ArchMan.dll stdcall';

Установка handle’оф для отображения прогресса сжатия. Если не выставлены — ход сжатия отображаться не будет. Здесь:

  • inWindowHandle — идентификатор формы, на которой расположен ProgressBar;
  • inProgressBarHandle — идентификатор индикатора прогресса.

SZSaveToFile

function SZSaveToFile(const fileName: PAnsiChar): THandle;
external 'SZSaveToFile@files:ArchMan.dll stdcall';

Запуск сжатия архива. Чтобы главная форма установщика не умирала на время сжатия — реализовано в отдельном потоке, поэтому после вызова — сразу же возвращает управление приложению. Сам InnoSetup порождать потоки не умеет, а делать это на чистом WinAPI в скрипте установщика — крутовато, поэтому пришлось пойти таким путём. Здесь:

  • fileName — путь и имя создаваемого архива.

SZIsThreadRunning

function SZIsThreadRunning(handle: THandle; timeToWait: Cardinal): Boolean;
external 'SZIsThreadRunning@files:ArchMan.dll stdcall';

Проверка, запущен ли поток сжатия архива. Возвращает истину, если поток запущен, ложь, если он был завершен или передан неправильный идентификатор. По-сути данная функция просто обертка для WaitForSingleObject. Здесь:

  • handle — идентификатор потока (его возвращает функция SZSaveToFile);
  • timeToWait — время ожидания ответа от потока в мс, рекомендуемое значение — 200.

Пример использования

function PackDirectory(dirName: string; fileName: string);
var
        ThreadHandle: THandle;
begin
        // Извлечение библиотеки 7-Zip
        if (not FileExists(ExpandConstant('{tmp}') + '7z.dll'))
                then ExtractTemporaryFile('7z.dll');

        // Создание директории для архива
        ForceDirectories(ExtractFileDir(fileName));

        // Инициализация архива
        SZNewArchive('zip', ExpandConstant('{tmp}'));
        // Добавление всех файлов в директории
        SZAddFiles(PAnsiChar(dirName), '', '*', true);
        // Установка индикатора для отображения прогресса
        SZSetProgressHandles(WizardForm.Handle, RepackProgressBar.Handle);

        // Запуск потока создания архива
        ThreadHandle := SZSaveToFile(PAnsiChar(fileName));
        // Цикл, проверяющий, завершено ли сжатие
        while (SZIsThreadRunning(ThreadHandle, 200)) do
                // Если нет - просто обрабатываем сообщения формы
                Application.ProcessMessages();
                // Закрываем хэндл потока
                CloseHandle(ThreadHandle);
        end;
...
PackDirectory(ExpandConstant('{tmp}tmpFilesDir'), ExpandConstant('{app}archive.zip'));

Comments