*SET PROCEDURE TO <tempfile> =RunExeAndWait("C:\Windows\Notepad.exe") PROCEDURE RunExeAndWait * procedure to fire up an external program and suspend all VFP activities until it closes * Written by: Albert Gostick * Last Updated: Nov 23, 2004 * Parameters: * tcExeName: application (exe or com etc) to fire up (file name and path only - no parameters for app); e.g. * c:\VFP\Test\TestApp.exe * tcCommandLine: full command line including parameters with a space between exe and parameters; full path to * exe should be wrapped in quotes if there is a space anywhere in the file path * tcStartupDir: startup directory for the process (optional) * Note: that none of the above parameters need to be null terminated as this character will be added below LPARAMETERS tcExeName, tcCommandLine, tcStartupDir LOCAL llOldAutoYield, llReturn, ; ; lcNullChar, lcStartupInfo, lcProcessInfo, lnPriority, lnInheritHandles, lnExitCode, lnResult, ; lnProcessHandle, lnThreadHandle * use this over and over so stuff in var STORE CHR(0) TO lcNullChar *** Parameter checks *** * all parameters must be and null-terminated if passed and empty character if not passed * tcExeName IF VARTYPE(tcExeName) # "C" STORE "" TO tcExeName ELSE IF NOT RIGHT(tcExeName,1) == lcNullChar STORE tcExeName + lcNullChar TO tcExeName ENDIF ENDIF * tcCommandLine IF VARTYPE(tcCommandLine) # "C" STORE "" TO tcCommandLine ELSE IF NOT RIGHT(tcCommandLine,1) == lcNullChar STORE tcCommandLine + lcNullChar TO tcCommandLine ENDIF ENDIF * tcStartupDir IF VARTYPE(tcStartupDir) # "C" STORE "" TO tcStartupDir ELSE IF NOT RIGHT(tcStartupDir,1) == lcNullChar STORE tcStartupDir + lcNullChar TO tcStartupDir ENDIF ENDIF * call function to set up DLL calls DeclareDLLs() * default return value (only set to .F. on error) STORE .T. TO llReturn * save autoyield setting to restore and then set for here STORE _VFP.AutoYield TO llOldAutoYield STORE .T. TO _VFP.AutoYield *** Short Documentation On CreateProcess *** * Parameters passed and what I have been able to figure out from various sources: * [1] lcApplicationName: optional; if not passed, the information in parsed from Param 2, lcCommandLine; must * be null terminated * [2] lcCommandLine: command line to run including any parameters to be passed to program; if param 1 is empty, * then param 2 must contain the file to run including path (e.g c:\Temp\Temp.exe) and this has to be * separated from the parameters with a space (eg. C:\Temp\Temp.exe <param>) * [3] lcProcessAttributes: something to do with assigning security attributes to the process or allowing the * returned handle to be inherited by child processes; not sure what this means but we pass zero which * means they cannot be inherited (I think - null cannot be inherited so zero must be same as null) * [4] lcThreadAttributes: sounds identical to [2] but applies to threads; zero passed so handle cannot be inherited * [5] llInheritHandles: if TRUE (1?) then each inheritable handle in the calling process is inherited by the * new process; our sample was set to 1 (true I think) * [6] lcCreationFlags: flags used when creating the process; in our case, flag is used to set priority for the * process so named lnPriority; value of 32 means "Normal" priority * [7] lcEnvironment: if null, new process uses environment of the calling process (if used, seems to be a string of * environmental vars (eg. DEV=0N) that is passed to set up the environment; each set of vars is delimted by * the null character and then the entire block is terminated with a null as well * [8] lcCurrentDirectory: directory to startup the process in (I think this is the same as the "Start-In" option' * on a shortcut; must be null-terminated; if not passed, the directory for the calling program is used; note that * in our sample this was not used but might be better than wrapping call to ExeWait in SET DEFAULT TO before * and after calling ExeWait() * [9] lcStartupInfo: string that is filled with startup info that after the call will contain (according to the docs) * "window station, desktop, standard handles and appearance of main window for new process"; although this is * passed in to function here, it does not seem to be parsed after returning; the string "D" + 67 nulls are * passed in as the string to be filled; it looks like this string can be filled with values to set things like * window caption of new process, window height etc; do not have good example on how to use yet * [10] lcProcessInfo: string that is filled with "process info" which I think is handles to the new process and it * thread; it is filled with 16 nulls and if the process is created correctly, then lcProcessInfo can be * parsed to obtain the following: handle to new process [chars 1 to 4], handle to thread [chars 5 to 8], * number identifying process [chars 9 to 12 I think], number identifying new thread [chars 13 to 16 I thnk]; * these seem to be "double word" values as they have to be converted using a function to convert from base * 256 values (see function String2DWord()) * Returns: non-zero if process successfully created (TRUE) or zero if process creation failed (FALSE) * set some values for CreateProcess lnInheritHandles = 1 lnPriority = 32 lcStartupInfo = CHR(68) + REPLICATE(CHR(0),67) lcProcessInfo = REPLICATE(CHR(0),16) * init lnResult STORE 0 TO lnResult * create the process and capture the result STORE CreateProcess(0,tcExeName+" "+tcCommandLine+CHR(0),0,0,; lnInheritHandles,lnPriority,; 0,0,@lcStartupInfo,@lcProcessInfo) TO lnResult * if non-zero returned, process was successfully created IF lnResult = 0 STORE GetLastError() TO lnErrorNum WAIT WINDOW "Last Error num: " + LTRIM(STR(lnErrorNum)) ELSE * string lcProcessInfo will now contain a series of handles in it; pull out 1st 4 chars as the process * handle and then the next 4 chars as the thread handle; these are in a base 256 format that need to * be coverted back to numeric (it seems) lnProcessHandle = String2dWord(SUBSTR(lcProcessInfo,1,4)) lnThreadHandle = String2DWord(SUBSTR(lcProcessInfo,5,4)) * wait until the termination of the program DOEVENTS DO WHILE .T. * reset exit code each loop STORE 0 TO ExitCode * fetch the exit code from the app =GetExitCodeProcess(lnProcessHandle,@ExitCode) * if exitcode = 259, this means app not busy so exit IF exitcode # 259 && not still busy EXIT ENDIF Sleep(100) && wait .1 seconds ENDDO CloseHandle(lnThreadHandle) CloseHandle(lnProcessHandle) ENDIF * restore autoyield setting if needed IF llOldAutoYield = .F. STORE .F. TO VFP.AutoYield ENDIF * call function to release DLLs ReleaseDLLs() RETURN (llReturn) * function to convert a string that is base 256 to it's numeric equivalent * Written by: Albert Gostick * Last Updated: Nov 22, 2004 PROCEDURE String2DWord PARAMETERS tcString RETURN ASC(SUBSTR(tcString,1,1)) + ; ASC(SUBSTR(tcString,2,1)) * 256 + ; ASC(SUBSTR(tcString,3,1)) * 65536 + ; ASC(SUBSTR(tcString,4,1)) * 16777216 TO lnReturn PROCEDURE DeclareDLLS * procedure to load required DLLs for above procs * Written by: Albert Gostick * Last Updated: Nov 23, 2004 LOCAL dllArray[1], lnNumDLLs * grab list of DLL's STORE ADLLS(dllarray) TO lnNumDLLs IF ASCAN(dllarray,'CREATEPROCESS') = 0 DECLARE INTEGER CreateProcess IN kernel32; INTEGER lpAppName, STRING lpCmdLine, INTEGER lpProcAttr,; INTEGER lpThrAttr, INTEGER bInhHandles, INTEGER dwCrFlags,; INTEGER lpEnvir, INTEGER lpCurDir, ; STRING @lpStInfo, STRING @lpProcInfo ENDIF IF ASCAN(dllarray,'GETLASTERROR') = 0 DECLARE INTEGER GetLastError IN kernel32 ENDIF IF ASCAN(dllarray,"CLOSEHANDLE") = 0 DECLARE INTEGER CloseHandle IN kernel32 INTEGER hObject ENDIF IF ASCAN(dllarray,"GETEXITCODEPROCESS") = 0 DECLARE INTEGER GetExitCodeProcess IN WIN32API INTEGER hProcess, INTEGER @lpExitCode ENDIF IF ASCAN(dllarray,"SLEEP") = 0 DECLARE Sleep IN kernel32 INTEGER dwMilliseconds ENDIF RETURN PROCEDURE ReleaseDLLs && release dlls for exewait function CLEAR DLLS CreateProcess CLEAR DLLS GetLastError CLEAR DLLS CloseHandle CLEAR DLLS GetExitCodeProcess CLEAR DLLS Sleep RETURN