{************************************************************************} {* *} {* file : Launch.PAS *} {* *} {* type : unit *} {* *} {* location : \QUIRT\SRC\DELPHI *} {* *} {* purpose : Launch external programs *} {* *} {* author : Lennert Ploeger (NKI / AVL) *} {* *} {* date : 19980325 *} {* *} {* portability: 32 bit delphi only (V2.0 up) *} {* *} {* notes : None *} {* *} {************************************************************************} {* Updates: When Who What 19980325 lsp Created 19980331 lsp Search for programs in PATH using SearchPath() 19980609 lsp Allow white-spaces for the program to launch 19980709 lsp Allow 'prog' to be empty in StartProgram 19980731 lsp Enclose both file and directory names in double quotes 19980901 lsp Removed some obsolete functions 19981004 mvh Added RunProgram (waits until ready) 19981005 mvh Renamed to RunProgramBlocking (waits until ready) 19981020 lsp Program launched in RunProgramBlocking() is started minimized and not given focus 19990110 mvh RunProgramBlocking returns value 19990111 lsp Fixed comment about CloseHandle() 19990112 mvh Added RunProgramWaiting, shortened code by reuse 19990425 lsp FileUtil -> QFileUtil 19990609 lsp Removed QFileUtil dependency 20000114 lsp Added show option to StartProgram 20000502 lsp+mvh Fixed argument passing for batch files 20020802 lsp More tweaking on passing command line arguments: only enclose file names when they contain spaces 20021024 lsp Implemented show=false by passing SW_HIDE instead of SW_SHOWMINNOACTIVE 20040427 mvh ELEKTA NKI-XVI0.1a RELEASE 20040608 mvh WBH NKI-XVI0.1b RELEASE (for testing only) 20041024 lsp Also pass SW_HIDE instead of SW_SHOWMINNOACTIVE when started from RunProgramBlocking() 20060521 mvh Merged 20060817 mvh ELEKTA NKI-XVI3.01 RESEARCHRELEASE 20080306 lsp Added bSilent option 20080415 mvh Return -1 status if program could not be started 20080522 mvh ELEKTA NKI-XVI4.13alpha RELEASE 20080716 lsp Corrected error handling 20081102 mvh ELEKTA NKI-XVI4.15alpha RELEASE 20081112 lsp Allow to pass extra params as part of "prog" 20081128 sr Added RunMultipleProgramsWaiting 20090114 mvh ELEKTA NKI-XVI4.22 RELEASE 20090302 sr Added bSequential option to RunMultipleProgramsWaiting 20090319 mvh ELEKTA NKI-XVI4.23 RELEASE 20090730 lsp Blocked out FileDirNameWin32FindData() 20090805 mvh+lsp ELEKTA NKI-XVI4.29 RELEASE 20091027 jn Added $mode delphi to be able to compile in Lazarus 20091029 sr Added mode option to RunProgramBlocking 20100916 jd Made compatible with Delphi XE (2011) 20101105 jn Added sleeptime parameter for runmultipleprogramswaiting, default 0 20101129 lsp Append .exe when prog is not found, prefix long paths, solve Delphi XE (20100916) problem by excluding FileCtrl 20110103 jn Merged 20110216 lsp Removed QFileUtil dependency once again (sorry Marcel): on Vista (x64) there are no programs that accept a path exceeding MAX_PATH characters, FileCtrl still needed for D5 20110324 lsp Do not add extra parameters to each file in ProgramStarter(), allow to leave out the .exe extension, added info about starting batch files. 20110325 lsp Pass nil for the first argument to CreateProcess() to be able to start batch files containing spaces, no longer call LongToShortPath() for batch files (not needed on XP and Vista), enclose all arguments containing spaces in double quotes, not only existing files or directories 20110516 lsp Removed unused TestExistence(), LongToShortFileName() and LongToShortPath() 20120302 lsp Require quotes for program file names containing spaces with implicit arguments (See 20081128) 20121127 lsp Add quote characters only when necessary in ProgramStarter() 20140710 lsp Resolved XE6 warnings *} unit Launch; {$ifndef VER130} {$WARN UNIT_PLATFORM OFF} {$WARN SYMBOL_PLATFORM OFF} {$warn unsafe_type off} {$warn unsafe_code off} {$warn unsafe_cast off} {$endif} {$IFDEF FPC} {$mode delphi} {$ENDIF} interface uses Windows; type TStringDynArray = array of string; function LocateProgramInSearchPath(ProgramName: string): string; function StartProgram(prog, workdir: string; params: array of string; show: boolean = true; const bSilent: boolean=False): boolean; function RunProgramBlocking(prog, workdir: string; params: array of string; mode: integer=SW_HIDE): integer; function RunProgramWaiting(prog, workdir: string; params: array of string): integer; function RunMultipleProgramsWaiting(prog, workdir: string; params: array of TStringDynArray; bSequential: boolean=false; sleeptime : integer=0): boolean; implementation uses System.UITypes, SysUtils, Dialogs, Forms{, Compatibility} {$IFDEF VER130} , FileCtrl {$ENDIF VER130} ; // Routine to retrieve file information using a file/dirname (*function FileDirNameWin32FindData(FullPathName: string; var Win32FindData: TWin32FindData): boolean; var Handle: THandle; begin Handle := FindFirstFile(PChar(FullPathName), Win32FindData); if Handle <> INVALID_HANDLE_VALUE then begin Windows.FindClose(Handle); Result := True; end else Result := False; end;*) // Search for 'ProgramName' in %PATH% using Win API function 'SearchPath' function LocateProgramInSearchPath(ProgramName: string): string; type BufType = array[0..MAX_PATH] of char; var SPPath: BufType; PathPtr: PChar; begin PathPtr := nil; if SearchPath(nil, PChar(ProgramName), '.exe', MAX_PATH, SPPath, PathPtr)>0 then Result := StrPas(SPPath) else Result := ProgramName; end; // Internal common code for the 3 exported program starters function ProgramStarter(prog, workdir: string; params: array of string; showmode: integer; const bSilent: boolean=False): integer; var StartInf : TStartupInfo; ProcInf : TProcessInformation; args : string; pworkdir : PChar; i : integer; begin // It appears that it is also possible to call ShellExecute, what seems // to be a bit a higher level function: // ShellExecute(handle, "open", path_to_file, NULL, NULL, SW_SHOWNORMAL); // Another alternative can be calling WinExec, but with the drawback of // not having the possibility to specify the work directory. ZeroMemory(@StartInf, sizeof(TStartupInfo)); ZeroMemory(@ProcInf, sizeof(TProcessInformation)); StartInf.cb := sizeof(TStartupInfo); StartInf.dwFlags := STARTF_USESHOWWINDOW; StartInf.wShowWindow := showmode; // prog may include implicit arguments. When the program file name contains // spaces, the program file in prog should be surrounded with quotes. When // prog contains spaces but no quotes, this function will add surrounding quotes. if (Pos(' ', prog)>0) and (Pos('"', prog)<=0) then args := '"' + prog + '"' else args := prog; for i:=low(params) to high(params) do begin // Enclose arguments containing spaces in double quotes to get them // seperated properly if Pos(' ', params[i])>0 then begin if (Length(args)>0) then // Try to add spaces only when necessary args := args + ' ' + AnsiQuotedStr(AnsiDequotedStr(params[i], '"'), '"') else args := AnsiQuotedStr(AnsiDequotedStr(params[i], '"'), '"'); end else begin if Length(args)>0 then // Try to add spaces only when necessary args := args + ' ' + params[i] else args := params[i] end end; if Length(workdir)>0 then pworkdir := PChar(workdir) else pworkdir := nil; // According to MSDN batch files need special treatment: // <q http://msdn.microsoft.com/en-us/library/ms682425%28v=vs.85%29.aspx> // To run a batch file, you must start the command interpreter; set // lpApplicationName to cmd.exe and set lpCommandLine to the following arguments: // /c plus the name of the batch file. // </q> // On Vista x64 this does not work directly. Instead of cmd.exe one should pass // the full path (e.g. c:\Windows\System32\cmd.exe), as follows from "The function // will not use the search path". // // However, it is also possible to run the batch file just like any other // executable (although pre-XP versions did not always allow spaces, which // were converted to short paths), so we will not call cmd.exe here. if not CreateProcess(nil, PChar(args), nil, nil, False, 0, nil, pworkdir, StartInf, ProcInf) then begin if not bSilent then MessageDlg('Unable to run: '#13#10'''' + args + '''', mtError, [mbOk], 0); result := 0; // ProcInf.hProcess turns out to be 0 when CreateProcess() fails end else result := ProcInf.hProcess; // The handles for both the process and the main thread must be closed through // calls to CloseHandle. The handle to ProcInf.hThread is not needed, so it is // best to close it immediately after the process is created. ProcInf.hProcess // may be needed by the caller. CloseHandle(ProcInf.hThread); end; // routine to start program 'prog' using the file-parameters in 'params'. // Be carefull with passing [] for the params, since the stackpointer seems // to get corrupt. However, starting a program with no arguments can be done // using StartProgram(program, workdir, [ ' ' ]). function StartProgram(prog, workdir: string; params: array of string; show: boolean=True; const bSilent: boolean=False): boolean; var hProcess: integer; begin if show then hProcess := ProgramStarter(prog, workdir, params, SW_SHOWDEFAULT, bSilent) else hProcess := ProgramStarter(prog, workdir, params, SW_HIDE, bSilent); CloseHandle(hProcess); Result := hProcess<>0 end; // routine to run program 'prog' using the file-parameters in 'params'. // and wait until it is finished // Be careful with passing [] for the params, since the stackpointer seems // to get corrupt. However, starting a program with no arguments can be done // using StartProgram(program, workdir, [ ' ' ]). function RunProgramBlocking(prog, workdir: string; params: array of string; mode: integer=SW_HIDE): integer; var hProcess: integer; j : Cardinal; begin hProcess := ProgramStarter(prog, workdir, params, mode { use SW_SHOWMINNOACTIVE when more feed back is desired }); if hProcess=0 then begin result := -1; // The return value of this function is the exit code of the process. exit; // Because exit codes are stored as an unsigned integer, we use -1 to end; // indicate an error. WaitForSingleObject(hProcess, INFINITE); GetExitCodeProcess(hProcess, j); result := integer(j); CloseHandle(hProcess); end; // routine to run program 'prog' using the file-parameters in 'params'. // and wait until it is finished. During the wait, however, messages are // processed so that the user interface remains 'live'. function RunProgramWaiting(prog, workdir: string; params: array of string): integer; var hProcess: integer; j : Cardinal; begin hProcess := ProgramStarter(prog, workdir, params, SW_SHOWMINNOACTIVE); if hProcess=0 then begin result := -1; // The return value of this function is the exit code of the process. exit; // Because exit codes are stored as an unsigned integer, we use -1 to end; // indicate an error. while WaitForSingleObject(hProcess, 10) = WAIT_TIMEOUT do Application.ProcessMessages; GetExitCodeProcess(hProcess, j); result := integer(j); CloseHandle(hProcess); end; // routine to run program 'prog' using the file-parameters in 'params'. // and wait until it is finished. During the wait, however, messages are // processed so that the user interface remains 'live'. function RunMultipleProgramsWaiting(prog, workdir: string; params: array of TStringDynArray; bSequential: boolean=false; sleeptime : integer=0): boolean; var hProcess: array of integer; j : Cardinal; i : integer; begin result := true; SetLength(hProcess,Length(params)); //Run all for i:=0 to Length(params)-1 do begin hProcess[i] := ProgramStarter(prog, workdir, params[i], SW_SHOWMINNOACTIVE); if sleeptime > 0 then sleep(sleeptime); if hProcess[i]=0 then begin result := false; exit; end; if bSequential then begin while WaitForSingleObject(hProcess[i], 10) = WAIT_TIMEOUT do Application.ProcessMessages; GetExitCodeProcess(hProcess[i], j); if (hProcess[i]=0) then begin CloseHandle(hProcess[i]); result := false; exit; end; CloseHandle(hProcess[i]); end; end; //Wait all if not bSequential then for i:=0 to Length(params)-1 do begin while WaitForSingleObject(hProcess[i], 10) = WAIT_TIMEOUT do Application.ProcessMessages; GetExitCodeProcess(hProcess[i], j); result := result and (hProcess[i]<>0); CloseHandle(hProcess[i]); end; end; end.
TreeComp is an example of a Delphi program using this code.
Thanks to Jimmy Harlindong for the program to generate the HTML-code out of the pascal source file (PastaHTML).