Volání externích programů

Určitě jste již někdy potřebovali spustit externí program a čekat,až skončí. VFP sice má příkaz RUN, ale jaksi to není ono.

Tento článek by nevznikl nebýt dvou věcí:
1) mailu Maroše Klempy ze dne 19.3.2001 v konferenci FoxPro
2) a mé nutné potřeby to taky konečně vyřešit

Úvod

Pro spouštění procesů se ve Woknech používá API funkce CreateProcess(). Jelikož tato funkce využívá struktur, které VFP bohužel nativně nepodporuje, vytvořil jsem pomocné třídy.
Tento článek si neklade za cíl podrobně popsat funkci CreateProcess(), parametrů a potřebných struktur - kompletní popis lze nalézt v MSDN , ale poukázat na využití této funkce.

Popis funkce CreateProcess()

BOOL CreateProcess(
  LPCTSTR lpApplicationName,                 // ukazatel na název spouštěného modulu
  LPTSTR lpCommandLine,                      // ukazatel na řetězec příkazového řádku
  LPSECURITY_ATTRIBUTES lpProcessAttributes, // bezpečnostní atributy procesu
  LPSECURITY_ATTRIBUTES lpThreadAttributes,  // bezpečnostní atributy threadu
  BOOL bInheritHandles,                      // handle inheritance flag
  DWORD dwCreationFlags,                     // příznak vytvoření procesu
  LPVOID lpEnvironment,                      // ukazatel na nový blok prostředí
  LPCTSTR lpCurrentDirectory,                // ukazatel na název startovacího adresáře
  LPSTARTUPINFO lpStartupInfo,               // ukazatel na STARTUPINFO
  LPPROCESS_INFORMATION lpProcessInformation // ukazatel na PROCESS_INFORMATION
);
lpApplicationName
Tento řetězec specifikuje plnou cestu a název modulu nebo jeho části,jenž se má být spuštěna.
Tento parametr nemusí být zadán.Pokud je však uveden,musí být uveden v lpCommandLine jako první.

lpCommandLine
Ukazatel na řetězec specifikující příkazový řádek. Tento parametr nemusí být zadán, ale pak musí být uveden parametr lpApplicationName.

lpProcessAttributes
Ukazatel na strukturu SECURITY_ATTRIBUTES. Používá se jen ve Win NT.

lpThreadAttributes
Ukazatel na strukturu SECURITY_ATTRIBUTES. Používá se jen ve Win NT.

bInheritHandles
Příznak,zda nový proces zdědí handly z volaného procesu. Jestliže ANO , pak každý zděděný otevřený handle ve volaném procesu je předán novému procesu. Předané handly mají stejná přístupová práva,jako původní,zděděné.

dwCreationFlags
Kumulativní příznak pro vytvoření procesu. Je to součet některých z níže uvedených konstant:
CREATE_DEFAULT_ERROR_MODE, CREATE_NEW_CONSOLE, CREATE_NEW_PROCESS_GROUP, CREATE_SEPARATE_WOW_VDM, CREATE_SHARED_WOW_VDM, CREATE_SUSPENDED, CREATE_UNICODE_ENVIRONMENT, DEBUG_PROCESS, DEBUG_ONLY_THIS_PROCESS, DETACHED_PROCESS
a NORMAL_PRIORITY_CLASS nebo IDLE_PRIORITY_CLASS nebo HIGH_PRIORITY_CLASS nebo REALTIME_PRIORITY_CLASS

Nás zajímá pouze konstanta NORMAL_PRIORITY_CLASS, která určuje prioritu nově spuštěného procesu.

lpEnvironment
Ukazatel na blok prostředí. Pokud parametr není zadán, pak nový proces použije blok prostředí volajícího procesu.

lpCurrentDirectory
Ukazatel na řetězec , který specifikuje počáteční (startovací) adresář. Tento adresář musí obsahovat disk. Pokud parametr není předán, pak nový proces použije startovací adresář volajícího procesu.

lpStartupInfo
Ukazatel na strukturu STARTUPINFO popisující jaká má být velikost, pozice atd. hlavního okna procesu.

