Spouštění programů
V této kapitole se dozvíte, zda a jak je možné svým programem spustit program jiný. V souvislosti s tímto tématem by bylo možná vhodné pohovořit o vytváření dynamických knihoven (knihovna funkcí, které se může program za chodu rozhodnout načítat a používat), nebo třeba o spouštění podprocesů programu (spuštění několika „vláken“ programu, které běží současně a dělají různé vylomeniny), či o rozmnožení sebe sama (program spustí sám sebe jako další „vlákno“). Tyto věci jsou však více méně systémově závislé (spíše více) a z tohoto důvodu je nemám v úmyslu zde probírat (proberu je až v části o programování v Linuxu). Bohužel, ani s přenositelností funkcí zde probíraných to není žádný med, to ostatně uvidíte sami.
Než začnu s výkladem, ukážu zde zdrojový kód programu mudrc, který budu v dalších programech spouštět.
- /*------------------------------------------------*/
- /* c40/mudrc.c */
- #include <stdio.h>
- {
- size_t i;
- /* Prvni argument je nazev programu (preskocime ho).
- * Muze to byt napr. * "c:\mudrc.exe" nebo jen
- * "mudrc". Zalezi to jednak na OS a v nasem pripade
- * take na funkci, ktera tento program bude volat.*/
- }
- }
- }
- /*------------------------------------------------*/
Pokud programu mudrc nepředáte žádné argumenty, vypíše jen hlášku Kdo to rusi me kruhy?, jinak vypíše zadané argumenty.
Záměrně nevypisuji první argument (tedy argv[0]), který obsahuje jméno spouštěného programu. To totiž může být jednak "c:\mudrc.exe", nebo "mudrc.exe" nebo jen "mudrc". První argument může být plné jméno programu, nebo jen jméno (případně s relativní cestou ..\mudrc.exe atp.), nebo dokonce jméno bez přípony. Záleží na tom, co který systém programu do argv[0] vloží. Vy si samozřejmě můžete program upravit tak, aby vypisoval i argv[0].
Funkce system()
Funkce system()
je jako jediná z funkcí zde zmiňovaných
definována v normě ANSI C. S jejím použitím máte tedy zaručenu
největší kompatibilitu. Funkce system()
,
jak už víte, spouští jiný
program. Podívejme se na to, jakým způsobem to dělá.
Funkce má pouze jediný argument (textový řetězec), kterým je
příkaz, který chcete spustit. Záměrně říkám příkaz a ne program.
Funkce system()
totiž spustí příkazový interpret (CMD.EXE, bash) a tomu pak tento
příkaz předá. Poté, co se příkazový interpret spustí, vykoná příkaz
a ukončí se, vrátí řízení vašemu programu. Příkazem může být
cokoliv, co můžete zadat na příkazovou řádku.
To, jaký příkazový interpret se spustí je ovlivněno tím, kde
program běží. To je také funkci system()
hodně vyčítáno. V DOSu
(Windows) půjde nejpravděpodobněji o command.com
MS-DOSu
(ale existují i jiné DOS-like systémy). V Linuxu může jít o bash, tcsh, csh
atp. Tím je také dáno, jaké systémové proměnné příkazový interpret
bude obsahovat (například PATH). Při každém spuštění funkce
system()
se spustí nový příkazový interpret. Pokud tedy v jednom volání
funkce system()
nastavíte proměnnou,
v dalším volání funkce system()
toto nastavení již nebude platné.
Další nevýhodou je náročnost na prostředky. Kromě spouštěného programu spouštíte ještě příkazový interpret. To však na dnešních nadupaných počítačích nemá téměř význam (nechcete-li z nějakého důvodu spouštět malý prográmek v cyklu milionkrát za sebou – to už by bylo citelně pomalé).
Jak tedy spustit správně příkaz? Pokud jde o příkazy samotného
interpretu, je to jednoduché. Stačí zadat jejich jméno (a za ním
parametry). Například command.com obsahuje příkaz pause
,
který způsobí pozastavení vykonávání příkazového interpretu (a ve
svém důsledku pozastavení programu, který tento příkaz pomocí
system()
spustil).
Dalším příkladem může být příkaz DIR
, po kterém budeme chtít
vypsat jen textové soubory.
Můžete spustit opravdu cokoliv, co v příkazovém interpretu. Takže například i přesměrování výstupu do souboru.
Se spouštěním programů je to, oproti příkazům příkazového řádku, už o trošku horší. Především musíte zajistit, aby příkazový interpret věděl, kde se program daného jména nachází. Nejjednodušším řešením je uvést plné jméno programu. Jinak příkazový interpret hledá například v aktuálním adresáři, nebo adresářích určených v systémové proměnné PATH (v DOSu inicializaci proměnné PATH najdete v souboru c:\autoexec.bat).
Návratovou hodnotou funkce system()
je 0 v případě úspěchu, jinak -1.
Další informace o návratovém kódu a funkci
system()
najdete v kapitole o knihovně <stdlib.h>, kde je
funkce system()
deklarována.
Najdete tam i příklad použití.
Tady předvedu příklad, jak spustit program mudrc. Předpokládám, že program mudrc je ve stejném adresáři, jako program system2 a že v proměnné PATH je uveden i aktuální adresář (tj. tečka).
/* c40/system2.c */
#include <stdlib.h>
#include <stdio.h>
#ifdef __unix__
#include <sys/wait.h>
#endif
#ifdef _MSC_VER
#define MUDRC "..\\Debug\\mudrc.exe"
#else
#define MUDRC "./mudrc"
#endif
int main(void)
{
printf("system2: system(\"" MUDRC "\");\n");
system(MUDRC);
printf("system2: system(\"mudrc Jak se vede?\");\n");
system(MUDRC " Jak se vede?");
printf("system2: Konec programu.\n");
return 0;
}
/*------------------------------------------------*/
Výstup z programu:
system2: system("mudrc"); *************************** * mudrc rika: * * Kdo to rusi me kruhy? * *************************** system2: system("mudrc Jak se vede?"); *************************** * mudrc rika: * * Argumenty: * * 1) Jak * * 2) se * * 3) vede? * *************************** system2: Konec programu.
Při spuštění programu system2 a přesměrování jeho výstupu
do souboru (dělal jsem to v Linuxu) se stala zvláštní věc. V
souboru byly na začátku výstupy ze spuštěných programů mudrc a až
na konci výstupy (z funkcí printf()
)
programu system2. Došlo k tomu
zřejmě tím, že spuštěný program mudrc ve chvíli svého
ukončení vyprázdnil svou vyrovnávací paměť, zatímco
program system2 ji vyprázdnil až poté.
Člověk někdy žasne, na co si při programování musí dávat pozor.
(Ještě že máme funkci fflush().)
Toto podivné chování můžete brát jako další nevýhodu funkce
system()
oproti ostatním zde probíraným funkcím. Jenom
pro zajímavost ukážu, jak vypadal výstup přesměrovaný do souboru
následujícím příkazem:
*************************** * mudrc rika: * * Kdo to rusi me kruhy? * *************************** *************************** * mudrc rika: * * Argumenty: * * 1) Jak * * 2) se * * 3) vede? * *************************** system2: system("mudrc"); system2: system("mudrc Jak se vede?"); system2: Konec programu.
Poznámka: Zabránit tomuto podivnému chování lze použitím funkce fflush(stdout).
Funkce exec*
Funkce exec()
nahradí aktuální proces novým (blbý, co?). Načte
zadaný program do paměti a ten, který funkci
exec()
spustil skončí (v
případě úspěchu). Pokud se program funkcí
exec()
nepodaří spustit,
aktuální proces poběží dál, funkce vrátí -1 a globální proměnná
errno se nastaví na hodnotu
chyby. Funkce exec()
nespouští příkazový
interpret (pokud to není přímo program, který chcete spustit :-),
ale jen daný program (rychlé a účinné).
Existuje celá rodina funkcí exec()
, které
postupně proberu.
Funkce exec*
nejsou součástí normy ANSI C.
Z toho důvodu jejich
deklaraci můžete v různých systémech najít v různých hlavičkových
souborech (<unistd.h>, <process.h>, <stdlib.h>),
nebo je nenajdete vůbec.
int execlp (const char *file, const char *arg, ...);
int execle (const char *path, const char *arg, ..., char *const envp[]);
int execv (const char *path, char *const argv[]);
int execvp (const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
Všechny předchozí funkce jsou (většinou) implementovány pomocí následující funkce:
U všech funkcí je prvním parametrem název programu, který chcete
spustit. Funkce execlp()
, execvp()
a execvpe
se liší od ostatních tím,
že při hledání programu prohledávají adresáře určené systémovou proměnnou
PATH (pokud není program zadán s absolutní nebo relativní cestou, tj. nepoužijete
v jeho jméně lomítko).
Dalšími argumenty funkcí exec*
jsou argumenty předávané spouštěnému
programu. Pamatujte si, že první předávaný argument by mělo být
jméno spouštěného programu a poslední argument musí být ukazatel
(char *)NULL
.
Funkce execle()
a
execvpe()
umožňují nastavit
nové systémové proměnné pro spouštěný program (pomocí argumentu
envp).
Rozdíl mezi funkcemi které mají v názvu l
a těmi, které mají v názvu v
je jen ve způsobu
předávání argumentů. Funkce s v
dostávají argumenty v poli, jehož
poslední položka musí být (char *)NULL
.
Příklad:
/* c40/exec1.c */
#define _CRT_NONSTDC_NO_DEPRECATE
#include <stdlib.h>
#include <stdio.h>
#ifndef __unix__
#include <process.h>
#else
#include <unistd.h>
#endif
#include <errno.h>
#ifdef _MSC_VER
#define MUDRC "..\\Debug\\mudrc.exe"
#else
#define MUDRC "./mudrc"
#endif
int main(void)
{
char *argv[] = { "mudrc", "prvni", "druhy", "treti", (char *)NULL };
char *env[] = { "PATH=.", (char *)NULL };
int navrat;
printf("exec1: execl(\"nesmysl\",\"nesmysl\",\"argument1\", (char *)NULL);\n");
navrat = execl("nesmysl", "nesmysl", "argument1", (char *)NULL);
/* kdyby nesmysl existoval, nasledujici kod by jiz neprobehl */
fprintf(stderr, "Navrat execl = %2i\n", navrat);
perror("CHYBA EXECL");
printf("exec1: execve(\"" MUDRC "\", argv, env);\n");
execve(MUDRC, argv, env);
/* pokud se podari spustit program mudrc,
* nasledujici kod se jiz neprovede */
fprintf(stderr, "Navrat execl = %2i\n", navrat);
perror("CHYBA EXECVE");
return 0;
}
/*------------------------------------------------*/
Makro _CRT_NONSTDC_NO_DEPRECATE
povoluje funkce, které považuje VS
za zastaralé,
jako je například funkce mkdir()
, nebo open()
,
nebo třeba všechny exec*()
a spawn*()
funkce.
Tyto funkce můžete ve VS studiu nahradit funkcemi s podtržítkem na začátku
(např. _open()
_sopen_s
),
což jsou novější ekvivalentní funkce od Microsoftu.
Výstup z programu:
exec1: execl("nesmysl","nesmysl","argument1",NULL); Navrat execl = -1 CHYBA EXECL: No such file or directory exec1: execve("mudrc", argv, env); *************************** * mudrc rika: * * Argumenty: * * 1) prvni * * 2) druhy * * 3) treti * ***************************
Pokud vám něco z příkladu není jasné, projděte si nápovědu (nebo manuálové stránky). Mimochodem, pokud jste se až dosud během výuky nepodívali do nápovědy, asi z vás moc dobrý programátoři nebudou.
Funkce spawn*
Rodina funkcí spawn*
se
chová stejně jako exec* s tím
rozdílem, že aktuální proces nenahradí a po
svém skončení se pokračuje v jeho vykonávání (myslím ve vykonávání
procesu, který funkci spawn*
spustil).
Dostupnost těchto funkcí je ještě menší, než funkcí
exec*
. V Linuxu toto prostě nepřeložíte.
V Linuxu můžete spustit nové vlákno funkcí fork()
či vfork()
a v novém vláknu spustit exec*()
,
který nahradí pouze aktuální vlákno. O vláknech ale bude řeč až v seriálu
o programování v Linuxu. Existují také funkce posix_spawn*
,
které jsou určené pro použití na embedded systémech.
Příklad:
/* c40/spawn1.c */
#define _CRT_NONSTDC_NO_DEPRECATE
#include <stdlib.h>
#include <stdio.h>
#include <process.h>
#include <errno.h>
#ifdef _MSC_VER
#define MUDRC "..\\Debug\\mudrc.exe"
#else
#define MUDRC "./mudrc"
#endif
int main(void)
{
printf
("spawn1: spawnl(\"mudrc\",\"mudrc\",\"prvni\",\"druhy\",\"treti\",NULL);\n");
if (spawnl(P_WAIT, MUDRC, "mudrc", "prvni", "druhy", "treti", NULL)
== -1)
perror("CHYBA SPAWNL");
printf("spawn1: konec.\n");
return 0;
}
/*------------------------------------------------*/
Výstup z programu:
spawn1: spawnl("mudrc","mudrc","prvni","druhy","treti",NULL); *************************** * mudrc rika: * * Argumenty: * * 1) prvni * * 2) druhy * * 3) treti * *************************** spawn1: konec.