Rick Strahl's Weblog
Rick Strahl's FoxPro and Web Connection Weblog
White Papers | Products | Message Board | News |

Using CreateProcess API instead of the FoxPro ! RUN command


7 comments
Thursday, August 24, 2006, 10:08:00 PM

A number of people on the Universal Thread were asking for code to execute an external program because of problems with the RUN command. The RUN command has a number of limitations that make it less than ideal for some operations.

 

I can’t remember all the details now but the command length is limited, it can’t deal with long filenames well and it doesn’t work if you need to pipe output into a file.

 

As an alternative you can use the CreateProcess API which gets you most of this functionality (except the piping which I couldn’t get to work in Fox code – but did in wwIPStuff using a DLL wrapper around CreateProcess).

 

Here’s the code for the CreateProcess class I use for most of my apps. To use:

 

? CreateProcess("c:\windows\Notepad.exe",;

                ["c:\westwind\wconnect\wcscripts\test.wcs"],1,.T.)

 

Which opens Notepad, with a parameter (the quotes are required only if there are spaces in the parameters), ShowWindow mode and waiting for completion:

 

************************************************************************

* wwAPI :: Createprocess

****************************************

***  Function: Calls the CreateProcess API to run a Windows application

***    Assume: Gets around RUN limitations which has command line

***            length limits and problems with long filenames.

***            Can do everything EXCEPT REDIRECTION TO FILE!

***      Pass: lcExe - Name of the Exe

***            lcCommandLine - Any command line arguments

***    Return: .t. or .f.

************************************************************************

FUNCTION Createprocess(lcExe,lcCommandLine,lnShowWindow,llWaitForCompletion)

LOCAL hProcess, cProcessInfo, cStartupInfo

 

DECLARE INTEGER CreateProcess IN kernel32 as _CreateProcess;

    STRING   lpApplicationName,;

    STRING   lpCommandLine,;

    INTEGER  lpProcessAttributes,;

    INTEGER  lpThreadAttributes,;

    INTEGER  bInheritHandles,;

    INTEGER  dwCreationFlags,;

    INTEGER  lpEnvironment,;

    STRING   lpCurrentDirectory,;

    STRING   lpStartupInfo,;

    STRING @ lpProcessInformation

 

 

cProcessinfo = REPLICATE(CHR(0),128)

cStartupInfo = GetStartupInfo(lnShowWindow)

 

IF !EMPTY(lcCommandLine)

   lcCommandLine = ["] + lcExe + [" ]+ lcCommandLine

ELSE

   lcCommandLine = ""

ENDIF

 

lnResult =  _CreateProcess(lcExe,lcCommandLine,0,0,1,0,0,;

                           SYS(5)+CURDIR(),cStartupInfo,@cProcessInfo)

 

lhProcess = CHARTOBIN( SUBSTR(cProcessInfo,1,4) )

 

IF llWaitForCompletion

   #DEFINE WAIT_TIMEOUT 0x00000102

   DECLARE INTEGER WaitForSingleObject IN kernel32.DLL ;

         INTEGER hHandle, INTEGER dwMilliseconds

 

   DO WHILE .T.

       *** Update every 100 milliseconds

       IF WaitForSingleObject(lhProcess, 100) != WAIT_TIMEOUT

          EXIT

        ELSE

           DOEVENTS

        ENDIF

   ENDDO

ENDIF

 

 

DECLARE INTEGER CloseHandle IN kernel32.DLL ;

        INTEGER hObject

 

CloseHandle(lhProcess)

 

RETURN IIF(lnResult=1,.t.,.f.)

 

FUNCTION getStartupInfo(lnShowWindow)

LOCAL lnFlags

* creates the STARTUP structure to specify main window

* properties if a new window is created for a new process

 

IF EMPTY(lnShowWindow)

  lnShowWindow = 1

ENDIF

 