lpProcessInformation
Ukazatel na strukturu PROCESS_INFORMATION, která po spuštění nového procesu obsahuje jeho identifikaci.

Pokud se funkce ukončí korektně, pak vrací číslo > 0.
Pokud se funkce neukončí korektně, pak vrací 0. Upřesňující kód chyby lze zjistit pomocí API funkce GetLastError().

Popis struktury STARTUPINFO

typedef struct _STARTUPINFO { // si
    DWORD   cb;
    LPTSTR  lpReserved;
    LPTSTR  lpDesktop;
    LPTSTR  lpTitle;
    DWORD   dwX;
    DWORD   dwY;
    DWORD   dwXSize;
    DWORD   dwYSize;
    DWORD   dwXCountChars;
    DWORD   dwYCountChars;
    DWORD   dwFillAttribute;
    DWORD   dwFlags;
    WORD    wShowWindow;
    WORD    cbReserved2;
    LPBYTE  lpReserved2;
    HANDLE  hStdInput;
    HANDLE  hStdOutput;
    HANDLE  hStdError; 
} STARTUPINFO, *LPSTARTUPINFO;
cb
Velikost struktury v bytech

lpReserved
Rezervováno. Člen nesmí být vyplněn před posláním struktury do CreateProcess()

lpDesktop
Pouze WIN NT: Ukazatel na řetězec specifikující buď název desktopu, nebo název desktopu a okna pro tento proces.

lpTitle
Pro konzole titulek nového okna. Pro GUI aplikace nesmí být tento člen vyplněn.

dwX, dvY
Ignorováno, pokud v dwFlags není uvedeno STARTF_USEPOSITION. Určují pozici levého horního rohu okna.

dwXSize, dwYSize
Ignorováno, pokud v dwFlags není uvedeno STARTF_USESIZE. Určují šířku a výšku okna.

dwXCountChars, dwYCountChars
Ignorováno, pokud v dwFlags není uvedeno STARTF_USECOUNTCHARS. Pro konzolové aplikace určují šířku a výšku okna ve znacích.

dwFillAttribute
Ignorováno, pokud v dwFlags není uvedeno STARTF_USEFILLATTRIBUTE. Pro konzolové aplikace určuje počáteční barvu textu a pozadí.

dwFlags
Bitové pole určující, které z vyplněných členů se mají použít:
Hodnota Popis
STARTF_USESHOWWINDOW Pokud tato hodnota není specifikována, člen wShowWindow je ignorován.
STARTF_USEPOSITION Pokud tato hodnota není specifikována, členové dwX a dwY jsou ignorovány.
STARTF_USESIZE Pokud tato hodnota není specifikována, členové dwXSize a dwYSize jsou ignorovány.
STARTF_USECOUNTCHARS Pokud tato hodnota není specifikována, členové dwXCountChars a dwYCountChars jsou ignorovány.
STARTF_USEFILLATTRIBUTE Pokud tato hodnota není specifikována, člen dwFillAttribute je ignorován.
STARTF_FORCEONFEEDBACK
STARTF_FORCEOFFFEEDBACK
STARTF_USESTDHANDLES

wShowWindow
Ignorováno, pokud v dwFlags není uvedeno STARTF_USESHOWWINDOW. Hodnota odpovídá konstantám SW_ pro zobrazení okna.

cbReserved2
Rezervováno, musí být vždy 0.

lpReserved2
Rezervováno, musí být vždy NULL.

hStdInput
Ignorováno, pokud v dwFlags není uvedeno STARTF_USESTDHANDLES. Specifikuje standardní handle pro vstup.

hStdOutput
Ignorováno, pokud v dwFlags není uvedeno STARTF_USESTDHANDLES. Specifikuje standardní handle pro výstup.

hStdError
Ignorováno, pokud v dwFlags není uvedeno STARTF_USESTDHANDLES. Specifikuje standardní handle pro hlášení chyb.

Popis struktury PROCESS_INFORMATION

typedef struct _PROCESS_INFORMATION { // pi
    HANDLE hProcess; 
    HANDLE hThread;
    DWORD dwProcessId;
    DWORD dwThreadId; 
} PROCESS_INFORMATION;
hProcess
Vrátí handle nově vytvořeného procesu,které je pak používáno všemi funkcemi pro práci s procesem.

