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


9 comments
August 24, 2006 •

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.
October 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
October 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
December 08, 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
December 08, 2006

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

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



Leo Mejia
January 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
August 09, 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
August 09, 2018

Rick,

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

Rob

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



Matthias
August 18, 2020

There's a bug in your code, in GetStartupInfo() you are checking for:

IF EMPTY(lnShowWindow) lnShowWindow = 1 ENDIF

But lnShowWindow = 0 (and therefore EMPTY) is a valid value! With lnShowWindow = 0 you can start a process "hidden", invisible in task bar and invisible in gui.

So you should replace it with IF VARTYPE(lnShowWindow) != "N" or something like that...

  • SW_HIDE (0) = Hides the window and activates another window.
  • SW_MAXIMIZE (3) = Maximizes the specified window.
  • SW_MINIMIZE (6) = Minimizes the specified window and activates the next top-level window in the z-order.
  • SW_RESTORE (9) = Activates and displays the window. If the window is minimized or maximized, Windows restores it to its original size and position. An application should specify this flag when restoring a minimized window.
  • SW_SHOW (5) = Activates the window and displays it in its current size and position.
  • SW_SHOWDEFAULT (10) = Sets the show state based on the SW_ flag specified in the STARTUPINFO structure passed to the CreateProcess function by the program that started the application. An application should call ShowWindow with this flag to set the initial show state of its main window.
  • SW_SHOWMAXIMIZED (3) = Activates the window and displays it as a maximized window.
  • SW_SHOWMINIMIZED (2) = Activates the window and displays it as a minimized window.
  • SW_SHOWMINNOACTIVE (7) = Displays the window as a minimized window. The active window remains active.
  • SW_SHOWNA (8) = Displays the window in its current state. The active window remains active.
  • SW_SHOWNOACTIVATE (4) = Displays a window in its most recent size and position. The active window remains active.
  • SW_SHOWNORMAL (1) = Activates and displays a window. If the window is minimized or maximized, Windows restores it to its original size and position. An application should specify this flag when displaying the window for the first time.

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



Rick Strahl
September 16, 2020

@Mattias - good catch.

Actually a more recent version of this code already has that in it.

IF VARTYPE(lnShowWindow) # "N"
  lnShowWindow = 1
ENDIF
 
© Rick Strahl, West Wind Technologies, 2003 - 2024