Inno Setup - 使Inno Setup Installer将其安装进度状态报告给主安装程序

时间:2016-12-03 11:52:01

标签: installation progress-bar inno-setup ipc

我目前有两个Inno Setup安装程序正在解决。我需要其中一个将其作为子安装程序的状态报告给另一个安装程序,即使它使用eventDrop: function(event, delta, revertFunc) { $(this).trigger('click'); }, eventClick: function(calEvent, jsEvent, view) { console.log('event clicked'); } 命令运行。

我需要根据子安装程序的安装进度在我的主安装程序中显示进度条,因为我不想要任何无限(选取框)进度条。

我还读到了IPC Mechanism in Delphi。如何将此通信功能(如泵)添加到Inno Setup源代码中?任何启动提示?

提前致谢。

1 个答案:

答案 0 :(得分:3)

我认为您不需要为此编写花哨的IPC内容。只需通过临时文件交换信息。

儿童安装程序代码:

[Files]
Source: InnoCallback.dll; Flags: dontcopy

[Code]

type
  TTimerProc = procedure(H: LongWord; Msg: LongWord; IdEvent: LongWord; Time: LongWord);

function SetTimer(
  Wnd: LongWord; IDEvent, Elapse: LongWord; TimerFunc: LongWord): LongWord;
  external 'SetTimer@user32.dll stdcall';

function WrapTimerProc(Callback: TTimerProc; ParamCount: Integer): LongWord;
  external 'wrapcallback@files:innocallback.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, WrapTimerProc(@ReportProgressProc, 4));
  end;
end;

主安装程序代码:

#define ChildInstaller "mysetup.exe"

[Files]
Source: {#ChildInstaller}; Flags: dontcopy
Source: InnoCallback.dll; Flags: dontcopy

[Code]

type
  TTimerProc = procedure(H: LongWord; Msg: LongWord; IdEvent: LongWord; Time: LongWord);

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';

function WrapTimerProc(Callback: TTimerProc; ParamCount: Integer): LongWord;
  external 'wrapcallback@files:innocallback.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, WrapTimerProc(@UpdateProgressProc, 4));

    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的回答。

代码正在使用InnoTools InnoCallback library来安排计时器。

最好使用TFileStream代替LoadStringFromFileSaveStringToFileTFileStream支持读取共享。对于LoadStringFromFileSaveStringToFile,如果双方碰巧同时尝试读写,则进度报告可能偶尔会暂时失败。

请参阅Inno Setup LoadStringFromFile fails when file is open in another process

这显示了子安装程序和主安装程序进程的链接方式(如果子安装程序未使用/verysilent开关运行,但仅使用/silent):

Linked progress

如果您需要使用独立进度条,则可以使用以下主安装程序代码:

#define ChildInstaller "mysetup.exe"

[Files]
Source: {#ChildInstaller}; Flags: dontcopy
Source: InnoCallback.dll; Flags: dontcopy

[Code]

type
  TTimerProc = procedure(H: LongWord; Msg: LongWord; IdEvent: LongWord; Time: LongWord);

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';

function WrapTimerProc(Callback: TTimerProc; ParamCount: Integer): LongWord;
  external 'wrapcallback@files:innocallback.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, WrapTimerProc(@UpdateProgressProc, 4));

    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;