Plateforme Level Extreme
Abonnement
Profil corporatif
Produits & Services
Support
Légal
English
Complex forms software to replace Delrina FF
Message
De
24/11/2004 12:19:22
 
 
À
23/11/2004 14:20:04
Information générale
Forum:
Visual FoxPro
Catégorie:
Autre
Divers
Thread ID:
00931761
Message ID:
00964378
Vues:
49
Hi Y'all,

Thought I would post back my function with a fair bit more documentation. It is different than the others posted here in that it minimizes the number of other functions called (e.g. the declare and release functions) only in that it is meant to be included in a .prg procedure file and not as a separate class. It is not "perfect" but might help another to get up to speed faster than I did with this - all the ideas are from the previous postings here (see comments in this method as to where I got the original code). The jury is still out on what to call the function DWordToNum() herein with many good points being made: until I decide, WYSIWYG...

Thanks for all the help whoever jumped in or posted previously!
Albert
PROCEDURE RunExeAndWait
* procedure to fire up an external program and suspend all VFP activities until it closes

* Written by: Albert Gostick
* Last Updated: Nov 24, 2004

* Parameters:
* tcExeName: application (exe or com etc) to fire up (full path and file name (no parameters though); note that
*    the string does NOT need to be wrapped in quotes if the string contains any spaces (and in testing, it
*    cannot in fact be wrapped in quotes or it throws error #123)
* 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 (or else the parser cannot
*   determine where the .exe filename ends and the parameters start; note that if parameters are to be passed,
*   then the .exe filepath has to be at the start of the string also
* tcStartupDir: startup directory for the process (optional); if not passed, defaults to SYS(5) + CURDIR()
* tnPriority: priority for the app to start; values should be 32 (Normal), 128 (High), 64 (Idle/Low) or 1600
*   (real-time, whatever that is); optional, defaults to 32
*
* Examples:
* RunExeAndWait('C:\Windows\NotePad.exe')
* RunExeAndWait('C:\Program Files\Adobe\Acrobat 6.0\Acrobat\Acrobat.exe')
* RunExeAndWait('C:\Program Files\Adobe\Acrobat 6.0\Acrobat\Acrobat.exe', ;
*   '"C:\Program Files\Adobe\Acrobat 6.0\Acrobat\Acrobat.exe" c:\Test\MyReport.pdf')

LPARAMETERS tcExeName, tcCommandLine, tcStartupDir, tnPriority

LOCAL llOldAutoYield, llReturn, laDLLArray[1], lnNumDLLs, ;
      llCreateProcessWasOpen, llGetLastErrorWasOpen, llCloseHandleWasOpen, llGetExitCodeWasOpen, llSleepWasOpen, ;
      ;
      lcNullChar, lcStartupInfo, lcProcessInfo, lnInheritHandles, lnExitCode, lnResult, ;
      lnProcessHandle, lnThreadHandle, lnProcessAttributes, nThreadAttributes, lnEnvironment

* use this over and over so stuff in var
STORE CHR(0) TO lcNullChar

*** Parameter checks ***
* all parameters should be and null-terminated if passed and empty character if not passed; note that in
* testing, it does not seem that the parameters have to be null terminated but will keep the code in

* tcExeName
IF VARTYPE(tcExeName) # "C"
   STORE "" TO tcExeName
ELSE
   STORE ALLTRIM(tcExeName) + IIF(RIGHT(tcExeName,1) == lcNullChar,"",lcNullChar) TO tcExeName
ENDIF

* tcCommandLine
IF VARTYPE(tcCommandLine) # "C"
   STORE "" TO tcCommandLine
ELSE
   STORE ALLTRIM(tcCommandLine) + IIF(RIGHT(tcCommandLine,1) == lcNullChar,"",lcNullChar) TO tcCommandLine
ENDIF

* tcStartupDir
IF VARTYPE(tcStartupDir) # "C"
   STORE SYS(5) + CURDIR() + lcNullChar TO tcStartupDir
ELSE
   STORE ALLTRIM(tcStartupDir) + IIF(RIGHT(tcStartupDir,1) == lcNullChar,"",lcNullChar) TO tcStartupDir
ENDIF

* tnPriority: default this to 32 if not passed; if passed, force to 32 if incorrect
IF VARTYPE(tnPriority) # "N"
   STORE 32 TO tnPriority
ELSE
   IF NOT INLIST(tnPriority,32,64,128,1600)
      STORE 32 TO tnPriority
   ENDIF
ENDIF

*** Declare Necessary Windows Functions ***

* init array in case no DLLs around and then grab
DIMENSION laDLLArray[1]
STORE ADLLS(laDLLArray) TO lnNumDLLs

* init some vars to indicate if they were open or not before
STORE .F. TO llCreateProcessWasOpen, llGetLastErrorWasOpen, llCloseHandleWasOpen, llGetExitCodeWasOpen, llSleepWasOpen

* Note: the ASCANs done below are case-insensitive and only search column 1
IF ASCAN(laDLLArray,"CREATEPROCESS",1,-1,1,15) = 0
   DECLARE INTEGER CreateProcess IN kernel32 ;
      STRING lpApplicationName, STRING lpCommandLine, INTEGER lpProcessAttributes, INTEGER lpThreadAttributes, ;
      INTEGER bInheritHandles, INTEGER dwCreationFlags, INTEGER lpEnvironment, STRING lpCurrentDirectory, ;
      STRING @lpStartupInfo, STRING @lpProcessInformation
   STORE .T. TO llCreateProcessWasOpen
ENDIF

IF ASCAN(laDLLArray,"GETLASTERROR",1,-1,1,15) = 0
   DECLARE INTEGER GetLastError IN kernel32
   STORE .T. TO llGetLastErrorWasOpen
ENDIF

IF ASCAN(laDLLArray,"CLOSEHANDLE",1,-1,1,15) = 0
   DECLARE INTEGER CloseHandle IN kernel32 INTEGER hObject
   STORE .T. TO llCloseHandleWasOpen
ENDIF

IF ASCAN(laDLLArray,"GETEXITCODEPROCESS",1,-1,1,15) = 0
   DECLARE INTEGER GetExitCodeProcess IN WIN32API INTEGER hProcess, INTEGER @lpExitCode
   STORE .T. TO llGetExitCodeWasOpen
ENDIF

IF ASCAN(laDLLArray,"SLEEP",1,-1,1,15) = 0
   DECLARE Sleep IN kernel32 INTEGER dwMilliseconds
   STORE .T. TO llSleepWasOpen
ENDIF

* 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

*** Documentation On CreateProcess() function ***

* Was able to glean this documentation from the sources that follow but note that in some cases, it may be incomplete
* or inaccurate as the documentation was somewhat sketchy; here were the main sources:

* Microsoft Sources:
* Best article: titled "CreateProcess" in MSDN library
* http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/createprocess.asp
* This one has more detail on what should be in StartupInfo string if you want to change the appearance of the
* startup process:
* http://msdn.microsoft.com/archive/default.asp?url=/archive/en-us/dnarw98bk/html/makingmodifyingprocesses.asp
* and then an article that explains the difference between passing arguments via Param1 and 2:
* http://support.microsoft.com/default.aspx?scid=kb;en-us;175986 i.e. article Q175986

* Universal Thread examples and samples:
* UT Thread: 931761 Message: 939258 (Tracy Holzer)
* UT Thread: 936327 Message: 936339 (Claudio Rola)
* UT Thread: 936327 Message: 936342 (Marcia Akins)
* (note that these last 2 messages use function WaitForSingleObject() as part of their example but it seems like
*  later examples in the UT have moved away from using that to using GetExitCodeProcess() instead)

* Parameters for CreateProcess():
* [1] lcApplicationName: the application file (e.g. exe) to run including the full path if it is not in the current
*     directory; the string should NOT be in quotes even if the file path contains spaces somewhere in the path;
*     optional; if not passed, the information has to be in parameter 2, lcCommandLine, instead; supposed to be
*     null-terminated but it did not seem to make a difference either way
* [2] lcCommandLine: if the exe to be run includes any parameters, then the entire command line needs to be in this
*     parameter (otherwise, if there are no parameters for the exe to be run, then this parameter can be left empty);
*     if not empty, lcCommandLine needs to include the exe file to run and it's parameters; it is better to include
*     the full path or else various paths are searched (see MSDN documentation on CreateProcess() for path search
*     specifics); an example is "C:\Windows\Notepad.exe c:\Temp\MyFile.txt"; note that there has to be a space in
*     between the .exe file and the parameters and because of this, if the path to the exe file contains any spaces,
*     then the .exe filepath must be enclosed in it's own quotes so that the parse can determine where the exe file
*     name ends and the paraemters begin e.g. ["C:\Program Files\Acrobat\Acrobat.exe" c:\Temp\SomePdf.pdf]
* [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; the sample given to us was set to 1
* [6] lcCreationFlags: flags used when creating the process; in our case, flag is used to set priority for the
*     process creation so we use passed in parameter tnPriority (usually 32, "Normal")
* [7] lnEnvironment: 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 delimited 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 the sample we started with this was not used and sample wrapped its code in SET DEFAULT TO <something> before
*     and after calling this method: I think it would be better to specify the startup directory for the exe running
*     by passing a parameter instead of using SET DEFAULT
* [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 (the "D" is ascii character 68 which "tells" the function how many bytes
*     are in the string that is being passed - 68) 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
lnProcessAttributes = 0
lnThreadAttributes = 0
lnInheritHandles = 1
lnEnvironment = 0
lcStartupInfo = CHR(68) + REPLICATE(CHR(0),67)
lcProcessInfo = REPLICATE(CHR(0),16)

* create the process and capture the result
STORE CreateProcess(tcExeName,tcCommandLine,lnProcessAttributes,lnThreadAttributes,lnInheritHandles, ;
   tnPriority,lnEnvironment,tcStartupDir,@lcStartupInfo,@lcProcessInfo) TO lnResult

* if non-zero returned, process was successfully created
IF lnResult = 0

   STORE GetLastError() TO lnErrorNum

   ?? CHR(7)

   * Debug: could not get the 2nd line ("Please inform system administrator") to show up now matter what I did; turns
   * out that the null character at the end of tcExeName is causing WAIT WINDOW to stop at that point so strip it

   WAIT WINDOW "Error number " + LTRIM(STR(lnErrorNum)) + " occurred when trying to startup process: " + ;
      CHR(13) + LEFT(tcExeName,LEN(tcExeName)-1) + CHR(13) + "Please inform system administrator"

   * set our return value to indicate could not process
   STORE .F. TO llReturn

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 = DWordToNum(SUBSTR(lcProcessInfo,1,4))
   lnThreadHandle = DWordToNum(SUBSTR(lcProcessInfo,5,4))

   * wait until the termination of the program
   DOEVENTS

   DO WHILE .T.

      * reset exit code each loop
      STORE 0 TO lnExitCode

      * fetch the exit code from the app
      GetExitCodeProcess(lnProcessHandle,@lnExitCode)

      * if exitcode = 259, this means app not busy so exit
      IF lnExitCode # 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

*** Release Win Functions Opened ***

* only release if the functions were not previously opened so as to not knock out if a program
* program further up the stack is relying on them being open; can just test laDLLArray() again

IF NOT llCreateProcessWasOpen
   CLEAR DLLS CreateProcess
ENDIF
IF NOT llGetLastErrorWasOpen
   CLEAR DLLS GetLastError
ENDIF
IF NOT llCloseHandleWasOpen
   CLEAR DLLS CloseHandle
ENDIF
IF NOT llGetExitCodeWasOpen
   CLEAR DLLS GetExitCodeProcess
ENDIF
IF NOT llSleepWasOpen
   CLEAR DLLS Sleep
ENDIF

RETURN (llReturn)


PROCEDURE DWordToNum
* function to convert a DWord (double wide word, which is 4 bytes base 256) to it's numeric equivalent

* Written by: Albert Gostick
* Last Updated: Nov 24, 2004

PARAMETERS tcDWord

LOCAL lnReturn

* work from left to right with the first char being base 256 raised to zero, the 2nd char being
* 256 squared and so on; used constants as probably processes faster

STORE ASC(SUBSTR(tcDWord,1,1)) + ;
   ASC(SUBSTR(tcDWord,2,1)) * 256 + ;
   ASC(SUBSTR(tcDWord,3,1)) * 65536 + ;
   ASC(SUBSTR(tcDWord,4,1)) * 16777216 ;
   TO lnReturn

RETURN lnReturn
Albert
Précédent
Répondre
Fil
Voir

Click here to load this message in the networking platform