The Executive’s Mom
The Run method of CExecutive calls one of the top ten most complicated Win32 functions, CreateProcess. Actually, CreateProcess isn’t quite as bad as it looks—once you figure out that most of its argument variations don’t apply to you. In the CExecutive class, it’s the properties that do the work. Most of them set up the STARTUPINFO structure containing input, and a few of them read the process and thread handles returned in the PROCESS_INFORMATION structure. I’m not going to get into the messy details of using these structures. Let’s just say that after your ducks are lined up, knocking them over with the Run method is no big deal.
Sub Run(sCmd As String)
' Process any environment variables
Dim sCmdLine As String, sPipeOut As String, sPipeErr As String
sCmdLine = MUtility.ExpandEnvStr(sCmd)
sProg = MParse.GetQToken(sCmdLine, " ")
' Create standard input, output, and error pipes
CreatePipes
' Create process and run it
If CreateProcess(sNullStr, sCmdLine, ByVal pNull, ByVal pNull, _
APITRUE, 0&, pNull, sInitDir, start, proc) Then
' Must close write end of out and err handles before you can read
CloseHandleNull hWriteStdOut
CloseHandleNull hWriteStdErr
Select Case ewm
Case ewmWaitIdle
' Wait, but allow painting and other processing
Do
GetExitCodeProcess proc.hProcess, iExit
DoEvents
Loop Until ReadPipeChunk And ReadPipeErrChunk And Completed
Case ewmWaitDead
' Stop dead until process terminates
Dim iResult As Long
iResult = WaitForSingleObject(proc.hProcess, INFINITE)
If iResult = WAIT_FAILED Then ErrRaise Err.LastDllError
' Get the return value
GetExitCodeProcess proc.hProcess, iExit
Do
Loop Until ReadPipeChunk And ReadPipeErrChunk And Completed
Case Else
' Caller must call use ExitCode and pipe chunks directly
End Select
CloseHandleNull proc.hProcess
CloseHandleNull proc.hThread
Else
ApiRaise Err.LastDllError
End If
End Sub
Most of the work here is handling the WaitMode property (represented by the emw variable). It’s very similar to what you already saw in WaitOnProgram.
The code to handle standard input and output pipes is the most difficult part. The standard input, standard output, and error pipes are set up in the CreatePipes procedure. The output can be read all at once from the PipedOutText property when Run is finished. If WaitMode is ewmNoWait, you can read the pipe in chunks the way you see it done in the Run method. Windows is not very forgiving in its handling of pipes. If you get anything wrong, you’ll crash, as I learned the hard way over and over again. But I think I’ve finally got the kinks worked out so that you can pipe safely with CExecutive.
The Shell function takes the same constants known to CreateProcess, ShellExecute, and their obsolete API ancestor WinExec. These are the same constants used by ShowWindow to modify the appearance of an existing window, and, frankly, many of them don’t make sense in the context of starting a new program. The Display Options settings in the Test Execute program—Hidden,Has Focus, Minimized, and Maximized—work better for me. Take a look at the logic in the check box event procedures and the GetDisplay function (TEXECUTE.FRM), which translates these sensible settings into the arbitrary constants required by Windows.