*| typedef struct _STARTUPINFO {

*| DWORD cb; 4

*| LPTSTR lpReserved; 4

*| LPTSTR lpDesktop; 4

*| LPTSTR lpTitle; 4

*| DWORD dwX; 4

*| DWORD dwY; 4

*| DWORD dwXSize; 4

*| DWORD dwYSize; 4

*| DWORD dwXCountChars; 4

*| DWORD dwYCountChars; 4

*| DWORD dwFillAttribute; 4

*| DWORD dwFlags; 4

*| WORD wShowWindow; 2

*| WORD cbReserved2; 2

*| LPBYTE lpReserved2; 4

*| HANDLE hStdInput; 4

*| HANDLE hStdOutput; 4

*| HANDLE hStdError; 4

*| } STARTUPINFO, *LPSTARTUPINFO; total: 68 bytes

 

#DEFINE STARTF_USESTDHANDLES 0x0100

#DEFINE STARTF_USESHOWWINDOW 1

#DEFINE SW_HIDE 0

#DEFINE SW_SHOWMAXIMIZED 3

#DEFINE SW_SHOWNORMAL 1

 

lnFlags = STARTF_USESHOWWINDOW

 

RETURN binToChar(80) +;

    binToChar(0) + binToChar(0) + binToChar(0) +;

    binToChar(0) + binToChar(0) + binToChar(0) + binToChar(0) +;

    binToChar(0) + binToChar(0) + binToChar(0) +;

    binToChar(lnFlags) +;

    binToWordChar(lnShowWindow) +;

    binToWordChar(0) + binToChar(0) +;

    binToChar(0) + binToChar(0) + binToChar(0) + REPLICATE(CHR(0),30)

 

************************************************************************

FUNCTION CharToBin(lcBinString,llSigned)

****************************************

***  Function: Binary Numeric conversion routine.

***            Converts DWORD or Unsigned Integer string

***            to Fox numeric integer value.

***      Pass: lcBinString -  String that contains the binary data

***            llSigned    -  if .T. uses signed conversion

***                           otherwise value is unsigned (DWORD)

***    Return: Fox number

************************************************************************

LOCAL m.i, lnWord

 

lnWord = 0

FOR m.i = 1 TO LEN(lcBinString)

 lnWord = lnWord + (ASC(SUBSTR(lcBinString, m.i, 1)) * (2 ^ (8 * (m.i - 1))))

ENDFOR

 

IF llSigned AND lnWord > 0x80000000

  lnWord = lnWord - 1 - 0xFFFFFFFF

ENDIF

 

RETURN lnWord

*  wwAPI :: CharToBin

 

************************************************************************

FUNCTION BinToChar(lnValue)

****************************************

***  Function: Creates a DWORD value from a number

***      Pass: lnValue - VFP numeric integer (unsigned)

***    Return: binary string

************************************************************************

Local byte(4)

If lnValue < 0

    lnValue = lnValue + 4294967296

EndIf

byte(1) = lnValue % 256

byte(2) = BitRShift(lnValue, 8) % 256

byte(3) = BitRShift(lnValue, 16) % 256

byte(4) = BitRShift(lnValue, 24) % 256

RETURN Chr(byte(1))+Chr(byte(2))+Chr(byte(3))+Chr(byte(4))

*  wwAPI :: BinToChar

 

************************************************************************

FUNCTION BinToWordChar(lnValue)

****************************************

***  Function: Creates a DWORD value from a number

***      Pass: lnValue - VFP numeric integer (unsigned)

***    Return: binary string

************************************************************************

RETURN Chr(MOD(m.lnValue,256)) + CHR(INT(m.lnValue/256))

 

Some of that code originated from Christof Wollenhaupt with a number of modifications made to support wait operations and easier access to the command line parameters.

 

To support piping etc. wwIPStuff.dll (part of West Wind Internet Protocols or West Wind Client Tools) includes a wrapper that does something like this:

 

************************************************************************

* wwAPI :: CreateprocessEx

****************************************

***  Function: Calls the CreateProcess API to run a Windows application

***    Assume: Gets around RUN limitations which has command line

***            length limits and problems with long filenames.

***            Can do Redirection

***            Requires wwIPStuff.dll to run!

***      Pass: lcExe - Name of the Exe

