Wednesday, November 28, 2018

Executing a program and waiting for it to finish

At the beginning of the month, I wrote "The next stage in the process is to convert the xlsx file (after completion) to a tab delimited file by means of the increasingly valuable program which I wrote with Delphi 10. This turns out to be much harder than expected - a topic for another blog entry."

Here is that second blog entry. In the past, I have used the ShellExecute procedure in Windows in order to execute a program in the context of another program; here is some sample code (taken from a 'program launcher'):
chdir (extractfiledir (path)); cmd:= ini.ReadString (key, 'Cmdline', '') + ' ' + pdata + #0; ShellExecute (handle, 'open', pchar (path), pchar (cmd), nil, sw_show)
Whilst this procedure does what it is supposed to do, the executing program has no way of knowing when the child program finishes. Sometimes this is not a problem, but sometimes it is. In the context of converting an xlsx file to tab delimited, it is very important to know when the child program has completed.

There is a second procedure in Windows called ShellExecuteEx which both executes the child program and also waits for it to finish. I am sure that I used this procedure once in a program, but I couldn't find the code and so had to rewrite it from scratch. Unfortunately, some of the examples which I looked at were misleading, so it took me some time to figure out exactly what had to be done. Here is the code which executes the converter program:

var exInfo: TShellExecuteInfo; exitcode: DWORD; prog: string; begin With exInfo Do begin cbSize:= Sizeof (exInfo); fMask:= SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_DDEWAIT; Wnd:= aHandle; lpVerb:= 'open'; lpFile:= pchar (extractfilename (prog)); prog:= extractfiledir (prog); if pos (' ', prog) > 0 then prog:= '"' + prog + '"'; lpDirectory:= pchar (prog); lpParameters:= pchar ('"' + f1 + '" "' + f2 + '" ' + flags); nShow:= SW_HIDE; end; if ShellExecuteEx (@exInfo) then begin while GetExitCodeProcess (exinfo.hProcess, exitcode) and (exitcode = STILL_ACTIVE) do Sleep (500); CloseHandle (exinfo.hProcess ); end;
I discovered that if the program to be executed has a space in its name (or more likely, in the directory name), then one has to put quotation marks around the name. Similarly, the parameters have to be protected by quotation marks. Since having written this code, it works very well in two programs of mine which execute the converter program.

This morning I had an epiphany about the above code: in a different program suite, there is a loader program which receives as input data from an examinee along with a list of exams (i.e. programs) that the examinee has to take. Each exam has to signal the loader program when it has been completed in order to cause the next exam to be executed. The loader program uses the ShellExecute method, which solves half of the problem; the second half of the problem (notification) is achieved by means of a complicated system of inter-program messages. Sometimes there are bugs with this method. It occurred to me this morning that I can drop all the message creating and receiving code: the launcher can use ShellExecuteEx in order to execute each exam, and of course, the launcher will receive a reliable message when the child program has completed. The child program doesn't even have to know that it is being run by the launcher, which simplifies a few things.

No comments: