In a previous blog post Execute a new detached process I discussed GB32 commands to start a new detached program, or process. After the new process is created and executing, the parent process cannot communicate with the new process. There is no relationship between the both, hence the term detached. The GB32 commands WinExec, Exec rely on the WinExec() API, which does not return a process instance handle. The only thing you can get from WinExec is some error code when starting the new process somehow failed.
But there is System
Starting a new process might involve more than simply executing it and leaving it out there to be managed by the user. Maybe your program needs to wait for the second application to quit, or you want to execute a process with elevated privileges, or maybe you just want a handle on the new process so you can manage it programmatically. All of this can be done using System, command or function, depending on your needs.
The System command/function comes in two flavors. A simple version, a function, that only takes one parameter. Then there is the less simple one, implemented also as a command and function, taking multiple arguments. When you only need the creation of new process, for example to get a handle on it, use the simple version. It only takes one parameter, the command line. The command line is a null-terminated character string that contains the filename plus optional parameters:
Large = System("filename [parameters]")
This looks much like the WinExec() – and Exec() – function, but without the the show-window argument (which is most of the times a ShowWindow SW_* constant to make the new process visible). The difference between System and the other functions is the meaningful return value, a 64-bit integer holding the process-handle and its process identification number. Together these values uniquely identify the new process.
Using System requires some effort
Using the System() function comes with a responsibility. The process handle must be freed using the CloseHandle(hProcess) API. First let’s see how to use System() and how to interpret the return value:
Global Int32 hProcess ' keep in memory Local Int32 ProcessId ' you may keep it Local Large Process64 ' can be discarded Exe$ = "process.exe", Param$ = "-options" Process64 = System(Exe$ & " " & Param$) hProcess = LoLarge(Process64) ProcessId = HiLarge(Process64)
In this scenario the System() function replaces WinExec, and Exec (and maybe ShellExec but that is a different story).
It might be preferable, because System() invokes the CreateProcess API directly, passing it Null for all parameters, except for the lpszCmdLine argument and the argument taking if the address of a PROCESS_INFORMATION structure. This structure receives the output (or return values) of CreateProcess. It is used to store/return four values; the process an thread handles, and the process-id and the thread-id. GB32 only returns the process handle and process-id through an Int64 data-type.
Taking a closer look at those handles
In pseudo GB32 code, System(cmdline$) function acts like this:
Dim pi As PROCESS_INFORMATION
If CreateProcess(0,cmdline$,0,…, V:pi)
Return MakeLarge(pi.Process.Id, pi.hProcess)
Generally, CloseHandle API only decrements a reference count on a handle. When the reference count reaches zero, the OS knows that the object can be freed. That’s all, it is not some kind of destroy-function. Closing a handle might become your responsibility, keep these rules in mind:
- With the function variant of System(), GB32 only closes the handle to the primary thread. It returns the process handle and you have to close it in time.
- With the System command both handles are automatically closed - unless you explicitly ask for them as return values.
When you specify the “hProcess var” and/or “hThread var” options with the System command you are explicitly asking to obtain the handles and taking responsibility. For instance, in the following code System does not call CloseHandle on the handles:
Global Handle hProcess System FileName$, hProcess hProcess, hThread hThread
When, one way or the other, the program gains ownership of a handle, it is also responsible for closing it. The application should close a handle when it isn’t necessary anymore. A good place for a call to CloseHandle could be in your exit code, after the main message loop in the main section of your program:
Do Sleep Until Me Is Nothing ~CloseHandle(hProcess) ' End of Program
Termination and usage count
CreateProcess creates two objects identified using handles: a handle to a thread object and a handle to a process object. These objects are given an initial usage count of one. Then, just before CreateProcess returns, the function opens both the process and the thread object and increments the usage count again to two. It then places handles and IDs in the members of the PROCESS_INFORMATION structure. In the end, CreateProcess has set the usage count for both objects to two. In the end, the handles must be decremented twice to reach zero.
Before the OS can completely free the process, it must - of course – terminate. This will decrement the usage count with one to one. To reach a zero usage count, the parent process must also call CloseHandle. Only then the final usage count od zero is reached.
To free the primary thread object, a two step decrement is also required. The thread’s usage count is decremented when its process terminates. (During process termination the thread’s handle is closed). But this leaves the thread with a count of one. Because the System command already decremented the usage count of the thread, it can be freed as well.
The reason to immediately close the primary thread’s handle must be found in the OS. It has something to do with multiple creations of the same process. I don’t know why, but it is heavily recommended to proceed this way. The process handle on the other hand can be kept to be used in future API calls that require a process-handle.
In the next post I’ll discuss the termination of child process.