***            lcCommandLine - Any command line arguments

***    Return: .t. or .f.

************************************************************************

FUNCTION CreateProcessEx(lcExe,lcCommandLine,lcStartDirectory,;

                         lnShowWindow,llWaitForCompletion,lcStdOutputFilename)

 

DECLARE INTEGER wwCreateProcess IN wwIPStuff.DLL AS _wwCreateProcess  ;

   String lcExe, String lcCommandLine, INTEGER lnShowWindow,;

   INTEGER llWaitForCompletion, STRING lcStartupDirectory, STRING StdOutFile

  

IF EMPTY(lcStdOutputFileName)

  lcStdOutputFileName = NULL

ENDIF

IF EMPTY(lcStartDirectory)

  lcStartDirectory = CHR(0)

ENDIF

 

IF !EMPTY(lcCommandLine)

   lcCommandLine = ["] + lcExe + [" ]+ lcCommandLine

ELSE

   lcCommandLine = ""

ENDIF

 

IF llWaitForCompletion

   lnWait = 1

ELSE

   lnWait = 0

ENDIF

IF EMPTY(lnShowWindow)

   lnShowWindow = 4

ENDIF  

 

lnResult = _wwCreateProcess(lcExe,lcCommandLine,lnShowWindow,lnWait,;

                            lcStartDirectory,lcStdOutputFileName)

  

RETURN IIF(lnResult == 1, .t. , .f.)

ENDFUNC

 

The main reason for this routine is the ability to write Standard output to a file. I use this in a number of applications like Help Builder for example, where I run the help compiler and need to capture the output from the execution of the compiler and display it later.

Posted in:

Feedback for this Weblog Entry


Re: Using CreateProcess API instead of the FoxPro ! RUN command



P.J.
Oct 12, 2006

I have been using the code from this article, cross-referencing it with the code from this KB article:

http://support.microsoft.com/kb/129796

The application I'm trying to call from VFP returns an exit code which I am trying to acquire. The KB article uses GetExitCodeProcess to get this value, but I cannot seem to get it to work in VFP. I have added the following code just before the CloseHandle call in the CreateProcess function:

local nExitCode nExitCode = 0

DECLARE INTEGER GetExitCodeProcess IN kernel32.DLL ; INTEGER hProcess, ; INTEGER lpExitCode =GetExitCodeProcess(lhProcess, @nExitCode)

When it gets to the last line, I receive a "Declare DLL call caused an exception" message. Any insight on this would be GREATLY appreciated.

Re: Using CreateProcess API instead of the FoxPro ! RUN command



Rick Strahl
Oct 12, 2006

The declaration needs to be changed so that lpExitCode is an input parameter:

INTEGER @lpExitCode

should do the trick

Re: Using CreateProcess API instead of the FoxPro ! RUN command



adx
Dec 8, 2006

In what versions of VFoxPro does this function work? Couldn't run it in VFP6.

Re: Using CreateProcess API instead of the FoxPro ! RUN command



Rick Strahl
Dec 8, 2006

Not really sure - I think BITRSHIFT was introduced in VFP 8.

Re: Using CreateProcess API instead of the FoxPro ! RUN command



Leo Mejia
Jan 12, 2007

What would be the proper way to Trace a process that you start in this manner. I use the CreateProcess but it ignores any asserts. Any clues.

re: Using CreateProcess API instead of the FoxPro ! RUN command



Robert
Aug 9, 2018

Rick,

I can get start a process using createprocess() but the lhProcess number is different to the PID listed in the TaskManager.

So when I use OpenProcess(PROCESS_QUERY_INFORMATION+PROCESS_VM_READ,0,lhProcess) it fails to open the handle.

Is there a way to get the correct pid back or is enumerating all the processes the only way to go?

Rob

re: Using CreateProcess API instead of the FoxPro ! RUN command



Robert
Aug 9, 2018

Rick,

Sorry I forgot to mention that I'm running a VFP compiled exe with createprocess()

Rob

 
© Rick Strahl, West Wind Technologies, 2003 - 2019