As it stands, there's not enough information in the question to give you an answer that explains exactly what is happening in your scenario. You have not done enough debugging yet. So this answer will be of a more didactic nature. I'm going to attempt to teach you how to diagnose a problem of this nature.
First of all, let's clear up the obvious problems with your code, that are important but peripheral to the actual problem.
- You are running a busy wait loop. That's always a bad idea. You are burning 100% CPU in the main GUI thread just waiting. There are two obvious ways to avoid that. Use a thread to perform the call to
ShellExecuteEx
and the subsequent wait, as suggested by Marko. Or useMsgWaitForMultipleObjects
to perform a wait that can be interrupted in order to process input events. - If
ShellExecuteEx
does return a process handle to you, you leak it. When an API function returns a handle, you typically are responsible for closing it when you are done with it. That takes a call toCloseHandle
. The documentation calls this out: The calling application is responsible for closing the handle when it is no longer needed. - You are not performing comprehensive error checking. You call a Win32 API function and fail to check its return value for an error. More on this later, but it's critical that you perform correct error checking.
Now, let's look closely at the problem you report in the question. The problem is that the call to ShellExecuteEx
succeeds, the document is opened, but the busy wait returns before the process that displays the document closes. Your busy loop looks like this:
repeat
Application.ProcessMessages;
GetExitCodeProcess(SEInfo.hProcess, ExitCode);
until (ExitCode <> STILL_ACTIVE) or Application.Terminated;
There's really not a lot of code here. If this loop returns earlier than you expect, how can that happen? The loop terminates when ExitCode <> STILL_ACTIVE
or when Application.Terminated
is True
. The first thing to do is to isolate which of those conditions leads to termination of the loop. Some straightforward debugging would yield that information. I find it hard to believe that you accidentally terminated your application so I am going to proceed on the basis that Application.Terminated
is False
and the ExitCode
test terminates the loop.
So, let's look again at the call to GetExitCodeProcess
. This is immediately suspicious to me because you don't perform any error checking. Now, I happen to have extra information that you are perhaps lacking. Specifically that SEInfo.hProcess
may not contain a process handle. That makes it easier for me to anticipate the problems, but you can learn all this under the debugger. From the documentation again:
A handle to the newly started application. This member is set on return and is always NULL unless fMask is set to SEE_MASK_NOCLOSEPROCESS. Even if fMask is set to SEE_MASK_NOCLOSEPROCESS, hProcess will be NULL if no process was launched. For example, if a document to be launched is a URL and an instance of Internet Explorer is already running, it will display the document. No new process is launched, and hProcess will be NULL.
Note ShellExecuteEx does not always return an hProcess, even if a process is launched as the result of the call. For example, an hProcess does not return when you use SEE_MASK_INVOKEIDLIST to invoke IContextMenu.
So, perhaps what is happening is that your document is being processed by the shell in a way such that the value of hProcess
returned to you is NULL
, i.e. 0
. When that happens, the call to GetExitCodeProcess
fails and returns False
. That's the error condition that you did not check. You simply called GetExitCodeProcess
and ignored whether or not it succeeded. If that call did not succeed, then ExitCode
will not have been assigned a meaningful value and it is simply a mistake to attempt to read ExitCode
at all. It only has meaning when GetExitCodeProcess
returns True
.
There's yet another failure mode that is even more subtle. It's possible that the call to ShellExecuteEx
returns a valid process handle. But the process that is started deals with the request by passing it on to a different process and then terminating. From your perspective the process that displays the document is still running, but the process that ShellExecuteEx
gives you has terminated. This commonly happens when an instance the process that displays the document is already running. For example, try calling your Fire
function passing a .pas
file when your Delphi IDE is open. A new process is started, but it immediately hands the file off to the running Delphi IDE and terminates.
OK, that's all I can think of for now. I hope that this helps you track down what's really happening in your scenario. It may not be the news you wish to hear though because I suspect that you will find that the approach of using ShellExecuteEx
and waiting on the process handle that it returns will not meet your needs. Unfortunately opening a document using the shell is much more complicated than it might initially seem.