hThread
Vrátí handle hlavního threadu (vlákna) nově vytvořeného procesu.

dwProcessId
Vrátí globální identifikátor procesu. Tato hodnota je platná od vytvoření procesu do jeho ukončení.

dwThreadId
Vrátí globální identifikátor threadu. Tato hodnota je platná od vytvoření threadu do jeho ukončení.

Zjištění ukončení běžícího procesu

Na zjištění zda běžící proces byl již ukončen se dají použít dvě API funkce - WaitForSingleObject() nebo GetExitCodeProcess().
Dají se použít dva různé způsoby:

Deklarace API funkcí

DECLARE INTEGER CreateProcess IN kernel32.DLL ;
   STRING lpApplicationName, ;
   STRING lpCommandLine, ;
   INTEGER lpProcessAttributes, ;
   INTEGER lpThreadAttributes, ;
   INTEGER bInheritHandles, ;
   INTEGER dwCreationFlags, ;
   INTEGER lpEnvironment, ;
   STRING @lpCurrentDirectory, ;
   STRING @lpStartupInfo, ;
   STRING @lpProcessInformation

DECLARE INTEGER WaitForSingleObject IN kernel32.DLL ;
   INTEGER hHandle, INTEGER dwMilliseconds

DECLARE INTEGER CloseHandle IN kernel32.DLL INTEGER hObject

DECLARE INTEGER GetLastError IN kernel32.DLL

DECLARE INTEGER GetExitCodeProcess IN kernel32.dll INTEGER,INTEGER @

Definice potřebných tříd

* (INTEGER)  LPTSTR  lpReserved
* (INTEGER)  LPTSTR  lpDesktop
* (INTEGER)  LPTSTR  lpTitle
* (INTEGER)  DWORD   dwX
* (INTEGER)  DWORD   dwY
* (INTEGER)  DWORD   dwXSize
* (INTEGER)  DWORD   dwYSize 
* (INTEGER)  DWORD   dwXCountChars
* (INTEGER)  DWORD   dwYCountChars 
* (INTEGER)  DWORD   dwFillAttribute
* (INTEGER)  DWORD   dwFlags
* (INTEGER)  WORD    wShowWindow 
* (INTEGER)  WORD    cbReserved2
* (INTEGER)  LPBYTE  lpReserved2 (??? vynecháno ???)
* (INTEGER)  HANDLE  hStdInput 
* (INTEGER)  HANDLE  hStdOutput
* (INTEGER)  HANDLE  hStdError

DEFINE CLASS _struc_STARTUPINFO AS DataEnvironment
   PROTECT lpReserved,lpDesktop,lpTitle,cbReserved2,lpReserved2
   lpReserved=.NULL.
   lpDesktop=.NULL.
   lpTitle=.NULL.
   dwX=0
   dwY=0
   dwXSize=0
   dwYSize=0
   dwXCountChars=0
   dwYCountChars=0
   dwFillAttribute=0
   dwFlags=0
   wShowWindow=0
   cbReserved2=0
   lpReserved2=.NULL.
   hStdInput=0
   hStdOutput=0
   hStdError=0

   PROCEDURE SizeOf()
      RETURN 68
   ENDPROC

   * Vytvoří string
   PROCEDURE CreateString()
      RETURN IToC4(This.SizeOf())+;
             IToC4(IIF(ISNULL(This.lpReserved),0,0))+;
             IToC4(IIF(ISNULL(This.lpDesktop),0,0))+;
             IToC4(IIF(ISNULL(This.lpTitle),0,0))+;
             IToC4(This.dwX)+;
             IToC4(This.dwY)+;
             IToC4(This.dwXSize)+;
             IToC4(This.dwYSize)+;
             IToC4(This.dwXCountChars)+;
             IToC4(This.dwYCountChars)+;
             IToC4(This.dwFillAttribute)+;
             IToC4(This.dwFlags)+;
             IToC4(This.wShowWindow)+;
             IToC4(This.cbReserved2)+;
             IToC4(IIF(ISNULL(This.lpReserved2),0,0))+;
             IToC4(This.hStdInput)+;
             IToC4(This.hStdOutput)+;
             IToC4(This.hStdError)

   ENDPROC

ENDDEFINE

* (INTEGER)  HANDLE hProcess
* (INTEGER)  HANDLE hThread
* (INTEGER)  DWORD dwProcessId 
* (INTEGER)  DWORD dwThreadId 

DEFINE CLASS _struc_PROCESS_INFORMATION AS DataEnvironment
   hProcess=0
   hThread=0
   dwProcessId=0
   dwThreadId=0


   * Vytvoří string
   PROCEDURE CreateString()
      RETURN IToC4(This.hProcess)+;
             IToC4(This.hThread)+;
             IToC4(This.dwProcessId)+;
             IToC4(This.dwThreadId)
   ENDPROC

   * Ze stringu načte zpět informace
   PROCEDURE ReadString(lcStruc)
      This.hProcess=C4ToI(LEFT(lcStruc,4))
      This.hThread=C4ToI(SUBST(lcStruc,5,4))
      This.dwProcessId=C4ToI(SUBST(lcStruc,9,4))
      This.dwThreadId=C4ToI(RIGHT(lcStruc,4))
   ENDPROC

ENDDEFINE

Konverzní procedury

**********************************
PROCEDURE C4ToI(lcInt) && Číslo v řetězcovém tvaru WINAPI 32 bitů
RETURN ASC(LEFT(lcInt,1))+;
       ASC(SUBS(lcInt,2,1))*256+;
       ASC(SUBS(lcInt,3,1))*65536+;
       ASC(RIGHT(lcInt,1))*256*65536 && Vrať číslo v decimálním tvaru

**********************************
PROCEDURE IToC4(liVal) && převede číslo na 4 bajtový řetězec
LOCAL lcVal,lii

lii=INT(liVal/256) && Vyděl 256
lcVal=CHR(liVal-lii*256) && Převeď rozdíl na znak a přičti ho
liVal=lii && Zapiš novou hodnotu pro převod

lii=INT(liVal/256) && Vyděl 256
lcVal=lcVal+CHR(liVal-lii*256)
liVal=lii && Zapiš novou hodnotu pro převod

lii=INT(liVal/256) && Vyděl 256
lcVal=lcVal+CHR(liVal-lii*256)
liVal=lii && Zapiš novou hodnotu pro převod

lii=INT(liVal/256) && Vyděl 256
RETURN lcVal+CHR(liVal-lii*256) && Převeď rozdíl na znak a přičti ho

Potřebné konstanty

* Funkce CreateProcess()
#DEFINE CREATE_DEFAULT_ERROR_MODE   0x04000000
#DEFINE CREATE_NEW_CONSOLE          0x00000010
#DEFINE CREATE_NEW_PROCESS_GROUP    0x00000200
#DEFINE CREATE_SEPARATE_WOW_VDM     0x00000800
#DEFINE CREATE_SHARED_WOW_VDM       0x00001000
#DEFINE CREATE_SUSPENDED            0x00000004
#DEFINE CREATE_UNICODE_ENVIRONMENT  0x00000400
#DEFINE DEBUG_PROCESS               0x00000001
#DEFINE DEBUG_ONLY_THIS_PROCESS     0x00000002
#DEFINE DETACHED_PROCESS            0x00000008

#DEFINE NORMAL_PRIORITY_CLASS       0x00000020
#DEFINE IDLE_PRIORITY_CLASS         0x00000040
#DEFINE HIGH_PRIORITY_CLASS         0x00000080
#DEFINE REALTIME_PRIORITY_CLASS     0x00000100

* Nastavení členů pro strukturu STARTUPINFO
#DEFINE STARTF_USESHOWWINDOW    0x00000001
#DEFINE STARTF_USESIZE          0x00000002
#DEFINE STARTF_USEPOSITION      0x00000004
#DEFINE STARTF_USECOUNTCHARS    0x00000008
#DEFINE STARTF_USEFILLATTRIBUTE 0x00000010
#DEFINE STARTF_FORCEONFEEDBACK  0x00000040
#DEFINE STARTF_FORCEOFFFEEDBACK 0x00000080
#DEFINE STARTF_USESTDHANDLES    0x00000100

