Kommandozeilenbefehl aus mehreren Strings zusammenfügen



  • Hallo zusammen,

    also ich denke, dass das Problem, was ich habe, nicht so kompliziert ist, aber irgendwie komme ich nicht weiter, da ich bisher wenig mit strings arbeiten musste.

    Ich möchte folgendes erreichen. Ich möchte einen neuen Prozess mit CreateProcess starten, das klappt auch soweit, im folgenden seht ihr den Befehl, den ich zum starten des Prozesses benutze:

    if(!CreateProcess(
            "c:\\Programme\\Test_App\\Test_App.exe",  //Prgrom path
             "/v c:\\data\\test_data.dat /k z:\\test.dat /ly /q /a",           // Command line
            NULL,           // Process handle not inheritable
            NULL,           // Thread handle not inheritable
            FALSE,          // Set handle inheritance to FALSE
            0,              // No creation flags
            NULL,           // Use parent's environment block
            NULL,           // Use parent's starting directory
            &si,            // Pointer to STARTUPINFO structure
            &pi )           // Pointer to PROCESS_INFORMATION structure
        )
    

    Wie man sieht, werden im Prozessaufruf mehrere Pfade und Optionen übergeben. Diese Pfade würde ich nun gerne aus einer Config Datei auslesen, sie mit den Optionen (z.B. /k path_1) zusammenfügen und dem CreatePrcoess einfach zwei strings übergeben, den program path und die command line.

    Das Auslesen der Datei funktioniert und die Pfade stehen in normaler Form in der Datei, also z.B.:
    c:\Programme\Test_App\Test_App.exe

    Nun meine Fragen; bei dem vorliegenden Aufruf muss der Command line Befehl mit "" übergeben werden, damit CreatProcess ihn als komplett erkennt und alles Optionen ausführt. Wenn ich nun einen char string übergebe, muss ich die Anführungszeichen auch hinzufügen und wie mache ich das, da sie ja als solches nicht vom compiler erkannt werden.

    Muss ich den Pfad vom einfachen backslash '\' zum doppelten wechseln? '\'?

    Habe mir das zusammenfügen von strings schon angeschaut, denke, dass strcat am besten geeignet ist, um die Befehlszeile zusammen zu setzen. Erste versuche, führten aber zu keinem Erfolg, also könnte ich mir vorstellen, dass es an diesen 2 Punkten scheitert.

    Danke für ein paar Tips!


  • Mod

    Also zuerst mal: Wenn du schon C++ machen willst und kein C, dann nimm std::string anstatt char Arrays, damit wird das hantieren mit Strings vergleichsweise trivial.

    Wenn du im Quelltext deines Programms Stringliterale mit " oder \ brauchst, dann hast du richtig erkannt, dass du stattdessen \" bzw. \\ schreiben musst. In externen Dateien darf dies selbstverständlich nicht so geschrieben werden.

    edit: Anscheinend werden hier im Forum (oder von meinem Browser) Escape Sequenzen auch ersetzt - aber ich kann da keine Konsistenz erkennen, manchmal wird ersetzt, manchmal nicht. Da ich jetzt nicht weiß woran dies liegt, will ich es nochmal mit Worten klarstellen:

    Um im Programmtext Literale zu haben, die Gänsefüßchen oder Backslash enthalten, musst du anstelle dieser Zeichen Backslash Gänsefüßchen beziehungsweise Backslash Backslash schreiben.



  • Habe ich das richtig verstanden, dass du alle Argumente, die du jetzt direkt im Code hast aus einer Datei lesen möchtest? Einschließlich des Programmnamens?

    Dann brauchst brauchst du in den Strings nichts zu escapen, musst also weder in der INI-Datei noch im Programm bespielsweise Doppel-Backslash statt Slasch schreiben.

    // --- Parameter.ini
    
    Programmname = c:\Programme\Test_App\Test_App.exe
    Datei1 = c:\data\test_data.dat
    
    // usw.
    

    Zum Zusammenbauen der Kommandozeile würde ich ostringstream nehmen. Da schiebst du dann alle Argumente einfach in der richtigen Reihenfolge rein:

    std::ostringstream oss;
    oss << "/v " << dateiname1 << " /k";   // usw.
    
    if(!CreateProcess(programmname.c_str(),
                      oss.str()c_str(),
                      NULL,
                      NULL,
                      FALSE,
                      0,
                      NULL,
                      NULL,
                      &si,
                      &pi))
    

    Worauf du dabei achten musst ist, dass die einzelnen Parameter der Kommandozeile durch Blanks getrennt werden. Falls du also in der INI-Datei z.B. Pfade angibst, die Blanks enthalten, musst du beim Zusammenbauen Anführungszeichen setzen. Was man natürlich vorsichtshalber immer machen kann.

    Wenn also in der INI-Datei etwas wie:

    Datei1 = c:\\Eigene Dateien\Meine Daten\test_data.dat
    

    stehen kann, muss das so zur Kommandozeile verarbeitet werden:

    std::ostringstream oss;
    oss << "/v " << "\"" << dateiname1 << "\"" << " /k";   // usw.
    
    if(!CreateProcess(programmname.c_str(),
                      oss.str()c_str(),
                      NULL,
                      NULL,
                      FALSE,
                      0,
                      NULL,
                      NULL,
                      &si,
                      &pi))
    

    Stefan.



  • Erst einmal Danke für die ersten Tipps, die schon sehr hilfreich waren. Der Rat mit std::string vereinfacht die Handhabung um einiges, habe meinen Code von char* auf string umgestellt.

    Ich habe auch einen ersten Erfolg mit euren Tipps erreicht, allerdings besteht nach wie vor ein Fehler.

    Um beim Code von Stefan zu bleiben:

    std::ostringstream oss;
    oss << "/v " << "\"" << dateiname1 << "\"" << " /k";   // usw.
    
    if(!CreateProcess(programmname.c_str(),
                      oss.str()c_str(),
                      NULL,
                      NULL,
                      FALSE,
                      0,
                      NULL,
                      NULL,
                      &si,
                      &pi))
    

    Bei programmname handelt es sich ja um einen Pfad, den ich direkt aus dem Config File auslese und nicht verändern muss. Wenn ich nur diesen Pfad als string hinzufüge, funktioniert der Prozessaufruf wunderbar. Dabei ist es auch nicht nötig einen doppelten Backslash einzufügen (wie Stefan bereits sagte)

    Hier ist meine erste Frage, warum braucht der string, den ich direkt eintrage Doppelbacklsash, wenn ich den string aus einer Datei ausleses aber nicht?

    Das eigentliche Problem tritt aber auf, wenn ich die CommandLine einfügen will. Wenn ich sie direkt übergebe habe ich:

    "/v c:\\data\\test_data.dat /k z:\\test.dat /ly /q /a"
    

    Ich habe mit den ostringstream Operationen verschiedene Versionen probiert,
    1.)ich habe die Zeile geschrieben, wie sie oben steht, allerdings ohne Anführungszeichen,
    2.) mit Anführungszeichen am Anfang und Ende der Zeile und
    3.) Anführungszeichen auch für die einzelnen Pfade, jedes Mal kommt der gleiche Fehler:

    error: invalid conversion from `const char*' to `CHAR*'
     error:   initializing argument 2 of `BOOL CreateProcessA(const CHAR*, CHAR*, _SECURITY_ATTRIBUTES*, _SECURITY_ATTRIBUTES*, BOOL, DWORD, void*, const CHAR*, _STARTUPINFOA*, _PROCESS_INFORMATION*)'
    

    Ich habe dabei .str() und .c_str() verwendet, um einen den String in einen Char* zu konvertieren.

    Muss ich in diesem Fall nun wieder den Doppelbackslah einführen oder besteht hier ein anderes Problem?

    Habt ihr eine Idee, was hier noch falsch ist?



  • Die CreateProcess-Funktion (s.a. http://msdn.microsoft.com/en-us/library/ms682425(VS.85).aspx) erwartet als 2. Parameter einen char* (statt einem const char*). Daher mußt du noch explizit casten:

    ..., static_cast<char *>(oss.str().c_str()), ...
    


  • Die CreateProcess-Funktion (s.a. http://msdn.microsoft.com/en-us/library/ms682425(VS.85).aspx) erwartet als 2. Parameter einen char* (statt einem const char*). Daher mußt du noch explizit casten:
    C/C++ Code:
    ..., static_cast<char *>(oss.str().c_str()), ...

    Also, wie ich das verstanden habe, konvertiert .c_str() einen string direkt in ein const char*.

    Habe deinem Tip aber natürlich trotzdem ausprobiert, was zu folgender Fehlermeldung füht:

    error: invalid static_cast from type `const char*' to type `char*'
    

    Ich werde mal weiterlesen, vielleicht harpert es ja wirklich in dem Punkt. Vielleicht hat ihr ja noch eine Idee?!



  • Um von einem const char * auf einen char * zu kommen, brauchst du const_cast<char *> nicht static_cast<char *> ! Tut mir Leid, dass ich dir Mist erzählt habe. Ich wusste nicht (mehr), dass CreateProcess() dort einen char * erwartet.

    tomasito schrieb:

    Hier ist meine erste Frage, warum braucht der string, den ich direkt eintrage Doppelbacklsash, wenn ich den string aus einer Datei ausleses aber nicht?

    Das liegt daran, dass der Compiler beim Übersetzen deines Codes den String noch interpretiert. Er ersetzt dabei sogenannte Escape-Sequenzen in nicht druckbare Zeichen. Z.B wird '\n' in einen Zeilenvorschub geändert oder '\t' in einen Tab. Man muss die Escape-Sequenz '\' schreiben, damit der Compiler letztlich einen Backslash in den String packt.

    Wenn du hingegen den String aus einer Datei liest, sieht der Compiler diesen ja nicht mehr. Darum darfst du auch keine Escape-Sequenzen verwenden.

    Stefan.



  • Die Funktion erwartet einen unqualifizierten char* , also können die char s, auf die der Zeiger verweist, von der Funktion verändert werden. Deshalb darfst du nicht c_str() übergeben, da das const -qualifiziert ist.

    Fordere stattdessen ein dynamisches Array mit genügend Speicher an, kopiere den Inhalt von c_str() und übergib den Zeiger auf das Array. Die C++-Variante wäre std::vector<char> und als Zeiger auf den C-String &vec[0] .



  • DStefan schrieb:

    Um von einem const char * auf einen char * zu kommen, brauchst du const_cast<char *> nicht static_cast<char *> !

    const_cast zur Umgehung von Compilerfehlern einzusetzen halte ich für sehr gefährlich. Seid ihr sicher, dass die Funktion nichts ändert? Auf MSDN ist der Parameter als Output-Parameter gekennzeichnet.



  • Nexus schrieb:

    DStefan schrieb:

    Um von einem const char * auf einen char * zu kommen, brauchst du const_cast<char *> nicht static_cast<char *> !

    const_cast zur Umgehung von Compilerfehlern einzusetzen halte ich für sehr gefährlich. Seid ihr sicher, dass die Funktion nichts ändert? Auf MSDN ist der Parameter als Output-Parameter gekennzeichnet.

    Du hast Recht, das sollte man nicht machen. Manchmal sind meine Tipps einfach zu hektisch 😉

    Stefan.



  • const_cast zur Umgehung von Compilerfehlern einzusetzen halte ich für sehr gefährlich. Seid ihr sicher, dass die Funktion nichts ändert? Auf MSDN ist der Parameter als Output-Parameter gekennzeichnet.

    Schade, es hat auf jeden Fall funktioniert. Aber dann zur Alternativlösung:

    Fordere stattdessen ein dynamisches Array mit genügend Speicher an, kopiere den Inhalt von c_str() und übergib den Zeiger auf das Array. Die C++-Variante wäre std::vector<char> und als Zeiger auf den C-String &vec[0].

    Dazu eine Frage; ich erhalte also ein const char*, wenn ich .c_str() verwende, wie bekomme ich es dann in ein char konvertiert, um es dem Vector hinzuzufügen. Benutze ich am besten push_back für den Kopiervorgang?



  • tomasito schrieb:

    Schade, es hat auf jeden Fall funktioniert.

    Einmal funktioniert heisst nicht, dass es das immer tun wird. Stichwort undefiniertes Verhalten.

    tomasito schrieb:

    Dazu eine Frage; ich erhalte also ein const char*, wenn ich .c_str() verwende, wie bekomme ich es dann in ein char konvertiert, um es dem Vector hinzuzufügen. Benutze ich am besten push_back für den Kopiervorgang?

    Ich würde gleich den Konstruktor verwenden (eventuell solltest du noch etwas mehr Speicher anfordern, falls die Funktion den braucht). Der Konstruktor erstellt seine Elemente aus einer Iterator-Range (in deinem Fall sind das einfache Zeiger). Das könnte so aussehen:

    const char* TmpBuffer = MyString.c_str();
    std::vector<char> Buffer(TmpBuffer, TmpBuffer + MyString.size());
    
    CreateProcess(..., &Buffer[0], ...);
    


  • Nexus schrieb:

    Ich würde gleich den Konstruktor verwenden (eventuell solltest du noch etwas mehr Speicher anfordern, falls die Funktion den braucht). Der Konstruktor erstellt seine Elemente aus einer Iterator-Range (in deinem Fall sind das einfache Zeiger). Das könnte so aussehen:

    const char* TmpBuffer = MyString.c_str();
    std::vector<char> Buffer(TmpBuffer, TmpBuffer + MyString.size());
    
    CreateProcess(..., &Buffer[0], ...);
    

    Das ginge auch mit MyString.begin() und MyString.end(). In beiden Fällen ist der String in Buffer aber nicht nullterminiert. Also entweder:

    const char* TmpBuffer = MyString.c_str();
    std::vector<char> Buffer(TmpBuffer, TmpBuffer + MyString.size());
    Buffer.push_back(0);
    
    CreateProcess(..., &Buffer[0], ...);
    

    Oder:

    std::vector<char> Buffer(MyString.size() + 1);
    std::strcpy(&Buffer[0], MyString.c_str());
    
    CreateProcess(..., &Buffer[0], ...);
    

    Stefan.



  • Stimmt, vielen Dank für die Ergänzung! 🙂



  • Sorry, für meinen falschen cast, wollte eigentlich erst einfach (char 😉 schreiben, aber dachte, ok, wir sind ja im C++ Forum. Klar muß dann dort ein const_cast hin.

    Laut MSDN ist nur für die WideChar-Version CreateProcessW der 2. Parameter als Output deklariert, und nicht für CreateProcessA.



  • Da ich an einem anderen Projekt getüfftelt habe, kommt meine Antwort etwas verspätet, wollte aber den Beitrag noch sauber beenden.

    const char* TmpBuffer = MyString.c_str();
    std::vector<char> Buffer(TmpBuffer, TmpBuffer + MyString.size());

    CreateProcess(..., &Buffer[0], ...);

    Ich habe letztendlich den Tip von Nexus implementiert und das Programm läuft einwandfrei.

    Ich danke euch alle für die Hilfe und die Erklärungen!

    tom



  • Aber du hast die Nullterminierung (siehe Bemerkung von DStefan) noch eingebaut? 🙂



  • Nexus schrieb:

    Aber du hast die Nullterminierung (siehe Bemerkung von DStefan) noch eingebaut? 🙂

    Ja, habe die Nullterminierung dran gehangen, habe mich nur auf dich bezogen, da die eigentliche Idee von dir kam.

    Trotzdem danke für das kritische Nachfragen, hätte mir auch durchgegangen sein können.


Log in to reply