How to create a Child process depending on it's Parent? - delphi

My application (main.exe) is executing a Child process (child.exe) using ShellExecuteEx.
But when I close or kill (via Process-Explorer) main.exe the child process remains active.
How to gracefully handle that, when main.exe terminates child.exe terminates also?

You need to use jobs. Main executable should create a job object, then you'll need to set JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE flag to your job object.
uses
JobsApi;
//...
var
jLimit: TJobObjectExtendedLimitInformation;
hJob := CreateJobObject(nil, PChar('JobName');
if hJob <> 0 then
begin
jLimit.BasicLimitInformation.LimitFlags := JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
SetInformationJobObject(hJob, JobObjectExtendedLimitInformation, #jLimit,
SizeOf(TJobObjectExtendedLimitInformation));
end;
Then you need to execute another process with CreateProcess function where dwCreationFlags must be set to CREATE_BREAKAWAY_FROM_JOB. If this function succeeds call AssignProcessToJobObject.
function ExecuteProcess(const EXE : String; const AParams: string = ''; AJob: Boolean = True): THandle;
var
SI : TStartupInfo;
PI : TProcessInformation;
AFlag: Cardinal;
begin
Result := INVALID_HANDLE_VALUE;
FillChar(SI,SizeOf(SI),0);
SI.cb := SizeOf(SI);
if AJob then
AFlag := CREATE_BREAKAWAY_FROM_JOB
else
AFlag := 0;
if CreateProcess(
nil,
PChar(EXE + ' ' + AParams),
nil,
nil,
False,
AFlag,
nil,
nil,
SI,
PI
) then
begin
{ close thread handle }
CloseHandle(PI.hThread);
Result := PI.hProcess;
end;
end;
//...
hApp := ExecuteProcess('PathToExecutable');
if hApp <> INVALID_HANDLE_VALUE then
begin
AssignProcessToJobObject(hJob, hApp);
end;
When all of this done all the child processes will be automatically terminated even if the main executable has been killed. You can get the JobsApi unit here. Note: I've not tested it with Delphi 7.
EDIT: Here you can download working demo project.

Try using Job Objects , check these functions CreateJobObject and AssignProcessToJobObject.
A job object allows groups of processes to be managed as a unit. Job
objects are namable, securable, shareable objects that control
attributes of the processes associated with them. Operations performed
on a job object affect all processes associated with the job object.
Examples include enforcing limits such as working set size and process
priority or terminating all processes associated with a job.

I think, it's very cool code. It's working for me, but I add some changes to be able user to set show window flags for child processes like SW_SHOW/SW_HIDE.
...
function ExecuteProcess(const EXE : String; const AParams: string = '';
const nCmdShow: Integer = SW_SHOW; AJob: Boolean = True): THandle;
var
SI : TStartupInfo;
PI : TProcessInformation;
AFlag: Cardinal;
begin
Result := INVALID_HANDLE_VALUE;
FillChar(SI,SizeOf(SI),0);
SI.cb := SizeOf(SI);
SI.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
SI.wShowWindow := nCmdShow;
if AJob then
AFlag := CREATE_BREAKAWAY_FROM_JOB
else
AFlag := 0;
...

Related

How to block VCL thread when running a shellexecute-like function

Apologies for improper terminology when referencing Delphi's VCL/main thread structure (if anyone has any resources to learn more about this I would appreciate it).
Basically, I have a VCL application where on a button click event, I want the user to be unable to interact with the initial VCL application that spawns the external exe.
I have a function called ExecuteExternalProcess that when passed the proper parameters, will not allow the next line(s) of code to execute until the external application has returned a value. This works well in other applications, but not so much when we are spawning the external exe from a VCL event.
Here is the Button Click Event that spawns the external process
procedure TMainForm.ButtonBtnClick(Sender: TObject);
var
error: Integer;
begin
ExecuteExternalProcess('test.exe', '', '', True, false, false, error);
showmessage('done');
end;
So this works, it doesn't display the 'done' message until the test.exe has finished executing. To reiterate, the issue is that while test.exe is running I can interact with the initial VCL application and do basically anything. I would like for the initial VCL application to stop completely and be inoperable until test.exe has finished executing.
Here is the code that spawns the exe if its any help(I am not the author I got it from here):
function ExecuteExternalProcess(const FileName, Params: string; Folder: string; WaitUntilTerminated, WaitUntilIdle, RunMinimized: boolean;
var ErrorCode: integer): boolean;
var
CmdLine: string;
WorkingDirP: PChar;
StartupInfo: TStartupInfo;
ProcessInfo: TProcessInformation;
begin
Result := true;
CmdLine := '"' + FileName + '" ' + Params;
//if Folder = '' then Folder := ExcludeTrailingPathDelimiter(ExtractFilePath(FileName));
ZeroMemory(#StartupInfo, SizeOf(StartupInfo));
StartupInfo.cb := SizeOf(StartupInfo);
if RunMinimized then
begin
StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
StartupInfo.wShowWindow := SW_SHOWMINIMIZED;
end;
if Folder <> '' then WorkingDirP := PChar(Folder)
else WorkingDirP := nil;
if not CreateProcess(nil, PChar(CmdLine), nil, nil, false, 0, nil, WorkingDirP, StartupInfo, ProcessInfo) then
begin
Result := false;
ErrorCode := GetLastError;
exit;
end;
with ProcessInfo do
begin
CloseHandle(hThread);
if WaitUntilIdle then WaitForInputIdle(hProcess, INFINITE);
if WaitUntilTerminated then
repeat
Application.ProcessMessages;
until MsgWaitForMultipleObjects(1, hProcess, false, INFINITE, QS_ALLINPUT) <> WAIT_OBJECT_0 + 1;
CloseHandle(hProcess);
end;
end;
Why not just disable the tmainform.

Suspend/resume processes as PsSuspend does

I hope this post is not a duplicate one. Let me explain:
I have considered the similar post How to pause / resume any external process under Windows? but with C++/Python preference and yet without an accepted answer as of the time of posting.
My Question:
I'm interested in a possible implementation in Delphi of the functionality provided by PsSuspend by Mark Russinovich of Windows Sysinternals.
Quotes:
PsSuspend lets you suspend processes on the local or a remote system,
which is desirable in cases where a process is consuming a resource
(e.g. network, CPU or disk) that you want to allow different processes
to use. Rather than kill the process that's consuming the resource,
suspending permits you to let it continue operation at some later
point in time.
Thank you.
Edit:
A partial implementation will do. Remote capability can be dropped.
You can try to use the following code. It uses the undocumented functions NtSuspendProcess and NtResumeProcess. I've tried it on Windows 7 64-bit from the 32-bit application built in Delphi 2009 and it works for me. Note that these functions are undocumented thus can be removed from future versions of Windows.
Update
The SuspendProcess and ResumeProcess wrappers from the following code are now functions and returns True if succeed, False otherwise.
type
NTSTATUS = LongInt;
TProcFunction = function(ProcHandle: THandle): NTSTATUS; stdcall;
const
STATUS_SUCCESS = $00000000;
PROCESS_SUSPEND_RESUME = $0800;
function SuspendProcess(const PID: DWORD): Boolean;
var
LibHandle: THandle;
ProcHandle: THandle;
NtSuspendProcess: TProcFunction;
begin
Result := False;
LibHandle := SafeLoadLibrary('ntdll.dll');
if LibHandle <> 0 then
try
#NtSuspendProcess := GetProcAddress(LibHandle, 'NtSuspendProcess');
if #NtSuspendProcess <> nil then
begin
ProcHandle := OpenProcess(PROCESS_SUSPEND_RESUME, False, PID);
if ProcHandle <> 0 then
try
Result := NtSuspendProcess(ProcHandle) = STATUS_SUCCESS;
finally
CloseHandle(ProcHandle);
end;
end;
finally
FreeLibrary(LibHandle);
end;
end;
function ResumeProcess(const PID: DWORD): Boolean;
var
LibHandle: THandle;
ProcHandle: THandle;
NtResumeProcess: TProcFunction;
begin
Result := False;
LibHandle := SafeLoadLibrary('ntdll.dll');
if LibHandle <> 0 then
try
#NtResumeProcess := GetProcAddress(LibHandle, 'NtResumeProcess');
if #NtResumeProcess <> nil then
begin
ProcHandle := OpenProcess(PROCESS_SUSPEND_RESUME, False, PID);
if ProcHandle <> 0 then
try
Result := NtResumeProcess(ProcHandle) = STATUS_SUCCESS;
finally
CloseHandle(ProcHandle);
end;
end;
finally
FreeLibrary(LibHandle);
end;
end;
There is no SuspendProcess API call in Windows. So what you need to do is:
Enumerate all the threads in the process. See RRUZ's answer for sample code.
Call SuspendThread for each of these threads.
In order to implement the resume part of the program, call ResumeThread for each thread.
There is a race condition for the "suspend all threads" implementation - what happens if the program you are trying to suspend creates one or more threads between the time that you create the snapshot and the time that you complete suspending?
You could loop, getting another snapshot and suspending any unsuspending threads, exiting only when you found none.
The undocumented function avoids this issue.
I just found the following snippets here (Author: steve10120).
I think they are valuables and I can't help posting them also as an alternative answer to my own question.
Resume Process:
function ResumeProcess(ProcessID: DWORD): Boolean;
var
Snapshot,cThr: DWORD;
ThrHandle: THandle;
Thread:TThreadEntry32;
begin
Result := False;
cThr := GetCurrentThreadId;
Snapshot := CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if Snapshot <> INVALID_HANDLE_VALUE then
begin
Thread.dwSize := SizeOf(TThreadEntry32);
if Thread32First(Snapshot, Thread) then
repeat
if (Thread.th32ThreadID <> cThr) and (Thread.th32OwnerProcessID = ProcessID) then
begin
ThrHandle := OpenThread(THREAD_ALL_ACCESS, false, Thread.th32ThreadID);
if ThrHandle = 0 then Exit;
ResumeThread(ThrHandle);
CloseHandle(ThrHandle);
end;
until not Thread32Next(Snapshot, Thread);
Result := CloseHandle(Snapshot);
end;
end;
Suspend Process:
function SuspendProcess(PID:DWORD):Boolean;
var
hSnap: THandle;
THR32: THREADENTRY32;
hOpen: THandle;
begin
Result := FALSE;
hSnap := CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if hSnap <> INVALID_HANDLE_VALUE then
begin
THR32.dwSize := SizeOf(THR32);
Thread32First(hSnap, THR32);
repeat
if THR32.th32OwnerProcessID = PID then
begin
hOpen := OpenThread($0002, FALSE, THR32.th32ThreadID);
if hOpen <> INVALID_HANDLE_VALUE then
begin
Result := TRUE;
SuspendThread(hOpen);
CloseHandle(hOpen);
end;
end;
until Thread32Next(hSnap, THR32) = FALSE;
CloseHandle(hSnap);
end;
end;
Disclaimer:
I didn't test them at all. Please enjoy and don't forget to feedback.

Getting output from a shell/dos app into a Delphi app

I have a commandline application coded in delphi that I need to call from a normal desktop application (also coded in delphi). In short, I want to call the commandline app and display the text it outputs "live" in a listbox.
It's been ages since I have played around with the shell, but I distinctly remember that in order to grab the text from a commandline app - I have to use the pipe symbol ">". Like this:
C:/mycmdapp.exe >c:/result.txt
This will take any text printed to the shell (using writeLn) and dump it to a textfile called "result.txt".
But.. (and here comes the pickle), I want a live result rather than a backlog file. A typical example is the Delphi compiler itself - which manages to report back to the IDE what is going on. If my memory serves me correctly, I seem to recall that I must create a "pipe" channel (?), and then assign the pipe-name to the shell call.
I have tried to google this but I honestly was unsure of how to formulate it. Hopefully someone from the community can point me in the right direction.
Updated: This question might be identical to How do I run a command-line program in Delphi?. Some of the answers fit what I'm looking for, although the title and question itself is not identical.
As ever so often Zarco Gajic has a solution: Capture the output from a DOS (command/console) Window. This is a copy from his article for future reference:
The example runs 'chkdsk.exe c:\' and displays the output to Memo1.
Put a TMemo (Memo1) and a TButton (Button1) on your form. Put this code in the OnCLick event procedure for Button1:
procedure RunDosInMemo(DosApp: string; AMemo:TMemo);
const
READ_BUFFER_SIZE = 2400;
var
Security: TSecurityAttributes;
readableEndOfPipe, writeableEndOfPipe: THandle;
start: TStartUpInfo;
ProcessInfo: TProcessInformation;
Buffer: PAnsiChar;
BytesRead: DWORD;
AppRunning: DWORD;
begin
Security.nLength := SizeOf(TSecurityAttributes);
Security.bInheritHandle := True;
Security.lpSecurityDescriptor := nil;
if CreatePipe({var}readableEndOfPipe, {var}writeableEndOfPipe, #Security, 0) then
begin
Buffer := AllocMem(READ_BUFFER_SIZE+1);
FillChar(Start, Sizeof(Start), #0);
start.cb := SizeOf(start);
// Set up members of the STARTUPINFO structure.
// This structure specifies the STDIN and STDOUT handles for redirection.
// - Redirect the output and error to the writeable end of our pipe.
// - We must still supply a valid StdInput handle (because we used STARTF_USESTDHANDLES to swear that all three handles will be valid)
start.dwFlags := start.dwFlags or STARTF_USESTDHANDLES;
start.hStdInput := GetStdHandle(STD_INPUT_HANDLE); //we're not redirecting stdInput; but we still have to give it a valid handle
start.hStdOutput := writeableEndOfPipe; //we give the writeable end of the pipe to the child process; we read from the readable end
start.hStdError := writeableEndOfPipe;
//We can also choose to say that the wShowWindow member contains a value.
//In our case we want to force the console window to be hidden.
start.dwFlags := start.dwFlags + STARTF_USESHOWWINDOW;
start.wShowWindow := SW_HIDE;
// Don't forget to set up members of the PROCESS_INFORMATION structure.
ProcessInfo := Default(TProcessInformation);
//WARNING: The unicode version of CreateProcess (CreateProcessW) can modify the command-line "DosApp" string.
//Therefore "DosApp" cannot be a pointer to read-only memory, or an ACCESS_VIOLATION will occur.
//We can ensure it's not read-only with the RTL function: UniqueString
UniqueString({var}DosApp);
if CreateProcess(nil, PChar(DosApp), nil, nil, True, NORMAL_PRIORITY_CLASS, nil, nil, start, {var}ProcessInfo) then
begin
//Wait for the application to terminate, as it writes it's output to the pipe.
//WARNING: If the console app outputs more than 2400 bytes (ReadBuffer),
//it will block on writing to the pipe and *never* close.
repeat
Apprunning := WaitForSingleObject(ProcessInfo.hProcess, 100);
Application.ProcessMessages;
until (Apprunning <> WAIT_TIMEOUT);
//Read the contents of the pipe out of the readable end
//WARNING: if the console app never writes anything to the StdOutput, then ReadFile will block and never return
repeat
BytesRead := 0;
ReadFile(readableEndOfPipe, Buffer[0], READ_BUFFER_SIZE, {var}BytesRead, nil);
Buffer[BytesRead]:= #0;
OemToAnsi(Buffer,Buffer);
AMemo.Text := AMemo.text + String(Buffer);
until (BytesRead < READ_BUFFER_SIZE);
end;
FreeMem(Buffer);
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
CloseHandle(readableEndOfPipe);
CloseHandle(writeableEndOfPipe);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin {button 1 code}
RunDosInMemo('chkdsk.exe c:\',Memo1);
end;
Update:
The above example reads the output in one step. Here is another example from DelphiDabbler showing how the output can be read while the process is still running:
function GetDosOutput(CommandLine: string; Work: string = 'C:\'): string;
var
SA: TSecurityAttributes;
SI: TStartupInfo;
PI: TProcessInformation;
StdOutPipeRead, StdOutPipeWrite: THandle;
WasOK: Boolean;
Buffer: array[0..255] of AnsiChar;
BytesRead: Cardinal;
WorkDir: string;
Handle: Boolean;
begin
Result := '';
with SA do begin
nLength := SizeOf(SA);
bInheritHandle := True;
lpSecurityDescriptor := nil;
end;
CreatePipe(StdOutPipeRead, StdOutPipeWrite, #SA, 0);
try
with SI do
begin
FillChar(SI, SizeOf(SI), 0);
cb := SizeOf(SI);
dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
wShowWindow := SW_HIDE;
hStdInput := GetStdHandle(STD_INPUT_HANDLE); // don't redirect stdin
hStdOutput := StdOutPipeWrite;
hStdError := StdOutPipeWrite;
end;
WorkDir := Work;
Handle := CreateProcess(nil, PChar('cmd.exe /C ' + CommandLine),
nil, nil, True, 0, nil,
PChar(WorkDir), SI, PI);
CloseHandle(StdOutPipeWrite);
if Handle then
try
repeat
WasOK := ReadFile(StdOutPipeRead, Buffer, 255, BytesRead, nil);
if BytesRead > 0 then
begin
Buffer[BytesRead] := #0;
Result := Result + Buffer;
end;
until not WasOK or (BytesRead = 0);
WaitForSingleObject(PI.hProcess, INFINITE);
finally
CloseHandle(PI.hThread);
CloseHandle(PI.hProcess);
end;
finally
CloseHandle(StdOutPipeRead);
end;
end;
You probably have the code on your harddisk already: the Execute function in the JclSysUtils unit of the JCL (JEDI Code Library) does what you need:
function Execute(const CommandLine: string; OutputLineCallback: TTextHandler;
RawOutput: Boolean = False; AbortPtr: PBoolean = nil): Cardinal;
You can supply it with a callback procedure:
TTextHandler = procedure(const Text: string) of object;
Did an answer too for better understanding:
{type TTextHandler =} procedure TTextHandlerQ(const aText: string);
begin
memo2.lines.add(atext);
end;
writeln(itoa(JExecute('cmd /C dir *.*',#TTextHandlerQ, true, false)));
You have to use /C then cmd /c is used to run commands in MS-DOS and terminate after command or process completion, otherwise it blocks output to memo.

How to find the name of the parent program that started us?

We want a program of ours in D7 to know if it was run via a ShellExecute command from one of our apps, or directly started by the user.
Is there a reliable way for a Delphi 7 program to determine the name of the program that ran it?
We of course could have our parent program use a command line argument or other flag, but we'd prefer the above approach.
TIA
There's no way to do what you want, I'm afraid. The application isn't told whether it's being run pro grammatically via ShellExecute (or CreateProcess), via a command line, a shortcut, or a double-click in Explorer.
Raymond Chen did an article a while back on this very topic, if I remember correctly; I'll see if I can find it and update my answer here.
Based on another answer and some code on Torry.net, I came to this function to get the parent process id. It seems to return a relevant number on Windows 7, and the windows functions it uses should be available at least since Win 2000.
uses Tlhelp32;
function GetProcessInfo(ProcessId: Cardinal; out ParentProcessId: Cardinal; out ExeFileName: string): Boolean;
var
hSnapShot: THandle;
ProcInfo: TProcessEntry32;
begin
hSnapShot := CreateToolHelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapShot <> THandle(-1)) then
try
ProcInfo.dwSize := SizeOf(ProcInfo);
if (Process32First(hSnapshot, ProcInfo)) then
repeat
if ProcInfo.th32ProcessID = ProcessId then
begin
ExeFileName := string(ProcInfo.szExeFile);
ParentProcessId := ProcInfo.th32ParentProcessID;
Result := True;
Exit;
end;
until not Process32Next(hSnapShot, ProcInfo);
finally
CloseHandle(hSnapShot);
end;
Result := False;
end;
procedure Test;
var
ProcessId, ParentProcessId, Dummy: Cardinal;
FileName: string;
begin
ProcessId := GetCurrentProcessId();
// Get info for current process
if GetProcessInfo(ProcessId, ParentProcessId, FileName) then
// Get info for parent process
if GetProcessInfo(ParentProcessId, Dummy, FileName) then
// Show it.
ShowMessage(IntToStr(ParentProcessId) + FileName);
end;
A word of caution! The parent process may no longer exist. Even worse, it's ID may have been recycled, causing this function to give you a different process than you asked for.
The simple answer is "No".
A more complex answer is "Not as easily as simply passing a command line param would be".
:)
What you need to do is identify the parent process of your process. Obtaining this is possible but not straightforward. Details of how to go about it can be obtained in this CodeProject article.
The biggest problem is that there is not strict hierarchical relationship between processes in Windows and PID (Process ID's) may be re-used. The PID you identify as your "parent" may not be your parent at all. If the parent process has subsequently terminated then it's PID may be re-used which could lead to some seemingly perplexing results ("My process was started by calc.exe? How is that possible?").
Trying to find bullet, water and idiot proof mechanisms to protect against the possible ways such a process might fail will be significantly more effort than simply devising and implementing a command line based convention between your launcher applications and the launchee by which the latter may identify the former.
A command line parameter is one such option but could be "spoofed" (if someone figures out what you are passing on the command line and for some reason could derive some value or benefit from mimicking this themselves).
Depending on how reliable and tamper proof you need the mechanism to be, this could still be enough however.
I've found getpids which does it using NtQueryInformationProcess to not only to obtain the parent process ID but also compare the process creation times - if the reported parent process was created after the child it means the reported parent ID has already been recycled.
Here is my Delphi unit I wrote to test it:
unit ProcInfo;
interface
uses
Windows, SysUtils;
function GetParentProcessId(ProcessID: DWORD; out ProcessImageFileName: string): DWORD; overload;
implementation
uses
PsApi;
var
hNtDll: THandle;
NtQueryInformationProcess: function(ProcessHandle: THandle; ProcessInformationClass: DWORD;
ProcessInformation: Pointer; ProcessInformationLength: ULONG; ReturnLength: PULONG): DWORD; stdcall;
const
UnicodeStringBufferLength = 1025;
type
PPEB = Pointer; // PEB from winternl.h not needed here
PPROCESS_BASIC_INFORMATION = ^PROCESS_BASIC_INFORMATION;
PROCESS_BASIC_INFORMATION = record
Reserved1: Pointer; // exit status
PebBaseAddress: PPEB;
Reserved2: array[0..1] of Pointer; // affinity mask, base priority
UniqueProcessId: ULONG_PTR;
Reserved3: Pointer; // parent process ID
end;
PProcessBasicInformation = ^TProcessBasicInformation;
TProcessBasicInformation = PROCESS_BASIC_INFORMATION;
PKernelUserTimes = ^TKernelUserTimes;
TKernelUserTimes = record
CreateTime: LONGLONG;
ExitTime: LONGLONG;
KernelTime: LONGLONG;
UserTime: LONGLONG;
end;
PUNICODE_STRING = ^UNICODE_STRING;
UNICODE_STRING = record
Length: USHORT;
MaximumLength: USHORT;
PBuffer: PChar;
Buffer: array[0..UnicodeStringBufferLength - 1] of Char;
end;
PUnicodeString = ^TUnicodeString;
TUnicodeString = UNICODE_STRING;
function GetProcessCreateTime(hProcess: THandle): LONGLONG;
var
ProcessTimes: TKernelUserTimes;
begin
Result := 0;
FillChar(ProcessTimes, SizeOf(ProcessTimes), 0);
if NtQueryInformationProcess(hProcess, 4, #ProcessTimes, SizeOf(ProcessTimes), nil) <> 0 then
Exit;
Result := ProcessTimes.CreateTime;
end;
function GetProcessParentId(hProcess: THandle): DWORD;
var
ProcessInfo: TProcessBasicInformation;
begin
Result := 0;
FillChar(ProcessInfo, SizeOf(ProcessInfo), 0);
if NtQueryInformationProcess(hProcess, 0, #ProcessInfo, SizeOf(ProcessInfo), nil) <> 0 then
Exit;
Result := DWORD(ProcessInfo.Reserved3);
end;
function GetProcessImageFileName(hProcess: THandle): string;
var
ImageFileName: TUnicodeString;
begin
Result := '';
FillChar(ImageFileName, SizeOf(ImageFileName), 0);
ImageFileName.Length := 0;
ImageFileName.MaximumLength := UnicodeStringBufferLength * SizeOf(Char);
ImageFileName.PBuffer := #ImageFileName.Buffer[0];
if NtQueryInformationProcess(hProcess, 27, #ImageFileName, SizeOf(ImageFileName), nil) <> 0 then
Exit;
SetString(Result, ImageFileName.PBuffer, ImageFileName.Length);
end;
function GetParentProcessId(ProcessId: DWORD; out ProcessImageFileName: string): DWORD;
var
hProcess, hParentProcess: THandle;
ProcessCreated, ParentCreated: LONGLONG;
begin
Result := 0;
ProcessImageFileName := '';
hProcess := OpenProcess(PROCESS_QUERY_INFORMATION, False, ProcessId);
if hProcess = 0 then
RaiseLastOSError;
try
Result := GetProcessParentId(hProcess);
if Result = 0 then
Exit;
ProcessCreated := GetProcessCreateTime(hProcess);
finally
CloseHandle(hProcess);
end;
hParentProcess := OpenProcess(PROCESS_QUERY_INFORMATION, False, Result);
if hParentProcess = 0 then
RaiseLastOSError;
try
ParentCreated := GetProcessCreateTime(hParentProcess);
if ParentCreated > ProcessCreated then
begin
Result := 0;
Exit;
end;
ProcessImageFileName := GetProcessImageFileName(hParentProcess);
finally
CloseHandle(hParentProcess);
end;
end;
initialization
hNtDll := GetModuleHandle('ntdll.dll');
if hNtDll <> 0 then
NTQueryInformationProcess := GetProcAddress(hNtDll, 'NtQueryInformationProcess');
end.
When I run the code from the IDE, I get the following results:
parent ID: 5140, parent image file name:
"\Device\HarddiskVolume1\Program Files\Embarcadero\RAD
Studio\8.0\bin\bds.exe"
so you may need to find a way to translate that into a "normal" path, e.g. "C:\Program Files\Embarcadero\RAD Studio\8.0\bin\bds.exe".

Wait before ShellExecute is carried out?

I have a hopefully quick question: Is it possible to delay execution of ShellExecute a little bit?
I have an application with autoupdater. After it downloads all necessary files etc, it renames current files to *.OLD and the new as the previous. Simple enough. But then I need to delete those .OLD files. This 'cleanup' procedure is executed on MainForm.OnActivate (with a check if it is the first activate proc). But this apparently happens too fast (I get False from DeleteFile). This is the procedure:
procedure TUpdateForm.OKBtnClick(Sender: TObject);
const SHELL = 'ping 127.0.0.1 -n 2';
begin
ShellExecute(0,'open',pchar(SHELL+#13+Application.ExeName),nil,nil,SW_SHOWNORMAL);
Application.Terminate;
end;
This procedure is supposed to restart the application. I am certain that the deleting problem is caused by the quick start of the second application, because if I restart it myself, giving it a little time, the files get deleted normally.
tl;dr version: I need to call ShellExecute() which waits a bit (0.1 sec or so) and THEN executes the command.
Note
I tried using the -ping command to try to delay it, but it didn't work.
Thank you very much in advance
Edit: Rephrased
I need this to happen || First app closes; Wait 100 ms; second app opens ||. I need to call ShellExecute first, then wait until the calling application closes itself completely, then execute the shell (i.e. open second application)
You're doing an autopatcher right ?
I've had the same problem and this is how I bypassed it :
You run second app with argument "--delay" or something like that.
Second app handles argument "--delay" and sleeps for 100 ms, then continues running normally.
This routine is some utils code in our game engine. It can run an executable and optionally wait for it to exit. It will return its exit code:
function TSvUtils.FileExecute(ahWnd: Cardinal; const aFileName, aParams, aStartDir: string; aShowCmd: Integer; aWait: Boolean): Integer;
var
Info: TShellExecuteInfo;
ExitCode: DWORD;
begin
Result := -1;
FillChar(Info, SizeOf(Info), 0);
Info.cbSize := SizeOf(TShellExecuteInfo);
with Info do begin
fMask := SEE_MASK_NOCLOSEPROCESS;
Wnd := ahWnd;
lpFile := PChar(aFileName);
lpParameters := PChar(aParams);
lpDirectory := PChar(aStartDir);
nShow := aShowCmd;
end;
if ShellExecuteEx(#Info) then
begin
if aWait then
begin
repeat
Sleep(1);
Application.ProcessMessages;
GetExitCodeProcess(Info.hProcess, ExitCode);
until (ExitCode <> STILL_ACTIVE) or Application.Terminated;
CloseHandle(Info.hProcess);
Result := ExitCode;
end;
end
end;
Here is some code that can check to see if a process exists. So... current app calls the updater and terminates. The updater can check to see if old app has terminated and do it's thing (rename, update, delete, etc):
function TSvUtils.ProcessExists(const aExeFileName: string; aBringToForgound: Boolean=False): Boolean;
var
ContinueLoop: BOOL;
FSnapshotHandle: THandle;
FProcessEntry32: TProcessEntry32;
begin
FSnapshotHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
FProcessEntry32.dwSize := SizeOf(FProcessEntry32);
ContinueLoop := Process32First(FSnapshotHandle, FProcessEntry32);
Result := False;
while Integer(ContinueLoop) <> 0 do
begin
if ((UpperCase(ExtractFileName(FProcessEntry32.szExeFile)) =
UpperCase(aExeFileName)) or (UpperCase(FProcessEntry32.szExeFile) =
UpperCase(aExeFileName))) then
begin
if aBringToForgound then
EnumWindows(#BringToForgroundEnumProcess, FProcessEntry32.th32ProcessID);
Result := True;
end;
ContinueLoop := Process32Next(FSnapshotHandle, FProcessEntry32);
end;
CloseHandle(FSnapshotHandle);
end;
If you can use CreateProcess instead of ShellExecute, you can wait on the process handle. The process handle is signalled when the application exits. For example:
function ExecAndWait(APath: string; var VProcessResult: cardinal): boolean;
var
LWaitResult : integer;
LStartupInfo: TStartupInfo;
LProcessInfo: TProcessInformation;
begin
Result := False;
FillChar(LStartupInfo, SizeOf(TStartupInfo), 0);
with LStartupInfo do
begin
cb := SizeOf(TStartupInfo);
dwFlags := STARTF_USESHOWWINDOW or STARTF_FORCEONFEEDBACK;
wShowWindow := SW_SHOWDEFAULT;
end;
if CreateProcess(nil, PChar(APath), nil, nil,
False, NORMAL_PRIORITY_CLASS,
nil, nil, LStartupInfo, LProcessInfo) then
begin
repeat
LWaitResult := WaitForSingleObject(LProcessInfo.hProcess, 500);
// do something, like update a GUI or call Application.ProcessMessages
until LWaitResult <> WAIT_TIMEOUT;
result := LWaitResult = WAIT_OBJECT_0;
GetExitCodeProcess(LProcessInfo.hProcess, VProcessResult);
CloseHandle(LProcessInfo.hProcess);
CloseHandle(LProcessInfo.hThread);
end;
end;
After ExecAndWait returns, then you can sleep for 100ms if you need to.
N#

Resources