Как выполнить 7zip без блокировки интерфейса InnoSetup?
Мой графический интерфейс InnoSetup зависает при распаковке.
У меня естьprocedure DoUnzip(source: String; targetdir: String)
с ядром
unzipTool := ExpandConstant('{tmp}\7za.exe');
Exec(unzipTool, ' x "' + source + '" -o"' + targetdir + '" -y',
'', SW_HIDE, ewWaitUntilTerminated, ReturnCode);
Эта процедура вызывается несколько раз иExec
Операция блокирует пользовательский интерфейс. Между исполнениями существует очень короткий момент, когда графический интерфейс Inno можно перетаскивать или перемещать.
Я знаю, что есть и другие вариантыTExecWait
вместоewWaitUntilTerminated
, лайкewNoWait
а такжеewWaitUntilIdle
, но, к сожалению, они не помогают в этом случае. С помощьюewNoWait
приведет к выполнению нескольких операций распаковки одновременно.
Я ищу способ выполнить внешнюю операцию распаковки и дождаться ее завершения, но без блокировки пользовательского интерфейса. Как я могу это реализовать?
Вот мои заметки и идеи:
Ожидание завершения процесса - это блокировка, если только вы не будете ждать в потоке, отличном от основного. Я думаю, что нужен какой-то обратный вызов, который выполняется, когда операция распаковки заканчивается.
Я знаю, что InnoSetup не предоставляет эту функцию из коробки, смотритеhttps://github.com/jrsoftware/issrc/issues/149
При поиске связанных проблем в StackOverflow я задал вопросИспользование обратного вызова для отображения имен файлов из внешней библиотеки декомпрессии (Inno Setup)где я нашелОтвет Миралса, Он использует InnoCallback в сочетании с другой DLL.
Я думаю, в моем случае это может быть7zxa.dll
для распаковки. Но он не принимает обратный вызов. Итак, следующий код является просто концепцией / идеей проекта. Одна проблема в том, что7zxa.dll
не принимает звонок Другая проблема заключается в том, что API 7zxa не очень подходит для работы.
[Code]
type
TMyCallback = procedure(Filename: PChar);
// wrapper to tell callback function to InnoCallback
function WrapMyCallback(Callback: TMyCallback; ParamCount: Integer): LongWord;
external 'WrapCallback@files:innocallback.dll stdcall';
// the call to the unzip dll
// P!: the 7zxa.dll doesn't accept a callback
procedure DoUnzipDll(Blah: Integer; Foo: String; ...; Callback: LongWord);
external 'DoUnzipDll@files:7zxa.dll stdcall';
// the actual callback action
procedure MyCallback(Filename: PChar);
begin
// refresh the GUI
end;
//-----
var Callback : LongWord;
// tell innocallback the callback procedure as 1 parameter
Callback := WrapMyCallback(@MyCallback, 1);
// pass the wrapped callback to the unzip DLL
DoUnzipDll(source, target, ..., Callback);
procedure DoUnzip(src, target : String);
begin
DoUnzipDll(ExpandConstant(src), ExpandConstant(target));
end;
Обновить
@Rik предложил объединить функцию WinAPI ShellExecuteEx () с INFINITE WaitForSingleObject.
Я реализовал и протестировал этот подход. Код ниже.
Распаковка работает, но окно InnoSetup можно перемещать / перетаскивать только на короткое время между отдельными операциями распаковки. Во время длительной разархивации графический интерфейс полностью не отвечает - не нужно перетаскивать или отменять кнопку. Я добавил BringToFrontAndRestore (), но кажется, что новый процесс имеет фокус.
const
WAIT_OBJECT_0 = $0;
WAIT_TIMEOUT = $00000102;
SEE_MASK_NOCLOSEPROCESS = $00000040;
INFINITE = $FFFFFFFF; { Infinite timeout }
type
TShellExecuteInfo = record
cbSize: DWORD;
fMask: Cardinal;
Wnd: HWND;
lpVerb: string;
lpFile: string;
lpParameters: string;
lpDirectory: string;
nShow: Integer;
hInstApp: THandle;
lpIDList: DWORD;
lpClass: string;
hkeyClass: THandle;
dwHotKey: DWORD;
hMonitor: THandle;
hProcess: THandle;
end;
function ShellExecuteEx(var lpExecInfo: TShellExecuteInfo): BOOL;
external 'ShellExecuteEx{#AW}@shell32.dll stdcall';
function WaitForSingleObject(hHandle: THandle; dwMilliseconds: DWORD): DWORD;
external '[email protected] stdcall';
function CloseHandle(hObject: THandle): BOOL; external '[email protected] stdcall';
procedure DoUnzip(source: String; targetdir: String);
var
unzipTool, unzipParams : String; // path to unzip util
ReturnCode : Integer; // errorcode
ExecInfo: TShellExecuteInfo;
begin
// source might contain {tmp} or {app} constant, so expand/resolve it to path name
source := ExpandConstant(source);
unzipTool := ExpandConstant('{tmp}\7za.exe');
unzipParams := ' x "' + source + '" -o"' + targetdir + '" -y';
ExecInfo.cbSize := SizeOf(ExecInfo);
ExecInfo.fMask := SEE_MASK_NOCLOSEPROCESS;
ExecInfo.Wnd := 0;
ExecInfo.lpFile := unzipTool;
ExecInfo.lpParameters := unzipParams;
ExecInfo.nShow := SW_HIDE;
if not FileExists(unzipTool)
then MsgBox('UnzipTool not found: ' + unzipTool, mbError, MB_OK)
else if not FileExists(source)
then MsgBox('File was not found while trying to unzip: ' + source, mbError, MB_OK)
else begin
// ShellExecuteEx combined with INFINITE WaitForSingleObject
if ShellExecuteEx(ExecInfo) then
begin
while WaitForSingleObject(ExecInfo.hProcess, INFINITE) <> WAIT_OBJECT_0
do begin
InstallPage.Surface.Update;
//BringToFrontAndRestore;
WizardForm.Refresh();
end;
CloseHandle(ExecInfo.hProcess);
end;
end;
end;