Inno Setup卸载模式调用SHFileOperation移至回收站失败求助
我在安装程序中尝试通过SHFileOperation实现将文件夹移至回收站的功能,该函数在安装模式下运行正常,但卸载模式下无法工作。Inno Setup 5和6版本分别抛出不同异常:
Inno Setup 5报错:
Runtime Error (at 2:67): Internal error: An attempt was made to access WizardForm before it has been created.
Inno Setup 6报错:
Runtime error (at 2:82): Could not call proc.
相关代码如下:
; -- Example1.iss -- ; Demonstrates copying 3 files and creating an icon. ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING .ISS SCRIPT FILES! [Setup] AppName=My Program AppVersion=1.5 WizardStyle=modern DefaultDirName={pf}\My Program DefaultGroupName=My Program UninstallDisplayIcon={app}\MyProg.exe Compression=lzma2 SolidCompression=yes [Files] Source: "MyProg.exe"; DestDir: "{app}" Source: "MyProg.chm"; DestDir: "{app}" [Icons] Name: "{group}\My Program"; Filename: "{app}\MyProg.exe" [Code] type TSHFileOpStruct = record hwnd: HWND; wFunc: UINT; pFrom: string; pTo: string; fFlags: Word; fAnyOperationsAborted: BOOL; hNameMappings: HWND; lpszProgressTitle: string; end; const FO_COPY = $0002; FO_DELETE = $0003; FOF_SILENT = $0004; FOF_NOCONFIRMATION = $0010; FOF_NOCONFIRMMKDIR = $0020; FOF_ALLOWUNDO = $0040; FOF_NOERRORUI = $0400; FOF_NO_UI = $0434; //(FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_NOCONFIRMMKDIR); function SHFileOperation(lpFileOp: TSHFileOpStruct): Integer; external 'SHFileOperationW@shell32.dll stdcall'; function MoveToTrash(path: string) : Boolean; var FileOp: TSHFileOpStruct; FromPath: string; begin FromPath := path + #0; FileOp.hwnd := WizardForm.Handle; FileOp.wFunc := FO_DELETE; FileOp.pFrom := FromPath; FileOp.fFlags := $0474; //(FOF_ALLOWUNDO | OF_SILENT); Result := SHFileOperation(FileOp) = 0; end; procedure CurStepChanged(CurStep: TSetupStep); var res : Integer; begin if CurStep=ssInstall then begin MoveToTrash('c:\9'); // WORKS FINE HERE end; end; procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); var res : Integer; begin if CurUninstallStep=usUninstall then begin MoveToTrash('c:\9'); // GETTING WEIRD ERROR HERE end; end;
问题原因与解决办法
这不是Inno Setup的Bug,你的代码存在两处关键问题:
1. 窗口句柄在卸载模式下的错误引用
卸载模式下,WizardForm并非始终存在(比如静默卸载时完全没有交互界面),直接访问WizardForm.Handle会触发Inno Setup 5中的报错。正确做法是区分安装/卸载模式,选择对应的窗口句柄;如果是静默运行,直接传入0(SHFileOperation允许hwnd参数为0)。
2. SHFileOperation的字符串格式错误
SHFileOperationW要求pFrom参数是双空终止的字符串(末尾需添加两个#0),你当前仅添加了一个,导致函数调用失败,对应Inno Setup 6中的"Could not call proc"错误。另外代码中fFlags的硬编码数值存在笔误(将FOF_SILENT写成OF_SILENT),虽然数值正确,但建议使用常量组合提升代码可读性。
修正后的代码
替换原Code段为以下内容:
[Code] type TSHFileOpStruct = record hwnd: HWND; wFunc: UINT; pFrom: string; pTo: string; fFlags: Word; fAnyOperationsAborted: BOOL; hNameMappings: HWND; lpszProgressTitle: string; end; const FO_COPY = $0002; FO_DELETE = $0003; FOF_SILENT = $0004; FOF_NOCONFIRMATION = $0010; FOF_NOCONFIRMMKDIR = $0020; FOF_ALLOWUNDO = $0040; FOF_NOERRORUI = $0400; FOF_NO_UI = $0434; //(FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_NOCONFIRMMKDIR); function SHFileOperation(lpFileOp: TSHFileOpStruct): Integer; external 'SHFileOperationW@shell32.dll stdcall'; function MoveToTrash(path: string) : Boolean; var FileOp: TSHFileOpStruct; FromPath: string; Handle: HWND; begin // 处理双空终止字符串 FromPath := path + #0#0; // 确定合适的窗口句柄 Handle := 0; if IsUninstaller then begin // 卸载模式下优先用卸载进度窗口句柄,不存在则传0 if UninstallProgressForm <> nil then Handle := UninstallProgressForm.Handle; end else begin // 安装模式下用向导窗口句柄 if WizardForm <> nil then Handle := WizardForm.Handle; end; FillChar(FileOp, SizeOf(FileOp), 0); FileOp.hwnd := Handle; FileOp.wFunc := FO_DELETE; FileOp.pFrom := FromPath; // 用常量组合替代硬编码,修正笔误 FileOp.fFlags := FOF_ALLOWUNDO or FOF_SILENT or FOF_NOERRORUI or FOF_NOCONFIRMATION; Result := SHFileOperation(FileOp) = 0; end; procedure CurStepChanged(CurStep: TSetupStep); begin if CurStep=ssInstall then begin MoveToTrash('c:\9'); end; end; procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); begin if CurUninstallStep=usUninstall then begin MoveToTrash('c:\9'); end; end;
关键修正点说明
- 增加
IsUninstaller判断,区分安装/卸载模式,选择对应窗口句柄 - 将
FromPath改为path + #0#0,满足SHFileOperationW的字符串格式要求 - 用
FillChar初始化结构体,避免未初始化的内存导致异常 - 用常量组合定义
fFlags,修正原笔误并提升代码可读性
内容的提问来源于stack exchange,提问作者Alexander Dyagilev

