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.

  1. /*------------------------------------------------*/
  2. /* c40/mudrc.c                                    */
  3. #include <stdio.h>
  4.  
  5. int main(int argc, char *argv[])
  6. {
  7.     size_t i;
  8.     printf("\n***************************\n");
  9.     printf("* mudrc rika:             *\n");
  10.     switch (argc) {
  11.     case 1:
  12.         printf("* Kdo to rusi me kruhy?   *\n");
  13.         break;
  14.     default:
  15.         printf("* Argumenty:              *\n");
  16.         /* Prvni argument je nazev programu (preskocime ho).
  17.          * Muze to byt napr.  * "c:\mudrc.exe" nebo jen
  18.          * "mudrc". Zalezi to jednak na OS a v nasem pripade
  19.          * take na funkci, ktera tento program bude volat.*/
  20.         for (i = 1; i < argc; i++) {
  21.             printf("* %2u) %-19s *\n", i, argv[i]);
  22.         }
  23.         break;
  24.     }
  25.     printf("***************************\n\n");
  26.     return 0;
  27. }
  28.  
  29. /*------------------------------------------------*/

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á.

int system(const char * string);

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).

system("pause");

Dalším příkladem může být příkaz DIR, po kterém budeme chtít vypsat jen textové soubory.

system("dir *.txt");

Můžete spustit opravdu cokoliv, co v příkazovém interpretu. Takže například i přesměrování výstupu do souboru.

system("dir *.txt > vystup.log");

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:

$ system2 > soubor.txt
***************************
* 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 execl (const char *path, const char *arg, ...);
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:

int execve (const char *file, char *const argv[], char *const envp[]);

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;
}

/*------------------------------------------------*/
Visual Studio

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.
Komentář Hlášení chyby
Created: 29.8.2003
Last updated: 26.6.2014
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..