我认为您不需要为此编写花哨的 IPC 代码。只需通过临时文件交换信息。
子安装程序代码:
[Code]
function SetTimer(
Wnd: LongWord; IDEvent, Elapse: LongWord; TimerFunc: LongWord): LongWord;
external 'SetTimer@user32.dll stdcall';
var
ProgressFileName: string;
PrevProgress: Integer;
procedure ReportProgressProc(
H: LongWord; Msg: LongWord; Event: LongWord; Time: LongWord);
var
Progress: Integer;
begin
try
Progress :=
(WizardForm.ProgressGauge.Position * 100) div WizardForm.ProgressGauge.Max;
if PrevProgress <> Progress then
begin
if not SaveStringToFile(ProgressFileName, IntToStr(Progress), False) then
begin
Log(Format('Failed to save progress %d', [Progress]));
end
else
begin
Log(Format('Saved progress %d', [Progress]));
PrevProgress := Progress;
end;
end;
except
Log('Exception saving progress');
end;
end;
procedure InitializeWizard();
begin
{ When run with /progress=<path> switch, will report progress to that file }
ProgressFileName := ExpandConstant('{param:progress}');
if ProgressFileName <> '' then
begin
Log(Format('Will write progress to: %s', [ProgressFileName]));
PrevProgress := -1;
SetTimer(0, 0, 250, CreateCallback(@ReportProgressProc));
end;
end;
主安装程序代码:
#define ChildInstaller "mysetup.exe"
[Files]
Source: {#ChildInstaller}; Flags: dontcopy
[Code]
function SetTimer(
Wnd: LongWord; IDEvent, Elapse: LongWord; TimerFunc: LongWord): LongWord;
external 'SetTimer@user32.dll stdcall';
function KillTimer(hWnd: LongWord; uIDEvent: LongWord): BOOL;
external 'KillTimer@user32.dll stdcall';
var
ProgressPage: TOutputProgressWizardPage;
ProgressFileName: string;
procedure UpdateProgressProc(
H: LongWord; Msg: LongWord; Event: LongWord; Time: LongWord);
var
S: AnsiString;
Progress: Integer;
begin
try
if not LoadStringFromFile(ProgressFileName, S) then
begin
Log(Format('Failed to read progress from file %s', [ProgressFileName]));
end
else
begin
Progress := StrToIntDef(S, -1);
if (Progress < 0) or (Progress > 100) then
begin
Log(Format('Read invalid progress %s', [S]));
end
else
begin
Log(Format('Read progress %d', [Progress]));
ProgressPage.SetProgress(Progress, 100);
end;
end;
except
Log('Exception updating progress');
end;
end;
procedure InstallChild;
var
ChildInstallerPath: string;
ChildInstallerParams: string;
Timer: LongWord;
InstallError: string;
ResultCode: Integer;
begin
ExtractTemporaryFile('{#ChildInstaller}');
ProgressPage := CreateOutputProgressPage('Running child installer', '');
ProgressPage.SetProgress(0, 100);
ProgressPage.Show;
try
Timer := SetTimer(0, 0, 250, CreateCallback(@UpdateProgressProc));
ChildInstallerPath := ExpandConstant('{tmp}\{#ChildInstaller}');
ProgressFileName := ExpandConstant('{tmp}\progress.txt');
Log(Format('Expecting progress in %s', [ProgressFileName]));
ChildInstallerParams :=
Format('/verysilent /progress="%s"', [ProgressFileName]);
if not Exec(ChildInstallerPath, ChildInstallerParams, '', SW_SHOW,
ewWaitUntilTerminated, ResultCode) then
begin
InstallError := 'Cannot start child installer';
end
else
if ResultCode <> 0 then
begin
InstallError :=
Format('Child installer failed with code %d', [ResultCode]);
end;
finally
{ Clean up }
KillTimer(0, Timer);
ProgressPage.Hide;
DeleteFile(ProgressFileName);
end;
if InstallError <> '' then
begin
// RaiseException does not work properly,
// while TOutputProgressWizardPage is shown
RaiseException(InstallError);
end;
end;
您可以像下面那样使用InstallChild,也可以在安装程序的任何其他位置使用:
function NextButtonClick(CurPageID: Integer): Boolean;
begin
Result := True;
if CurPageID = wpReady then
begin
try
InstallChild;
except
MsgBox(GetExceptionMessage, mbError, MB_OK);
Result := False;
end;
end;
end;
另一个好的解决方案是使用PrepareToInstall event function。例如,请参阅我对Inno Setup torrent download implementation 的回答。
对于CreateCallback function,你需要 Inno Setup 6。如果你被 Inno Setup 5 卡住了,你可以使用 InnoTools InnoCallback 库中的 WrapCallback 函数。
最好使用TFileStream 而不是LoadStringFromFile 和SaveStringToFile。 TFileStream 支持阅读共享。使用LoadStringFromFile 和SaveStringToFile,如果双方碰巧同时尝试读写,进度报告可能偶尔会暂时失败。
见Inno Setup LoadStringFromFile fails when file is open in another process。
这显示了子安装程序和主安装程序的进度是如何链接的(如果子安装程序没有使用/verysilent 开关运行,但仅使用/silent):
如果您需要使用独立的进度条,您可以使用以下主安装程序代码:
#define ChildInstaller "mysetup.exe"
[Files]
Source: {#ChildInstaller}; Flags: dontcopy
[Code]
function SetTimer(
Wnd: LongWord; IDEvent, Elapse: LongWord; TimerFunc: LongWord): LongWord;
external 'SetTimer@user32.dll stdcall';
function KillTimer(hWnd: LongWord; uIDEvent: LongWord): BOOL;
external 'KillTimer@user32.dll stdcall';
var
ProgressFileName: string;
procedure UpdateProgressProc(
H: LongWord; Msg: LongWord; Event: LongWord; Time: LongWord);
var
S: AnsiString;
Progress: Integer;
begin
try
if not LoadStringFromFile(ProgressFileName, S) then
begin
Log(Format('Failed to read progress from file %s', [ProgressFileName]));
end
else
begin
Progress := StrToIntDef(S, -1);
if (Progress < 0) or (Progress > 100) then
begin
Log(Format('Read invalid progress %s', [S]));
end
else
begin
Log(Format('Read progress %d', [Progress]));
WizardForm.ProgressGauge.Position :=
Progress * WizardForm.ProgressGauge.Max div 100;
end;
end;
except
Log('Exception updating progress');
end;
end;
procedure InstallChild;
var
ChildInstallerPath: string;
ChildInstallerParams: string;
Timer: LongWord;
ResultCode: Integer;
begin
ExtractTemporaryFile('{#ChildInstaller}');
try
Timer := SetTimer(0, 0, 250, CreateCallback(@UpdateProgressProc));
ChildInstallerPath := ExpandConstant('{tmp}\{#ChildInstaller}');
ProgressFileName := ExpandConstant('{tmp}\progress.txt');
Log(Format('Expecting progress in %s', [ProgressFileName]));
ChildInstallerParams :=
Format('/verysilent /progress="%s"', [ProgressFileName]);
if not Exec(ChildInstallerPath, ChildInstallerParams, '', SW_SHOW,
ewWaitUntilTerminated, ResultCode) then
begin
MsgBox('Cannot start child installer', mbError, MB_OK);
end
else
if ResultCode <> 0 then
begin
MsgBox(Format(
'Child installer failed with code %d', [ResultCode]), mbError, MB_OK);
end;
finally
{ Clean up }
KillTimer(0, Timer);
DeleteFile(ProgressFileName);
end;
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssInstall then
begin
InstallChild;
end;
end;