Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

wait for process terminate

145 views
Skip to first unread message

David Reeve

unread,
Oct 8, 2004, 11:37:27 PM10/8/04
to
Maybe there is an obvious answer to this one, but the beasts are snapping at
my heels and leaving little time for mature reflection, let alone reading my
favourite ng :-(

I want to quickly knock out a simple app to handle the installation of a
fairly complex suite of software. I have a number of individual
InstallShield setups which I wish to launch from this simple installer by
clicking the appropriate buttons. I would have thought it was simple enough
to use CreateProcess and then a WaitForSingleObject as per code example in
the FAQ , but not so. The InstallShield setup.exe looks like its 16bit code
and runs under a VDM instance. My code runs to the wait, but setup.exe is
never started. If you terminate the waiting process, then setup.exe starts
and runs OK. Further investigation shows that setup.exe never runs until the
calling app returns to its message loop. Any suggestions?


procedure TFrmFDInstaller.BtnDIOServerInstallClick(Sender: TObject);
var
sPath: string;
begin
sPath := FAppDir + 'DigioInstallationFiles\';
ProgramRunWait (sPath + 'Setup.exe', '', True);
end;

// from comp.lang.pascal.delphi.misc FAQ with mod to return thread id
function TFrmFDInstaller.ProgramRunWait (const theCommandLine,
defaultDirectory : string; Wait : boolean):Cardinal;
var sInfo : StartUpInfo;
pInfo : Process_Information;
dDir,
pMsg : pChar;
eNo : integer;
e : Exception;
begin
FillChar (sInfo, SizeOf (sInfo), 0);
sInfo.cb := SizeOf (sInfo);
if defaultDirectory <> '' then
dDir := pChar (defaultDirectory)
else
dDir := nil;
if CreateProcess (nil, pChar (theCommandLine), nil, nil, False, 0, nil,
dDir, sInfo, pInfo) then begin
try
if Wait then
WaitForSingleObject (pInfo.hProcess, INFINITE);
finally
CloseHandle (pInfo.hThread);
CloseHandle (pInfo.hProcess);
result := pInfo.dwThreadId;
end;
end
else begin
eNo := GetLastError;
pMsg := AllocMem (4096);
try
FormatMessage (format_message_From_System, nil, eNo, 0, pMsg,
4096, nil);
e := ELaunchIntaller.Create ('Create Process Error #' + IntToStr
(eNo) + ': ' + String (pMsg));
finally
FreeMem (pMsg);
end;

raise e;
end;
end;

Jamie

unread,
Oct 9, 2004, 4:42:21 AM10/9/04
to
try calling Application.ProcessMessages rite after the createprocess.

David Reeve

unread,
Oct 9, 2004, 6:27:17 AM10/9/04
to

"Jamie" <jamie_5_not_vali...@charter.net> wrote in message
news:10met1f...@corp.supernews.com...

> try calling Application.ProcessMessages rite after the createprocess.
>
I tried playing with this in various ways all to no avail. I don't think the
behaviour has anything to do with a particular message being blocked, rather
it looks more like a scheduling problem where the main VCL thread has to be
idling before the child process is scheduled to run. However, this is
exactly what a WaitForSingleObject should do. Also a Sleep(10000) should
relinquish the time slice, but in fact waits 10 seconds before the button
click returns and setup.exe runs. Boosting the priority of setup.exe using
the creation params of CreateProcess doesn't help either.

Dave


Maarten Wiltink

unread,
Oct 11, 2004, 3:53:03 AM10/11/04
to
"David Reeve" <dree...@big-pond.net.au> wrote in message
news:92P9d.19454$5O5....@news-server.bigpond.net.au...

> [...] Also a Sleep(10000) should relinquish the time slice, but in
> fact waits 10 seconds before [stuff happens].

Sleep(10000) should wait ten thousand milliseconds, and it looks like
it does. Sleep(0) is the usual way to go under.

Groetjes,
Maarten Wiltink


David Reeve

unread,
Oct 11, 2004, 10:35:55 AM10/11/04
to
"Maarten Wiltink" <maa...@kittensandcats.net> wrote in message
news:416a3dc7$1$568$e4fe...@news.xs4all.nl...

Either way, the time slice should be yielded. Because it took 10secs for the
program to launch, it appears yielding the VCL thread is not the critical
issue.... see my fuller post.

Dave


Maarten Wiltink

unread,
Oct 11, 2004, 10:42:54 AM10/11/04
to
"David Reeve" <dree...@big-pond.net.au> wrote in message
news:fTwad.22406$5O5....@news-server.bigpond.net.au...

> "Maarten Wiltink" <maa...@kittensandcats.net> wrote in message
> news:416a3dc7$1$568$e4fe...@news.xs4all.nl...
>> "David Reeve" <dree...@big-pond.net.au> wrote in message
>> news:92P9d.19454$5O5....@news-server.bigpond.net.au...

>>> [...] Also a Sleep(10000) should relinquish the time slice, but in
>>> fact waits 10 seconds before [stuff happens].
>>
>> Sleep(10000) should wait ten thousand milliseconds, and it looks like
>> it does. Sleep(0) is the usual way to go under.
>
> Either way, the time slice should be yielded.

Yes, but not at once. Only at the end of its nap. In fact, I'm not sure
if Sleep() does or should in fact release the timeslice for delays
other than zero.

Groetjes,
Maarten Wiltink


David Reeve

unread,
Oct 11, 2004, 10:59:42 AM10/11/04
to
"David Reeve" <dree...@big-pond.net.au> wrote in message
news:92P9d.19454$5O5....@news-server.bigpond.net.au...
>

OK, it's quite some hours later, but I reckon I'm on top of this one. It is
to do with messaging being blocked. If you do something like this....

repeat
dwOutcome := WaitForSingleObject (pInfo.hProcess, 10);
Application.HandleMessage;
until dwOutcome = WAIT_OBJECT_0;

then setup.exe is launched, but the rate at which InstallShield loads itself
depends upon the timeout set in WFSO. While waiting, messages are not
processed and the launched process grinds to a halt. I suspect there may be
some DDE trickery being used by setup.exe, but haven't had time to
investigate.

Clearly MsgWaitForMultipleObjects is what we need to keep a message loop
alive while we wait for the launched process to terminate. A bit of Googling
came up with some code from Peter Below which addressed this problem. It
provided a wait function which handled messages coming in from other threads
via Sendmessage, as well processed WM_PAINT, but left everything else to
back up in the message queue. I have provided extra functionality to strip
mouse clicks as you don't want impatient clicks on the parent window
building up only to be processed when setup.exe returns.

Another problem is that the process handle being waited on is for the VDM
process hosting the launched 16bit app, not the app itself. Using a creation
flag of CREATE_SEPARATE_WOW_VDM provides a unique VDM instance for the
launched app, so that waiting on the VDM amounts to the same as waiting on
the 16bit app. However, there is a noticeable speed penalty waiting for the
VDM to load and unload.

My working code follows below...

Dave

// from WinExecAndWait32V2 by Pat Richley, Peter Below
// processes paint messages and messages sent from other
// threads while waiting for the launched process to terminate
// forces 16bit app to run in its own VDM so that wait actually
// waits on the close of this VDM instance
// now strips mouse clicks from the queue while waiting

procedure ExecuteAndWait( FileName: string);
var
zAppName:array[0..512] of char;
StartupInfo:TStartupInfo;
ProcessInfo:TProcessInformation;
pMsg : PChar;
iErrNo: integer;
e: Exception;
procedure WaitFor( processHandle: THandle);
var
msg: TMsg;
ret: DWORD;
begin
repeat
ret := MsgWaitForMultipleObjects(
1, // 1 handle to wait on
processHandle, // the handle
False, // wake on any event
INFINITE, // wait without timeout
QS_PAINT OR // wake on paint messages }
QS_SENDMESSAGE OR // or messages from other threads
QS_MOUSEBUTTON // or mouse clicks }
);
if ret = WAIT_FAILED then Exit; // can do little here
if ret = (WAIT_OBJECT_0 + 1) then begin
{ Woke on a message, process paint messages and swallow mouse clicks.
Calling
PeekMessage gets messages sent from other threads processed. }
while PeekMessage( msg, 0, WM_PAINT, WM_PAINT, PM_REMOVE ) do
DispatchMessage( msg );
while PeekMessage( msg, 0, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE )
do
end;
until ret = WAIT_OBJECT_0;
end;

begin { ExecuteAndWait }
StrPCopy(zAppName,FileName);
FillChar(StartupInfo,Sizeof(StartupInfo),#0);
StartupInfo.cb := Sizeof(StartupInfo);
if CreateProcess(nil,
zAppName, { pointer to command line string }
nil, { pointer to process security attributes }
nil, { pointer to thread security attributes }
false, { handle inheritance flag }
CREATE_SEPARATE_WOW_VDM or { creation flags }
NORMAL_PRIORITY_CLASS,
nil, { pointer to new environment block }
nil, { pointer to current directory name }
StartupInfo, { pointer to STARTUPINFO }
ProcessInfo) { pointer to PROCESS_INF }
then begin
try
Waitfor(ProcessInfo.hProcess);
finally
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
end;
end
else begin
iErrNo := GetLastError;
pMsg := AllocMem(4096);
try
FormatMessage (Format_Message_From_System, nil, iErrNo, 0, pMsg, 4096,
nil);
e := Exception.Create('Create Process Error #' + IntToStr(iErrNo) + ':
' + String(pMsg));
finally
FreeMem(pMsg);

Message has been deleted

Rob Kennedy

unread,
Oct 11, 2004, 4:49:07 PM10/11/04
to
Maarten Wiltink wrote:
> "David Reeve" <dree...@big-pond.net.au> wrote in message
> news:fTwad.22406$5O5....@news-server.bigpond.net.au...
>> Either way, the time slice should be yielded.
>
> Yes, but not at once. Only at the end of its nap. In fact, I'm not sure
> if Sleep() does or should in fact release the timeslice for delays
> other than zero.

The only time Sleep does not release its timeslice is when its argument
is 0 and there are no other qualifying threads waiting to be scheduled.
With a non-zero timeout, Sleep will release its timeslice, and it will
not be awarded another timeslice until at least that many milliseconds
have elapsed. If there are no other qualifying threads in the meantime,
then I don't know what happens, but that's for the OS to determine.

--
Rob

Rob Kennedy

unread,
Oct 11, 2004, 4:57:44 PM10/11/04
to
L D Blake wrote:
> Ummm... I'm reading this and absorbing all the nifty tech info but I'm
> wondering why the OP wouldn't use a setup generator such as INNO which has
> built in run capabilities and scripting?

If the program he's installing is not his own, he might not have control
over how it gets packaged. Even if he does have access to the program's
component parts and the license to redistribute them in a custom
package, the problem of waiting for the pre-made installer to complete
might still be easier than the problem of figuring out how to install
the program from all those parts.

--
Rob

Message has been deleted
Message has been deleted

Rob Kennedy

unread,
Oct 11, 2004, 5:40:49 PM10/11/04
to
L D Blake wrote:

> On Mon, 11 Oct 2004 15:49:07 -0500, Rob Kennedy <m...@privacy.net> wrote:
>> The only time Sleep does not release its timeslice is when its argument
>> is 0 and there are no other qualifying threads waiting to be scheduled.
>
> Not Exactly... From the SDK...

I know. I read that before I posted. Which part of it did you mean to
draw my attention to?

--
Rob

Message has been deleted

Rob Kennedy

unread,
Oct 11, 2004, 7:09:17 PM10/11/04
to
L D Blake wrote:
> Actually the only time Sleep *does* release it's timeslice is when the count
> is 0 ...
>
> "A value of zero causes the thread to relinquish the remainder of its time
> slice to any other thread of equal priority that is ready to run. If there are
> no other threads of equal priority ready to run, the function returns
> immediately, and the thread continues execution. "

If the function returns immediately and continues execution, it doesn't
sound to me like it really relinquished anything. On the other hand,
maybe it gives up the remainder of its timeslice so the scheduler can
run, and when the scheduler determines that there's nothing else to do,
Sleep returns to find that the thread has a brand-new timeslice rather
than the partial one from before. Is there a way we could test this?

The latter behavior would be consistent with an apparent bug in
SetThreadPriority: Each time a thread sets its priority, its timeslice
is reset to full length, so a thread can potentially grant itself an
infinitely long timeslice.

--
Rob

Message has been deleted

David Reeve

unread,
Oct 12, 2004, 4:46:36 AM10/12/04
to
"L D Blake" <n...@any.adr> wrote in message
news:jpdlm09s9uu86mn9u...@4ax.com...
[snip]

> Ummm... I'm reading this and absorbing all the nifty tech info but I'm
> wondering why the OP wouldn't use a setup generator such as INNO which has
> built in run capabilities and scripting?
>
> I appreciate that solving this problem in Delphi is a challenge... but
still.
>

You know how it goes.....

a) 'I can knock together something in Delphi in an hour or two, and then it
will give me all the future flexibility I might need."
b) 6 hours later pouring through the API ..... 'I'm this far in, I'm not
giving up now'
c) 12 hours later.... 'It took longer than expected, but it's an investment
in know-how.....really... err... perhaps... err'

