-
-
Save reikjarloekl/d90a72177cab69ab4aa6b11edc5b21a1 to your computer and use it in GitHub Desktop.
Helper for executing executables without blocking the InnoSetup GUI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Based on code written by Rik and Jens A. Koch (@jakoch) on StackOverflow: | |
// http://stackoverflow.com/questions/32256432/how-to-execute-7zip-without-blocking-the-innosetup-ui | |
[Code] | |
#IFDEF UNICODE | |
#DEFINE AW "W" | |
#ELSE | |
#DEFINE AW "A" | |
#ENDIF | |
// --- Start "ShellExecuteEx" Helper | |
const | |
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; | |
TTickCallBack = procedure; | |
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'; | |
function GetExitCodeProcess(Process: THandle; var ExitCode: Cardinal): Boolean; | |
external '[email protected] stdcall'; | |
// --- End "ShellExecuteEx" Helper | |
// --- Start "Application.ProcessMessage" Helper | |
{ | |
InnoSetup does not provide Application.ProcessMessage(). | |
This is "generic" code to recreate a "Application.ProcessMessages"-ish procedure, | |
using the WinAPI function PeekMessage(), TranslateMessage() and DispatchMessage(). | |
} | |
type | |
TMsg = record | |
hwnd: HWND; | |
message: UINT; | |
wParam: Longint; | |
lParam: Longint; | |
time: DWORD; | |
pt: TPoint; | |
end; | |
const | |
PM_REMOVE = 1; | |
function PeekMessage(var lpMsg: TMsg; hWnd: HWND; wMsgFilterMin, wMsgFilterMax, wRemoveMsg: UINT): BOOL; external '[email protected] stdcall'; | |
function TranslateMessage(const lpMsg: TMsg): BOOL; external '[email protected] stdcall'; | |
function DispatchMessage(const lpMsg: TMsg): Longint; external '[email protected] stdcall'; | |
procedure AppProcessMessage; | |
var | |
Msg: TMsg; | |
begin | |
while PeekMessage(Msg, WizardForm.Handle, 0, 0, PM_REMOVE) do begin | |
TranslateMessage(Msg); | |
DispatchMessage(Msg); | |
end; | |
end; | |
// --- End "Application.ProcessMessage" Helper | |
function ExecNonBlocking(executable: String; parameters: String; tickCallBack: TTickCallBack): Cardinal; | |
var | |
ExecInfo: TShellExecuteInfo; // info object for ShellExecuteEx() | |
ExitCode: Cardinal; | |
begin | |
Result := 1; | |
// executable and parameters might contain {tmp} or {app} constant, so expand/resolve it to path names | |
executable := ExpandConstant(executable); | |
parameters := ExpandConstant(parameters); | |
// prepare information about the application being executed by ShellExecuteEx() | |
ExecInfo.cbSize := SizeOf(ExecInfo); | |
ExecInfo.fMask := SEE_MASK_NOCLOSEPROCESS; | |
ExecInfo.Wnd := 0; | |
ExecInfo.lpFile := executable; | |
ExecInfo.lpParameters := parameters; | |
ExecInfo.nShow := SW_HIDE; | |
{ | |
The executable is run via ShellExecuteEx() | |
Then the installer uses a while loop with the condition | |
WaitForSingleObject and a very minimal timeout | |
to execute AppProcessMessage. | |
AppProcessMessage is itself a helper function, because | |
Innosetup does not provide Application.ProcessMessages(). | |
Its job is to be the message pump to the InnoSetup GUI. | |
This makes the window responsive/dragable again, | |
while the extraction is done in the background. | |
} | |
if ShellExecuteEx(ExecInfo) then | |
begin | |
while WaitForSingleObject(ExecInfo.hProcess, 100) = WAIT_TIMEOUT | |
do begin | |
if (tickCallBack <> nil) then tickCallBack(); | |
AppProcessMessage; | |
WizardForm.Refresh(); | |
end; | |
GetExitCodeProcess( ExecInfo.hProcess , ExitCode); | |
Result := ExitCode; | |
CloseHandle(ExecInfo.hProcess); | |
end; | |
end; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Compared to the original gist, this returns the exit code and calls an (optional) calback while waiting for the external program to finish.