|
Post by troycheek on Dec 27, 2015 17:37:34 GMT 1
When using the System command to start a new program from inside a GB32 program, you can give it the hProcess parameter which returns a handle. For example, System "notepad", hProcess hand1%. The help file says to Dim the handle as a type handle, but I saw a 32 bit integer (%) used somewhere in this situation, it seemed to work, so I got into the habit of using that instead. Can anyone explain why this is a good/bad idea?
If I start a program running with System with the command above, I can check it's progress later by using the ~GetExitCodeProcess(hand1%, V:stat1%) command. The help file mentions the STATUS_PENDING which appears to have a value of 259. I take this to mean that the program hasn't exited yet. Maybe it's still running normally, maybe it's hung up, but it definitely hasn't exited yet. Are there any other useful exit code STATUS constants defined in GB32? Just from playing around with Notepad, I get an exit code of 0 if I close it normally and 1 if I kill it with the Windows Task Manager. I assume a program can return custom exit codes, probably to denote some error or another. Is there some kind of standard? Could a program return an exit code of 259? If I ran a compiler that returned 259 errors, would GB32 think it was still running?
Edit: Okay, I played some more. All the Windows programs from various sources I tried returned an exit code of 0 when they exited normally. So I wrote a program that started several programs and waited for their exit codes to add up to 0 to know that they'd all exited. Wouldn't you know it, one of the programs decided that an exit code should be the number of files successfully processed, so 0 was an error and a higher number was a good thing. So I had to go back to checking that individual exit codes do not equal STATUS_PENDING.
|
|
|
Post by dragonjim on Dec 31, 2015 2:13:29 GMT 1
Handles A handle is a 32-bit integer pointing to the location of an object - hence, a 32-bit integer will work equally well. The main difference is that a handle can not be used in arithmetic operations and this provides a partial safeguard against it being accidentally altered (taken from the help file).
Exit Codes Different programs have different exit codes and these are usually generated using the ExitProcess(ExitCode) API. However, as you have spotted, there are some conventions, but they are not universal and, as you also spotted, you can only be sure about any particular program through testing and seeing what code is returned.
As to constants: C++ defines EXIT_SUCCESS = 0 and EXIT_FAILURE = 1, and these are generally the same for Windows (but these are not pre-defined by GB32). There are numerous STATUS_xxx constants that are...
STATUS_FLOAT_DIVIDE_BY_ZERO STATUS_FLOAT_INEXACT_RESULT STATUS_FLOAT_INVALID_OPERATION STATUS_FLOAT_OVERFLOW STATUS_FLOAT_STACK_CHECK STATUS_FLOAT_UNDERFLOW STATUS_GUARD_PAGE_VIOLATION STATUS_ILLEGAL_INSTRUCTION STATUS_IN_PAGE_ERROR STATUS_INTEGER_DIVIDE_BY_ZERO STATUS_INTEGER_OVERFLOW STATUS_INVALID_DISPOSITION STATUS_INVALID_HANDLE STATUS_NO_MEMORY STATUS_NONCONTINUABLE_EXCEPTION STATUS_PENDING STATUS_PRIVILEGED_INSTRUCTION STATUS_SEGMENT_NOTIFICATION STATUS_SINGLE_STEP STATUS_STACK_OVERFLOW STATUS_TIMEOUT STATUS_USER_APC STATUS_WAIT_0
...of which a few might be useful.
The long and short of it is, there are conventions as to what values of exit codes should be, but there are no constraints. If a program returned 259, then it would be up to the receiving program to decide whether this was taken to mean STATUS_PENDING or 259 errors... For example, if a Symantec installation fails, an exit code is added to the log; if 259 is reported, it means "No more data is available."
I hope some of the above is helpful.
|
|
|
Post by troycheek on Dec 31, 2015 3:57:18 GMT 1
That information was indeed helpful. I think as long as I don't call a program that thinks 259 is a proper exit code, I can continue to see if the program is running by checking its exit code against STATUS_PENDING. My other idea is to call the Windows command tasklist and check its output for the name of the program and/or its PID. (That is what the "ProcessID var" option in the System command returns, right?)
I never really had a need for this in the past because I'd pretty much always use the System command's WAIT option. It was only recently that I realized I was running multiple single-threaded time-consuming programs one after another when I could be running them all at the same time and maybe do a little processing on my own while waiting for them to finish.
|
|
|
Post by dragonjim on Dec 31, 2015 12:25:45 GMT 1
This is a little function I converted from VB many years ago that finds a process by its name; there's no reason why it won't work if you check for your ProcessID rather than the process name.
Function FindProcess(match$)
Const TH32CS_SNAPHEAPLIST = &H1 Const TH32CS_SNAPPROCESS = &H2 Const TH32CS_SNAPTHREAD = &H4 Const TH32CS_SNAPMODULE = &H8 Const TH32CS_SNAPALL = (TH32CS_SNAPHEAPLIST Or TH32CS_SNAPPROCESS Or TH32CS_SNAPTHREAD Or TH32CS_SNAPMODULE) Const TH32CS_INHERIT = &H80000000 Const MAX_PATH As Integer = 260 Const PROCESS_ALL_ACCESS = 0xFFF
Type PROCESSENTRY32 dwSize As Long cntUsage As Long th32ProcessID As Long th32DefaultHeapID As Long th32ModuleID As Long cntThreads As Long th32ParentProcessID As Long pcPriClassBase As Long dwFlags As Long szExeFile As String * 260 End Type
Local hSnapShot As Long, uProcess As PROCESSENTRY32, r As Boolean, match As Boolean, strProcName As String hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0) uProcess.dwSize = Len(uProcess) r = Process32First(hSnapShot, uProcess) Do While r And match = False strProcName = UCase(Left$(uProcess.szExeFile, Iif(InStr(1, uProcess.szExeFile, Chr$(0)) > 0, InStr(1, uProcess.szExeFile, Chr$(0)) - 1, 0))) If Trim$(strProcName) <> "" Then If Upper(Left(strProcName, Len(match$))) = Upper(match$) Then match = True End If r = Process32Next(hSnapShot, uProcess) Loop ~CloseHandle(hSnapShot) Return match EndFunc
|
|
|
Post by troycheek on Jan 1, 2016 0:27:35 GMT 1
Your function works fine in my tests, but I have no idea how it works or how to alter it. (Don't feel obligated to explain it.) I instead hacked together the following function:
Function FindPID(match%) Local tfn$, l%, fn%, buf$ tfn$ = TempFileName("task", "txt") System "cmd /c tasklist /FI " + Chr$(34) + "PID eq " + Str$(match%) + Chr$(34) + " > " + tfn$, Show SW_HIDE, Wait fn% = FreeFile Open tfn$ for Input As # fn% l% = LOF%(# fn%) buf$ = Space$(l%) BGet # fn%, V:buf$, l% Close # fn% KillTempFile tfn$ If InStr(buf$, Str$(match%)) > 0 Then Return True Else Return False EndIf EndFunc This uses the Windows tasklist command filtered by the PID number and sends the output to a temp file. If the process is running, the output file will have a line containing the process name and its PID. If not, the output file will consist of something like "no tasks found." Not as elegant as your solution, but it's something I understand and can program off the top of my head when needed.
Along those lines, is there a preferred method for requesting/forcing exit of a program started with the System command? It can be done with Windows commands. Something along the lines of
System "cmd /c taskkill /pid " + Str$(pid%), Show SW_HIDE, Wait to ask nicely and adding a "/f" at the end to force the issue. (Actually, I'd been using the process name, but now that I've realized that System can return a PID, I think I'll use that since it's considerably less likely that two processes will get assigned the same PID than two copies of someprog.exe might be running at the same time.)
|
|
|
Post by dragonjim on Jan 3, 2016 23:47:07 GMT 1
Sorry for the delay in replying...
To use my routine to find a process by ID, see below (the changes are in the last part of the function).
Global pid% = GetCurrentProcessId() Print FindProcess(pid%)
Function FindProcess(processID%) Const TH32CS_SNAPHEAPLIST = &H1 Const TH32CS_SNAPPROCESS = &H2 Const TH32CS_SNAPTHREAD = &H4 Const TH32CS_SNAPMODULE = &H8 Const TH32CS_SNAPALL = (TH32CS_SNAPHEAPLIST Or TH32CS_SNAPPROCESS Or TH32CS_SNAPTHREAD Or TH32CS_SNAPMODULE) Const TH32CS_INHERIT = &H80000000 Const MAX_PATH As Integer = 260 Const PROCESS_ALL_ACCESS = 0xFFF Type PROCESSENTRY32 dwSize As Long cntUsage As Long th32ProcessID As Long th32DefaultHeapID As Long th32ModuleID As Long cntThreads As Long th32ParentProcessID As Long pcPriClassBase As Long dwFlags As Long szExeFile As String * 260 End Type Local hSnapShot As Long, uProcess As PROCESSENTRY32, r As Boolean, match As Boolean, strProcName As String hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0) uProcess.dwSize = Len(uProcess) r = Process32First(hSnapShot, uProcess) Do While r And match = False If uProcess.th32ProcessID = processID% Then match = True r = Process32Next(hSnapShot, uProcess) Loop ~CloseHandle(hSnapShot) Return match EndFunc
Your example works fine; the one above just cuts out the file saving and thus should be quicker.
As to terminating a process, the method you are using is probably as good as any other; the only API I know of is TerminateProcess(pid%) which would be similar to your /f tag.
|
|
|
Post by troycheek on Jan 4, 2016 11:50:10 GMT 1
(My code had an mistake where I used Str$(pid%) instead of Str$(match%) in the System command in the function, but it ran fine anyway for me because I was using a global pid% to hold the ID I was looking for in main. You probably did the same. I edited my post to fix this.)
Stupid curiosity. To the benchmarks! Your version is indeed quicker at ~5 ms to my version's ~150 ms. Most of that time is spent waiting for the Windows command "tasklist" to finish and return. Even a simple "dir" command takes almost as long. Probably something to do with Windows creating and destroying the new console window, even though the System command's "Show SW_HIDE" parameter keeps it from showing up. Ironically, saving the temp file to a RAM disk or a USB drive on another computer on the LAN made no difference. I guess Windows is caching the <1 KB file in RAM for the short time it's needed and never gets around to actually writing it to the media before it's deleted. It says exactly that in the Help file under TempFileName, doesn't it? I never really noticed this before because I tend to NOT use TempFileName and NOT delete my temp files so I can check them later if the program doesn't produce results as expected.
Anyway, either of our methods will work for me time-wise because I usually only perform such a check once a second or so. By that point the GB32 code is finished with whatever else it was doing and is just idling while waiting for the external program to exit. If I was doing something more processor intensive, I'd probably go with your version (I've made certain I saved a local copy just in case I need it in the future). I'm currently on a "I can code it myself from memory because I understand it so well" kick. Then again, that's how all my sorts end up being Bubble. Heck, that's why I'm still using GB32 instead of learning Visual Basic or something.
|
|
|
Post by dragonjim on Jan 4, 2016 15:57:14 GMT 1
Glad my example was of some assistance - what I forgot to mention was to check out this article about System and, of cursory interest, this article on closing Child processes on Sjouke Hamstra's site - as the Wiki definition of a Child Process is "a process created by another process (the parent process)", this last one may be of some interest to you with what you are currently doing. As to continuing with GB32, although its implementation and GUI may be dated, its structure is still amongst the easiest and most intuitive of the languages I've dabbled in; in addition, its IDE is better that most when it comes to debugging. However, if you were looking to move on, most people would advise C++ (or it's near clone Java) which have the advantage of being cross platform (for Android, read customised Java), rather the VB.NET which, although supported by Microsoft, seems to have been eclipsed by C++ and XMAL which are the basis for the new Modern apps. All that said, keep persevering with GB32 - considering the last major release was around the turn of the millennium, it is still pretty bulletproof when you consider how many applications either no longer work on new OSs or need to run in Compatibility Mode.
|
|
|
Post by troycheek on Apr 6, 2016 2:52:27 GMT 1
I've been playing some more and discovered a few instances when Windows taskkill command called with PID does NOT actually kill the process. This wouldn't be so bad, but in most cases taskkill still reports SUCCESS and GetExitCodeProcess no longer returns STATUS_PENDING. I have not noticed this problem when using taskkill with an image name (program name), but that method doesn't work when you have multiple copies of the same program running and only want to kill one particular copy.
The method outlined in Sjouke's Closing A Child Process article does seem to work in those same instances. I've not gotten it to fail yet.
Aha! The PID returned by the GB32 System command is not always the PID that shows up in tasklist. I think the program I'm running (ffmpeg) is itself spawning some other processes. Running taskkill with the PID returned by System doesn't kill all these processes, so at least some of ffmpeg continues to run. I think Sjouke's method of sending a WM_CLOSE message to all the windows associated with the PID is telling ffmpeg to shut itself down semi-gracefully which stops all the processes. I'll bet if Sjouke's "polite" WM_CLOSE method didn't work and fell back to the "messy" TerminateProcess I'd have the same problem with ffmpeg.
Edit: It just occurred to me that I'm running ffmpeg with a command like System "cmd /C ffmpeg -lots -of -parameters". System isn't running ffmpeg but is rather running cmd.exe and telling it to run ffmpeg. The PID returned might be for cmd.exe and not ffmpeg. I got into the habit of doing this when running "internal" Windows commands instead of external programs. More testing is required.
|
|
|
Post by dragonjim on Apr 6, 2016 19:08:59 GMT 1
Thanks for keeping us informed on your progress - it makes interesting reading.
I think you've managed to answer any questions you may have had; the only comment I can think of adding is that, you may be correct with the "messy" TerminateProcess; however, I have read somewhere that the most recent offerings from Microsoft SHOULD close any child processes when their parent process is terminated - that said, you've found examples where this hasn't happened (cmd.exe and ffmpeg for instance), so maybe that was just wishful thinking...
|
|
|
Post by troycheek on Apr 7, 2016 5:23:39 GMT 1
I'm running Windows 7 and turned off updates a long time ago, so it's quite possible any recent improvements from Microsoft aren't reflected in my system.
I've played around some more and confirmed that if I use System "cmd /C ffmpeg someparams" the PID returned is for cmd.exe and not ffmpeg, and taskill on that PID will kill cmd and won't kill ffmpeg, at least on my system. I think. I swear that for a while the same taskkill command worked to kill ffmpeg on my "test" computer but didn't work on the "production" computer, but now I can't get it to work at all on any computer or see how it ever could have worked to begin with.
EDIT: to properly kill cmd.exe and whatever cmd.exe is running, use the command "taskkill /f /t /pid 1234" where "1234" is the pid of the cmd.exe program. The /t kills all the program threads started by the cmd.exe program.
Obviously, the solution is to not use the "cmd /C" trick when running external programs. Yet when I try to run ffmpeg this way, sometimes it doesn't run, but always works in my simple test programs. Aha! Now I remember why I use "cmd /C" all the time. For "serious" work I'm redirecting some of the console output from ffmpeg to a file for later use with the ">" redirect symbol. System "ffmpeg someparams >logfile.txt" passes the ">logfile.txt" to ffmpeg as another parameter or does something else that ffmpeg chokes on (it exits immediately and I can't read the error message fast enough). System "cmd /C ffmpeg someparams >logfile.txt" works because all the parameters are passed to cmd.exe first and it's cmd's job to know how to parse things like the redirect symbol. Regardless, the WM_CLOSE method seems to stop ffmpeg when I need it to, so all is good.
I'm glad you find this interesting. To be honest, I'm mostly posting this stuff here so I can find it again myself in the future if I forget anything.
|
|