Техническое задание
Довольно часто мне приходится собирать установочные пакеты и в этом деле нет равных 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'));