*--------------------------------------------------------------------------------------------------------------------------------------------------------
* (EN) AUTOGENERATED - ATTENTION!! - NOT INTENDED FOR EXECUTION!! USE ONLY FOR MERGING CHANGES AND STORING WITH SCM TOOLS!!
*--------------------------------------------------------------------------------------------------------------------------------------------------------
*< FOXBIN2PRG: Version="1.21" SourceFile="process.vcx" CPID="1252" /> (Solo para binarios VFP 9 / Only for VFP 9 binaries)
*
*
DEFINE CLASS api_apprun AS custom 
 	*< CLASSDATA: Baseclass="custom" Timestamp="" Scale="Pixels" Uniqueid="" />

	*<DefinedPropArrayMethod>
		*m: checkprocessexitcode
		*m: extractdword
		*m: getprochandle
		*m: killproc
		*m: launchapp
		*m: launchappandwait
		*m: parseprocessinfostruc
		*m: releasehandle
		*p: iccommandline
		*p: icerrormesage
		*p: icerrormessage
		*p: iclaunchdir
		*p: icwindowmode
		*p: inprocesshandle
		*p: inthreadhandle
	*</DefinedPropArrayMethod>

	PROTECTED inprocesshandle,inthreadhandle
	*<PropValue>
		iccommandline = space(0)
		icerrormesage = (space(0))
		iclaunchdir = (space(0))
		icwindowmode = (space(0))
		inprocesshandle = .NULL.
		inthreadhandle = .NULL.
		Name = "api_apprun"
	*</PropValue>
	
	PROCEDURE checkprocessexitcode
		LPARAMETER nProcessToCheck
		IF TYPE('nProcessToCheck') # 'N'
			nProcessToCheck = this.inProcessHandle
		ENDIF
		DECLARE SHORT GetExitCodeProcess IN Win32API AS CheckExitCode ;
			INTEGER hProcess, ;
			INTEGER @lpdwExitCode
		LOCAL nExitCode
		nExitCode = 0
		IF ! ISNULL(nProcessToCheck)
			IF CheckExitCode(nProcessToCheck, @nExitCode) = 1
				*	We retrieved an exit code (259 means still running, tho
				RETURN nExitCode
			ELSE
				*	Process did not exist in process table - no exit status
				this.icErrorMessage = 'Process to check not in active Process Table'
				RETURN NULL
			ENDIF
		ELSE
			this.icErrorMessage = 'NULL process handle passed to CheckProcessExitCode'
			RETURN NULL
		ENDIF
		
	ENDPROC

	PROCEDURE Destroy
		WITH THIS
			IF TYPE('.inThreadHandle') = 'N' AND NOT ISNULL(.inThreadHandle)
				.ReleaseHandle(.inThreadHandle)
				.inThreadHandle = NULL
			ENDIF
			IF TYPE('.inProcessHandle') = 'N' AND NOT ISNULL(.inProcessHandle)
				.ReleaseHandle(.inProcessHandle)
				.inProcessHandle = NULL
			ENDIF
		ENDWITH
		DODEFAULT()
		
			
			
	ENDPROC

	PROCEDURE extractdword
		LPARAMETER cStringToExtractFrom
		IF TYPE('cStringToExtractFrom')='C' AND LEN(cStringToExtractFrom) >= 4
			RETURN (((ASC(SUBST(cStringToExtractFrom,4,1))*256) + ;
									ASC(SUBST(cStringToExtractFrom,3,1)))*256 + ;
									ASC(SUBST(cStringToExtractFrom,2,1)))*256 + ;
									ASC(LEFT(cStringToExtractFrom,1))
		ELSE
			this.icErrorMessage = 'Invalid DWORD string passed for conversion'
			RETURN NULL
		ENDIF
		
	ENDPROC

	PROCEDURE getprochandle
		RETURN this.inProcessHandle
	ENDPROC

	PROCEDURE Init
		*
		*	API_AppRun - use the CreateProcess() API to launch, monitor, and kill an Executable
		*
		*	Properties:
		*
		*	inProcessHandle			(P)	ProcessHandle generated by CreateProcess()
		*	inThreadHandle			(P) ThreadHandle for First Thread of inProcessHandle
		*	icErrorMessage			R/O Error Message Detailed Description
		*	icCommandLine			R/W Command Line to launch via CreateProcess()
		*	icLaunchDir				R/W Directory to use as startup dir for CreateProcess()
		*	icWindowMode			R/W Window Start Mode, one of (HID, NOR, MIN, MAX) or empty
		*							defaults to empty, the default for the executable is used
		*
		*	Methods:
		*
		*	Init					(O) Command Line, (O) Start Dir, (O) Window Start Mode
		*							If sent, the icCommandLine, icLaunchDir and icWindowMode properties are set
		*	Destroy
		*	LaunchApp				// Launches .icComandLine from .icLaunchDir in .icWindowMode
		*							// NB - at least .icCommandLine must be set to not fail
		*							RETURNS: BOOL, check icErrorMessage on .F.
		*	LaunchAppAndWait		// Call LaunchApp() and wait on either user termination or process termination
		*							RETURNS: BOOL, check icErrorMessage on .F.
		*	CheckProcessExitCode	(O) Process handle to check, defaults to .inProcessHandle
		*							// Get Process named by Process Handle's Exit Code (259 = still running)
		*							RETURNS:  Integer, check on NULL, if NULL, check icErrorMessage
		*	ExtractDWORD			(R) String to convert
		*							//Converts a 4 byte or longer string to a DWORD integer
		*							RETURNS:  Integer, check on NULL, if NULL arg was invalid
		*	KillProc				(O) Process handle to Terminate, defaults to .inProcessHandle
		*							// Kills specified process using TerminateProcess()
		*							RETURNS:  BOOL, check icErrorMessage on .F.
		*	GetProcHandle			//  Returns the Process Handle for the current Process
		*							// NB - only useful for KillProc(), since Destroy will close the handle
		*							RETURNS:  Integer, check for NULL, if NULL no process was started yet
		*	ParsePROCESSINFOStruc	// Pulls the Process Handle and Thread Handle from the PROCESSINFO structure
		*							// Only used internally
		*	ReleaseHandle			(R)  Handle to Close
		*							//  Invokes CloseHandle() to explicitly release process/thread handles
		*							//  Only used internally, but can be externalized
		*							RETURNS:  BOOL, check .icErrorMessage if false
		*
		LPARAMETERS tcCommandLine, tcLaunchDir, tcWindowMode
		*	Set up the environment for the object
		LOCAL aDirTest[1,5]
		WITH THIS
			.icErrorMessage = ''
			.icCommandLine = ''
			.icLaunchDir = ''
			.icWindowMode = ''
			.inProcessHandle = NULL
			.inThreadHandle = NULL
			* store parameters if passed
			IF TYPE('tcCommandLine') = 'C'
				.icCommandLine = ALLTRIM(tcCommandLine)
			ENDIF
			DO CASE
			CASE TYPE('tcLaunchDir') # 'C'
				*	Not a character expression - ignore
			CASE ADIR(aDirTest, tcLaunchDir, 'D') # 1
				*	Either directory doesn't exist, or there's a wildcard in the expression
				.icErrorMessage = 'Invalid directory for startup passed to Init method'
			OTHERWISE
				*	Valid directory - save it
				.icLaunchDir = ALLTRIM(tcLaunchDir)
			ENDCASE
			DO CASE
			CASE TYPE('tcWindowMode') # 'C'
				*	Not passed in or not valid type
			CASE INLIST(PADR(UPPER(ALLTRIM(tcWindowMode)),3),'NOR','MIN','MAX','HID')
				*	Valid mode - set it
				.icWindowMode = PADR(UPPER(ALLTRIM(tcWindowMode)),3)
			OTHERWISE
				*	No a valid character string
				IF ! EMPTY(.icErrorMessage)
					.icErrorMessage = .icErrorMessage + ' & '
				ENDIF
				.icErrorMessage = .icErrorMessage + CHR(10) + CHR(13) + 'Invalid WindowMode passed to Init Method'
			ENDCASE
		ENDWITH
		RETURN .T.
		
	ENDPROC

	PROCEDURE killproc
		LPARAMETER nProcessToKill
		IF TYPE('nProcessToKill') # 'N'
			nProcessToKill = This.inProcessHandle
		ENDIF
		DECLARE SHORT TerminateProcess IN WIN32API AS KillProc ;
			INTEGER hProcess, ;
			INTEGER uExitCode
		LOCAL nResult
		IF ! ISNULL(nProcessToKill)
			nResult = KillProc(nProcessToKill,0)
			this.icErrorMessage = IIF(nResult = 0, 'TerminateProcess() could not kill process handle requested','')
		ELSE
			this.icErrorMessage = 'NULL handle passed to KillProc()'
			nResult = 0
		ENDIF
		RETURN (nResult = 1)
	ENDPROC

	PROCEDURE launchapp
		LOCAL cCommandLine, uFromDir, cWindowMode
		WITH This
			.icErrorMessage = ''
			IF TYPE('.icCommandLine') # 'C'
				*	Command line must be a character string
				.icErrorMessage = 'icCommand line must be set, and a string value'
				RETURN .F.
			ELSE
				cCommandLine = ALLTRIM(.icCommandLine)
			ENDIF
			IF TYPE('.icLaunchDir') # 'C' OR EMPTY(.icLaunchDir)
				*	If not a character string, pass a null pointer, defaulting to Current Working Dir
				uFromDir = 0
			ELSE
				*	Otherwise, null pad the string
				uFromDir = .icLaunchDir + CHR(0)
			ENDIF
			IF TYPE('.icWindowMode') # 'C'
				*	If not passed, set to null string
				cWindowMode = ''
			ELSE
				*	Translate the passed window mode to uppercase
				cWindowMode = UPPER(.icWindowMode)
			ENDIF
			*	This API call does the work.  The parameters are as follows:
			*		lpszModuleName - ptr-> file name of module to execute.  Since we aren't launching .CPLs, do not use
			*		lpszCommandLine - ptr-> command to execute, as passed in method
			*		lpSecurityAttributesProcess - ptr-> SECURITY_ATTRIBUTES structure for Process.  Pass a null pointer
			*		lpSecurityAttributesThread - ptr-> SECURITY_ATTRIBUTES structure for first thread.  Pass a null pointer
			*		bInheritHandles - whether or not chlid inherits parent handles.  Since no SECURITY_ATTRIBUTES passed, default to FALSE
			*		dwCreateFlags - Process Creation Mode flag set.  we use the default mode at normal priority, ie 0
			*		lpvEnvironment	- ptr-> a set of environment strings as if a MULTI_SZ.  We don't set, so pass a null pointer
			*		lpszStartupDir - ptr-> the starting directory.  If none provided to method, pass a null pointer
			*		lpStartInfo - ptr-> a STARTUPINFO structure.  We use one structure member at times.
			*		lpProcessInfo - ptr-> a PROCESS_INFORMATION structure, used to return PID/PHANDLE detail.  We use one member
			DECLARE SHORT CreateProcess IN WIN32API AS CrPr ;
				STRING lpszModuleName, ;
				STRING @lpszCommandLine, ;
				STRING lpSecurityAttributesProcess, ;
				STRING lpSecurityAttributesThread, ;
				SHORT bInheritHandles, ;
				INTEGER dwCreateFlags, ;
				STRING lpvEnvironment, ;
				STRING lpszStartupDir, ;
				STRING @lpStartInfo, ;
				STRING @lpProcessInfo
		
			LOCAL cProcessInfo, cStartUpInfo
		
			*	Make default Structures for the CreateProcess call
			*
			*	ProcessInfo - 4 bytes, a Process handle, a Thread Handle, a (DWORD) ProcessId and a (DWORD) ThreadID
			*					we save the Process handle and return it to caller in tnReturnProcessHandle
		
			cProcessInfo = REPL(CHR(0),16)
		
			*	StartUpInfo is a 68 byte long complex structure;  we either have 68 bytes with a cb member (byte 1) 68
			*	or with cb of 68, dwFlag low order byte (byte 45) of 1, and low order byte wShowWindow (byte 49) set to
			*	the SW_ value appropriate for the Window Mode desired.
		
			DO CASE
			CASE cWindowMode = 'HID'
				*	Hide - use STARTF_USESHOWFLAG and value of 0
				cStartUpInfo = CHR(68) + ;
								REPL(CHR(0),43) + ;
								CHR(1) + ;
								REPL(CHR(0),23)
			CASE cWindowMode = 'NOR'
				*	Normal - use STARTF_USESHOWFLAG and value of 1
				cStartUpInfo = CHR(68) + ;
								REPL(CHR(0),43) + ;
								CHR(1) + ;
								REPL(CHR(0),3) + ;
								CHR(1) + ;
								REPL(CHR(0),19)
			CASE cWindowMode = 'MIN'
				*	Minimize - use STARTF_USESHOWFLAG and value of 2
				cStartUpInfo = CHR(68) + ;
								REPL(CHR(0),43) + ;
								CHR(1) +  ;
								REPL(CHR(0),3) + ;
								CHR(2) + ;
								REPL(CHR(0),19)
			CASE cWindowMode = 'MAX'
				*	Maximize - use STARTF_USESHOWFLAG and value of 3
				cStartUpInfo = CHR(68) + ;
								REPL(CHR(0),43) + ;
								CHR(1) +  ;
								REPL(CHR(0),3) + ;
								CHR(3) + ;
								REPL(CHR(0),19)
			OTHERWISE
				*	Use default of application
				cStartUpInfo = CHR(68) + REPL(CHR(0),67)
			ENDCASE
			LOCAL lResult
			lResult = CrPr(	0, ;
							cCommandLine, ;
							0, 0, 0, 0, 0, ;
							uFromDir, ;
							@cStartUpInfo, ;
							@cProcessInfo)
			*	Strip the handles from the PROCESS_INFORMATION structure and save in private properties
			IF lResult = 1
				.ParseProcessInfoStruc(cProcessInfo)
				RETURN .T.
			ELSE
				.icErrorMessage = 'Process Specified by icCommandLine could not be started'
				RETURN .F.
			ENDIF
		ENDWITH
	ENDPROC

	PROCEDURE launchappandwait
		#Define cnINFINITE 		0xFFFFFFFF
		#Define cnHalfASecond	500	&& milliseconds
		#Define cnTimedOut		258	&& 0x0102
		
		Local loEx As Exception
		
		*	We need some API calls, declare here
		*	GetCurrentProcess returns the pseudohandle of the current process (ie VFP instance)
		Declare Integer GetCurrentProcess In WIN32API As GetCurrProc
		*	WaitForIdleInput waits until the application is instantiated and at it's event loop
		Declare Integer WaitForInputIdle In WIN32API As WaitInpIdle ;
			INTEGER nProcessHandle, ;
			INTEGER nWaitForDuration
		*	WaitForSingleObject waits until the handle in parm 1 is signalled or the timeout period expires
		Declare Integer WaitForSingleObject In WIN32API As WaitOnAppExit ;
			INTEGER hProcessHandle, ;
			INTEGER dwTimeOut
		*	Save the Process handle if any and the result of LaunchApp
		*	Fire the app and save the process handle
		Local uResult
		uResult = 0
		With This
			.icErrorMessage = ''
			If .LaunchApp()
				uResult = 1
				*	It's been launched;  wait until we're idling along
				=WaitInpIdle(GetCurrProc(),cnINFINITE)
				*	As long as the other process exists, wait for it
				Do While WaitOnAppExit(.inProcessHandle, cnHalfASecond) = cnTimedOut
					*	Give us an out in case the other app hangs - let
					Try
						* 01.10.2008
						* nu stiu de ce da eroare la inkey()
						lnInkey = Inkey()
						*!*				StrToFile('inkey: ' + Transform(lnInkey) + ' type: ' + Type('lnInkey'),"c:\inkey.txt")
						If lnInkey = 27
							*	Still running but we aren't waiting - return a -1 as a warning
							.icErrorMessage = 'Process started but user did not wait on termination'
							uResult = 0
							Exit
						Endif
					Catch To loEx
						* do nothing
					Endtry
					* 01.10.2008 ^
				Enddo
			Else
				*	Return 0 to indicate failure
				uResult = 0
			Endif
		Endwith
		Return (uResult = 1)
		
	ENDPROC

	PROCEDURE parseprocessinfostruc
		LPARAMETER cProcessInfoStructure
		WITH This
			.inProcessHandle = .ExtractDWORD(cProcessInfoStructure)
			.inThreadHandle = .ExtractDWORD(SUBST(cProcessInfoStructure,5))
		ENDWITH
				
		
	ENDPROC

	PROTECTED PROCEDURE releasehandle
		LPARAMETER nHandleToRelease
		LOCAL nResult
		*	Use CloseHandle(), returns a BOOL;  0 = False
		DECLARE SHORT CloseHandle IN Win32API AS CloseHand INTEGER nHandleToClose
		IF TYPE('nHandleToRelease') = 'N' AND ! ISNULL(nHandleToRelease)
			nResult = CloseHand(nHandleToRelease)
			this.icErrorMessage = IIF(nResult = 0, 'TerminateProcess() failed to close handle','')
		ELSE
			this.icErrorMessage = 'Invalid handle passed to ReleaseHandle() invocation'
			nResult = 0
		ENDIF
		RETURN (nResult = 1)
	ENDPROC

ENDDEFINE
