Programmaufrufe innerhalb von C/C++-Programmen

weiter zu Aufruf von Programmen in Linux

2. Aufruf von Programmen in Windows, Teil 2: CreateProcess

Die hier zugrunde liegende Problemstellung ist die einer Button-Leiste, mit der Programme geöffnet werden. Die Code-Beispiele sind Ausschnitte aus dem Programm silidock.

Einleitung:

CreateProcess erzeugt einen neuen Prozess, der im gleichen Sicherheits-Kontext läuft wie der aufrufende Prozess.
Zur Erinnerung: Wir programmieren in Windows, Backlashs müssen "escaped" werden: "C:\\pfad\\datei.exe", sonst wird der Backlash als Escape-Sequenz interpretiert.

CreateProcess ist ab Windows XP verfügbar ( weitere Infos) .
Für Windows XP bis Windows 7 (und Windows Server 2003 - 2008) muss der Header WinBase.h (neben Windows.h) eingebunden werden.
Ab Windows 8 (Windows-Server 2012) der Header Processthreadsapi.h.

Syntax:


BOOL WINAPI CreateProcess(
  _In_opt_     LPCTSTR lpApplicationName,                 // Name des ausführbaren Moduls, z.B. "cmd" oder "notepad"
                                                          // oder eine ausführbare Datei mit absolutem oder
                                                          // (zum aktuellen Verzeichnis) relativem Pfad oder NULL
  _Inout_opt_  LPTSTR lpCommandLine,                      // Auszuführende Kommandozeile
  _In_opt_     LPSECURITY_ATTRIBUTES lpProcessAttributes, // Attribute für den Prozess:  SECURITY_ATTRIBUTES oder NULL
  _In_opt_     LPSECURITY_ATTRIBUTES lpThreadAttributes,  // Attribute für den Thread:  SECURITY_ATTRIBUTES oder NULL
  _In_         BOOL bInheritHandles,                      // Vererbung des Handles (ähnlich wie file descriptors in Linux): 
                                                          // TRUE oder FALSE
                                                          // bei TRUE müssen die Handles geschlossen werden!
  _In_         DWORD dwCreationFlags,                     // Flags u.a. über Vererbung, Priorität oder NULL
  _In_opt_     LPVOID lpEnvironment,                      // Umgebungsvariable oder NULL
  _In_opt_     LPCTSTR lpCurrentDirectory,                // Verzeichnis für den Prozess oder NULL 
                                                          // (= aktuelles Verzeichnis des aufrufenden Prozesses)
  _In_         LPSTARTUPINFO lpStartupInfo,               // Zeiger auf das  STARTUPINFO oder STARTUPINFOEX
                                                          // enthält u.a. Position, Größe...
  _Out_        LPPROCESS_INFORMATION lpProcessInformation // zeiger auf die  PROCESS_INFORMATION
                                                          // enthält Prozess- und Thread-Handles, Prozess-ID und Thread-ID
                                                          // die Handles müssen geschlossen werden
);

    				
lpApplicationName und lpCommandLine können alternativ oder gemeinsam verwendet werden. Das ausführbare Modul muss entweder als Name der Anwendung (lpApplicationName) oder als Kommando (lpCommandLine) mit nachfolgendem Leerzeichen ("cmd.exe ") angegeben werden.
Modulangaben in lpCommandLine können mit absolutem oder realtivem Pfad angegeben werden; es werden auch das 32-Bit-System- und das 16-Bit-System-Verzeichnis durchsucht sowie das Windows-Verzeichnis und die PATH-Variable. "cmd" oder "calc" würden also gefunden. Die Erweiterung (extension), beispielsweise "exe" oder "com", sollte ebenfalls angegeben werden. Pfade mit Leerzeichen müssen in Anführungszeichen stehen.
Wenn für lpProcessAttributes und lpThreadAttributes NULL angegeben wird, erhält der Prozess den Standard- securty descriptor. Wenn nicht Eigentümer*in oder Zugriff speziell definiert werden sollen, genügt das.

Einige der möglichen Flags sind für den Anwendungsfall hier hilfreich, denn die mit silidock aufgerufenen Programme sollen möglichst unabhängig vom aufrufenden Programm laufen:
CREATE_DEFAULT_ERROR_MODE - Der Prozess erhält einen eigenen Standard-Error-Modus statt den des aufrufenden Prozesses.
DETACHED_PROCESS - Die Konsole des aufrufenden Prozesses wird nicht vererbt. Der Prozess erhält zunächst keine Konsole (im Gegensatz zu CREATE_NEW_CONSOLE).

Das aktuelle Verzeichnis sollte geändert werden, da Shells oft ein bestimmtes Verzeichnis benötigen:
Aus dem vollständigen Dateinamen mit Pfad wird der Pfad (path) abgetrennt: string file_and_path (program_call);
string path = file_and_path.substr(0, file_and_path.find_last_of("/\\") );
Damit der neu erzeugte Prozess sauber beendet werden kann, müssen die Handles hProcess und hThread mit CloseHandle geschlossen werden, am besten, sobald der Prozess erfolgreich erzeugt wurde. Ansonsten bleiben diese Handles, bis der aufrufende Prozess beendet ist.

Im Fehlerfall setzt CreateProcess einen last-error code, der mit der Funktion GetLastError() abgefangen werden kann. Da ein numerischer Wert für die meisten Menschen wenig Bedeutung hat, ist es sinnvoll, mit der Funktion FormatMessage() eine Beschreibung des Fehlers in einer MessageBox anzuzeigen.

Code-Schnipsel:


// C++ Header
#include <iostream>

// C-Header
extern "C" {
#include <windows.h>
#include <WinBase.h.h> // Windows XP bis Windows 7
//#include <Processthreadsapi.h.h> // Windows 8 ff.
}

void execute_program(const char* program_call, const char* param )
{
    string file_and_path (program_call);
    string path = file_and_path.substr(0, file_and_path.find_last_of("/\\") );

    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory( &si, sizeof(si) );
    si.cb = sizeof(si);
    ZeroMemory( &pi, sizeof(pi) );

    // Start the process.
    if( CreateProcess(
            program_call, // executable file
            (LPSTR) param,  // Parameter for command line (NULL)
            NULL,           // Process handle
            NULL,           // Thread handle
            FALSE,          // Inheritance
            CREATE_DEFAULT_ERROR_MODE | DETACHED_PROCESS,// Own error mode and no console
            NULL,           // Environment of parent
            (LPCSTR) path.c_str(),// Directory of executable file
            &si,            // Pointer to STARTUPINFO structure
            &pi )           // Pointer to PROCESS_INFORMATION structure
    == FALSE)
    {
        // get error description
        DWORD errCode = GetLastError();
        char *err;
        FormatMessage(
            FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
            NULL,
            errCode,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // default language
            (LPTSTR) &err,
            0,
            NULL);

        // concatenate with program_call
        char error_message[strlen(err) + strlen(program_call) + 1];
        strcpy (error_message,err);
        strcat (error_message,program_call);
        // display message
        MessageBox(NULL, (LPCTSTR)error_message, TEXT("Error"), MB_OK);

        return;
    }

    // Close process and thread handles.
    CloseHandle( pi.hProcess );
    CloseHandle( pi.hThread );
}
    				

zurück zu Teil 1: ShellExecute