Inno Setup:在卸载期间从安装程序读取文件

Inno Setup: Reading a file from installer during uninstallation

在卸载过程中使用以下代码

BitmapImage := TBitmapImage.Create(InstallTopPanel);
BitmapImage.AutoSize := True;
BitmapImage.Bitmap.LoadFromFile(
  ExpandConstant( '{tmp}\WizardSmallImageFile.bmp') );
BitmapImage.Parent := InstallTopPanel;
BitmapImage.Top := (InstallTopPanel.ClientHeight - 58) / 2;
BitmapImage.Left := InstallTopPanel.ClientWidth - 55 - 10;

我得到一个错误:

Exception : can not open file. C:\users\xyz\AppData\Local\Temp\is-U3Q8P.tmp\WizardSmallImageFile.Bmp. File not found.

我还尝试在调用 LoadFromFile 之前使用 ExtractTemporaryFile,卸载期间不支持该方法。

ExtractTemporaryFile('WizardSmallImageFile.bmp');

所以,问题是如何在卸载期间查看图像或具体 WizardSmallImageFile

我上面的代码构建了一个带有自定义面板的自定义表单。喜欢这里:.

正确,ExtractTemporaryFile 从安装程序中提取文件。因此它无法在卸载程序中工作,因为安装程序不再可用。

另请注意,无论如何您都无法从安装程序中提取 WizardSmallImageFile directive 引用的文件。您必须添加自己的副本。


如果在卸载过程中需要使用某些文件,则必须在安装程序中安装它,然后在卸载程序中使用安装的副本。

[Files]
Source: "WizardSmallImageFile.bmp"; DestDir: "{app}";

[Code]

function InitializeUninstall(): Boolean;
begin
  ...
  BitmapImage := TBitmapImage.Create(...);
  ...
  BitmapImage.Bitmap.LoadFromFile(
    ExpandConstant('{app}\WizardSmallImageFile.bmp'));
  ...
end;

如果不想安装文件,可以将图像数据嵌入到代码中。

不幸的是,Unicode Inno Setup 在处理二进制数据时非常有限,因为它倾向于尝试将所有内容转换为 UTF-8。但经过多次尝试,我最终得到了一些工作代码。

请注意,该代码使用从 Inno Setup preprocessor 调用的 PowerShell 代码 – PowerShell 仅在编译时需要,而不是 run/install-time。

将此代码添加到 [Code] 部分前面的某处:

function CryptStringToBinary(
  sz: string; cch: LongWord; flags: LongWord; binary: string; var size: LongWord;
  skip: LongWord; flagsused: LongWord): Integer;
  external 'CryptStringToBinaryW@crypt32.dll stdcall';

const
  CRYPT_STRING_HEX = ;

procedure WriteBinaryStringToStream(S: string; Stream: TStream);
var
  Buffer: string;
  Size: LongWord;
  Len: Integer;
begin
  Len := Length(S);
  SetLength(Buffer, (Len div 4) + 1);
  Size := Len div 2;
  if (CryptStringToBinary(S, Len, CRYPT_STRING_HEX, Buffer, Size, 0, 0) = 0) or
     (Size <> Len div 2) then
  begin
    RaiseException('Error decoding binary string');
  end;
  
  Stream.WriteBuffer(Buffer, Size);
end;  

function StreamFromBinaryString(S: string): TStream;
begin
  Result := TStringStream.Create('');
  WriteBinaryStringToStream(S, Result);
  Result.Position := 0;
end;

procedure LoadBitmapFromBinaryString(Bitmap: TBitmap; S: string);
var
  Stream: TStream;
begin
  Stream := StreamFromBinaryString(S);
  try
    Bitmap.LoadFromStream(Stream);
  finally
    Stream.Free;
  end;
end;

procedure SaveBinaryStringToFile(FileName: string; S: string);
var
  Stream: TStream;
begin
  Stream := TFileStream.Create(FileName, fmCreate);
  try
    WriteBinaryStringToStream(S, Stream);
  finally
    Stream.Free;
  end;
end;

#define FileToBinaryString(str FileName) \
  Local[4] = ExtractFileName(FileName), \
  Local[0] = AddBackslash(GetEnv("TEMP")) + Local[4] + ".pas", \
  Local[1] = \
    "-ExecutionPolicy Bypass -Command """ + \
    "Write-Host 'Generating code for " + Local[4] + "'; " + \
    "$bytes = [System.IO.File]::ReadAllBytes('" + FileName + "'); " + \
    "$s = '''' + (($bytes | foreach { $_.ToString('X2') }) -join '') + ''''; " + \
    "Set-Content -Path '" + Local[0] + "' -Value $s;" + \
    """", \
  Exec("powershell.exe", Local[1], SourcePath, , SW_HIDE), \
  Local[2] = FileOpen(Local[0]), \
  Local[3] = FileRead(Local[2]), \
  FileClose(Local[2]), \
  DeleteFileNow(Local[0]), \
  Local[3]

然后您可以使用 FileToBinaryString preprocessor macro 在编译时(或更准确地说,在预处理时)将文件转换为十六进制字符串,例如:

'4D5A50000200000004000F00FFFF0000B800000....'

在运行时,您将十六进制字符串与某些函数一起使用 WriteBinaryStringToStreamStreamFromBinaryStringLoadBitmapFromBinaryStringSaveBinaryStringToFile.

在您的情况下,您将使用:

LoadBitmapFromBinaryString(
  BitmapImage.Bitmap, {#FileToBinaryString("C:\path\WizModernSmallImage.bmp")});

在编译时,这会转换为如下代码:

LoadBitmapFromBinaryString(
  BitmapImage.Bitmap, '4D5A50000200000004000F00FFFF0000B800000....');

pre-processor/Pascal 编译器对一个字符串有大约 100M 个字符的限制。对于大于 20-30 MB 的文件,您实际上首先会遇到 PowerShell 脚本的 [编译时] 内存限制。尽管即使对于较小的大小(大于几 MB),PowerShell 脚本的编译时性能也很差。不过脚本可以显着优化。

由于十六进制编码,安装程序的大小增加了两倍。这可以通过使用一些更有效的编码来改进,例如 Base64 (CRYPT_STRING_BASE64)。与 [Files] 部分包含的文件相比,代码部分甚至没有被压缩(对于图像来说不是问题,因为这些已经被压缩,但与 DLL 等不同)。

代码需要 Inno Setup 的 Unicode 版本。无论如何,在 21 世纪,您不应该使用 Ansi 版本。具有讽刺意味的是,在 Ansi 版本中实现它会更容易。请参阅 了解与 Ansi 和 Unicode 版本的 Inno Setup 兼容的 CryptStringToBinary 的使用。尽管在 Ansi 版本中,您实际上可以使用二进制字符串来代替十六进制字符串。