Truth is that this is a complicated installation where there are
dependencies due to COM servers which have to be installed and registered in
the correct sequence, along with the fact we might only be updating part of
an existing installation and need to establish what the customer already has
in place, along with the fact that there are config files specific to a
given customer, along with the fact various modules are enabled for certain
customers according to licence keys..... and the list goes on :-(

In any event it was an interesting 'gotcha' to stumble across, and that
section in help re the sleep function I have puzzled over for the last
however many years now makes some sort of sense....

"You have to be careful when using Sleep and DDE. If a thread creates any
windows, it must process messages. DDE sends messages to all windows in the
system. If you have a thread that uses a wait function with no time-out
interval, the system will deadlock. Therefore, if you have a thread that
creates windows, use MsgWaitForMultipleObjects or
MsgWaitForMultipleObjectsEx,
rather than Sleep."

Dave


Message has been deleted

Martin Harvey (Demon account)

unread,
Oct 12, 2004, 5:03:07 PM10/12/04
to
On Mon, 11 Oct 2004 18:09:17 -0500, Rob Kennedy <m...@privacy.net>
wrote:

>Sleep returns to find that the thread has a brand-new timeslice rather

>than the partial one from before. Is there a way we could test this?

It won't quite work like this - sleep will return to give it the
remainer of it's "timeslice", and the scheduler will be reinvoked at
the appropriate time.

The reason it is very likely to be this way is that the scheduler will
be invoked on a timer interrupt. Giving a thread a "brand new"
timeslice after the use of sleep would require sceduling all further
"timeslices" some fraction of the timer period down.

Not only that, it's probably not a good idea to allow that sort of
behaviour, because an application could concievably steal timeslices
off another if it knew this was the case.... although it would have to
know (or take an educated guess) at the scheduling activity of the
other.

MH.

Martin Harvey (Demon account)

unread,
Oct 12, 2004, 5:15:31 PM10/12/04
to
On Tue, 12 Oct 2004 01:14:32 -0400, L D Blake <n...@any.adr> wrote:

>Hmmmm... I'm thinking we pretty much have to take their word for it... I
>certainly don't know any way to determine the length of a timeslice...

I can think of a way, but it's gonna be RAM intensive, and I could
only *guarantee* that it would work with a little bit of kernel
support, but it's highly likely you could get it to work with user
mode code only - with the modulo, that you'd have to look at the
results and apply some sensible interprtation on them.

Ideal method:

0. Allocate two threads. Get each thread to perform the folowing
steps:

1. Allocate some memory as a BIG array.
2. Lock it all into physical RAM.
3. Write a very busy loop that every 1,000 iterations (say 100
microseconds or so) reads the pentium time stamp counter, and stores
it in the next entry in the array.

The time stamp counter is not saved or restored on context switches,
you both threads leave you a paper trail of when they're executing.
You then should start to look for periodicities in the data. Not only
will you get the duration of the timeslices, you should also be able
to work out how much time is required for an interrupt and context
switch.

The catch is that 2) isn't possible with a bit of kernel mode support.

In order to get round it in user mode, you'll need to:

1. Write and read to all allocated memory before starting the timing
(windows is demand paged, so it aint really allocated until you use
it).
2. Try to keep the amount of memory used sufficiently small so that no
paging occurs, because once that happens, all your timings will go out
of the window.

MH.

0 new messages