* Konstanty pro člen wShowWindow ve struktuře STARTUPINFO
#DEFINE SW_HIDE             0
#DEFINE SW_SHOWNORMAL       1
#DEFINE SW_NORMAL           1
#DEFINE SW_SHOWMINIMIZED    2
#DEFINE SW_SHOWMAXIMIZED    3
#DEFINE SW_MAXIMIZE         3
#DEFINE SW_SHOWNOACTIVATE   4
#DEFINE SW_SHOW             5
#DEFINE SW_MINIMIZE         6
#DEFINE SW_SHOWMINNOACTIVE  7
#DEFINE SW_SHOWNA           8
#DEFINE SW_RESTORE          9
#DEFINE SW_SHOWDEFAULT      10
#DEFINE SW_MAX              10

* Funkce WaitForSingleObject()
#DEFINE WAIT_TIMEOUT 0x00000102
#DEFINE INFINITE     0xFFFFFFFF && Infinite timeout

* Funkce GetExitCodeProcess()
#DEFINE STILL_ACTIVE 0x00000103

Ukázkový kód

* Vytvoř objekty pro vygenerování struktur
loStartupInfo=CREATEOBJECT("_struc_STARTUPINFO")
loProcessInfo=CREATEOBJECT("_struc_PROCESS_INFORMATION")

* Nastav aby se okno skrylo
loStartupInfo.dwFlags=STARTF_USESHOWWINDOW
loStartupInfo.wShowWindow=SW_HIDE

* Nastav aby se okno zobrazilo na pozicích 10,20
*loStartupInfo.dwFlags=STARTF_USESHOWWINDOW + STARTF_USEPOSITION
*loStartupInfo.wShowWindow=SW_NORMAL
*loStartupInfo.dwX=10
*loStartupInfo.dwY=20


* Vytvoř řetězce
lcStartupInfo=loStartupInfo.CreateString()
lcProcessInfo=loProcessInfo.CreateString()

lcDir="c:\temp\"
lcProgram="explorer /select,c:\windows\calc.exe"

IF CreateProcess(.NULL., lcProgram, 0, 0, 1,  NORMAL_PRIORITY_CLASS, 0, @lcDir, @lcStartupInfo, @lcProcessInfo)#0
   * Výborně neselhalo to
   loProcessInfo.ReadString(@lcProcessInfo)
   DO WHILE WaitForSingleObject(loProcessInfo.hProcess, 100 ) = WAIT_TIMEOUT
   ENDDO
   =CloseHandle(loProcessInfo.hProcess) && Uzavři handle

   * Pokud se použije timer
   *loTim=CREATEOBJECT("_ExcpTimer")
   *loTim.hProcess=loProcessInfo.hProcess
   *loTim.Enabled=.T.
ELSE
   * Selhalo spuštění procesu
ENDIF

DEFINE CLASS _ExcpTimer AS _cpTimer
   PROCEDURE Process_Close
      =MESSAGEBOX("Explorer spuštěn")
   ENDPROC
ENDDEFINE

Ten zbytek...

Poznámky:
1) Jelikož VFP nativně nepodporuje struktury, je ze strany VFP naplňování řetězců do struktur velmi obtížné. Z toho důvodu je zakázáno vyplňovat vlastnosti lpDesktop, lpTitle ve třídě _struc_STARTUPINFO. Pokud by jste chtěli používat tyto členy, pak by jste museli museli použít knihovnu struct.vcx, která slouží pro vytvoření různých struktur pro práci s API funkcemi.
2) RUN - není to ono především z toho důvodu,že je jako první spuštěn foxrun.pif se všemi specifikacemi a pak teprve vlastní externí program.Foxrun.pif ovšem volá služby MS-DOS a jeho použití pak vyvolává problémy především pod Windows NT,kde je DOS jen další programovou konzolou.

Použité zdroje:
mail konference Foxpro na Pandoře.Z nějakých neznámých příčin, se mi nepodařilo najít tento mail jak na Pandoře, tak v Archívu :-( .
MSDN MSDN - Processes and Threads

Jazyková korektura: Paul Arleth gw4@volny.cz
Odborná korektura: Roman Procházka rprochazka@acedesign.cz