Announcement

Collapse
No announcement yet.

DOS-Anwendung mit CreateProcess und WaitForSingleObject

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • DOS-Anwendung mit CreateProcess und WaitForSingleObject

    Ich habe ein Problem DOS und Windows-Programme (Exe-Dateien) mit CreateProcess so aufzurufen, das das Programm erst nach erfolgreicher Durchführung den Programmablauf fortsetzt.<br>

    Während WaitForSingleObject unter Windows 95/98 korrekt reagiert, wird unter Windows NT der Programmablauf unvermindert fortgesetzt.<br>
    Im Buch "Das Win32API Band 1" wird darauf hingewiesen, das WaitForSingleObject unter Windows NT nur reagiert wenn der Handle mit dem Flag SYNCHRONIZE (0x100000L) geöffnet worden ist.<br>
    Leider ist es mir bisher nicht gelungen nachzuvollziehen, wie ich dies mit der ProcessCreate-Funktion erreichen kann. <br>
    <br>
    Hilfreich wäre vielleicht eine kurze Beispieldarstellung wie der CreateProcess aufzurufen wäre um dies zu erreichen.<br>
    <br>
    Gibt es desweiteren noch bessere Methoden DOS- und Windows-Exe-Dateien aufzurufen ?<br>
    ShellExecute eignet sich ja wie ich lesen musste nur für Win32-Programme

  • #2
    Hallo,

    zuerst einmal sollte der Begriff "DOS-Anwendung" näher definiert werden. Ist damit eine <b>Konsolenanwendung</b> gemeint, die als 32-Bit-Programm im Fenster der <i>Eingabeaufforderung</i> läuft oder soll ein altes 16-Bit-Programm gestartet werden?

    Falls eine Konsolenanwendung gestartet werden soll, sollte der folgende Aufruf (Auszug aus einem Beispielprojekt aus meinem Buch <i>Delphi Win32-Lösungen</i>) funktionieren:
    <pre>
    function ExecConsoleAppAndWait(const sApp, sParams, sTitle: String;
    wShow: Word): DWord;
    var
    aSI : TStartupInfo;
    aPI : TProcessInformation;
    aProc : THandle;
    dwError : DWord;
    begin
    FillChar(aSI, SizeOf(aSI), 0);
    aSI.cb := SizeOf(aSI);
    // Text für die Fensterzeile zuweisen
    aSI.lpTitle := PChar(sTitle);
    aSI.wShowWindow := wShow;
    if not CreateProcess(PChar(sApp), PChar(sParams), nil, nil, False,
    CREATE_NEW_CONSOLE OR NORMAL_PRIORITY_CLASS,
    nil, nil, aSI, aPI) then
    begin
    dwError := GetLastError;
    raise Exception.CreateFmt('Programm kann nicht gestartet werden.' +
    #10#13 + 'Fehler-Code %d',[dwError]);
    end;
    aProc := aPI.hProcess;
    CloseHandle(aPI.hThread);
    if WaitForSingleObject(aProc, Infinite) <> Wait_Failed then
    GetExitCodeProcess(aProc, Result);
    CloseHandle(aProc);
    end;
    </pre>
    Auf das Ende einer normalen Win32-Anwendung kann über den folgenden Weg gewartet werden (auch dieses Beispiel stammt aus dem o.g. Buch), wobei dieser Weg auch unter Windows NT funktioniert:
    <pre>
    procedure TFormMain.Button1Click(Sender: TObject);
    begin
    WaitExecute('calc.exe');
    end;

    {
    Das Programm startet eine andere Win32-Anwendung und wartet auf deren
    Ende. Allerdings soll die eigene Anwendung während dieser Zeit immer
    noch auf Benutzeraktionen und Windows-Botschaften reagieren. Der
    Benutzer kann das Fenster verschieben, als Icon ablegen und wieder
    von der Taskleiste herstellen. Dazu ist es notwendig, das die Win32-
    API-Funktion MsgWaitForMultipleObjects nicht nur beim Ende des gestarteten
    Prozesses zurückkehrt, sondern immer dann, wenn eine neue Botschaft von
    Windows zugestellt wurde. Aus diesem Grund verwendet die API-Funktion
    MsgWaitForMultipleObjects die Konstante QS_ALLINPUT, somit kehrt die
    Funktion bei jeder neuen Botschaft zurück. Über den anschliessenden
    Aufruf von ProcessMessages werden die neuen Botschaften als bearbeitet
    markiert, so dass sich MsgWaitForMultipleObjects wieder für neue
    Botschaften auf Lauer legt, solange der gestartet Prozess noch läuft.
    }

    procedure TFormMain.WaitExecute(sEXE: String);
    var
    aTSI : TStartupInfo;
    aTPI : TProcessInformation;
    iRet : Integer;
    begin
    FillChar(aTSI, SizeOf(aTSI), #0);
    FillChar(aTPI, SizeOf(aTPI), #0);
    aTSI.CB := SizeOf(aTSI);
    if not CreateProcess(nil, PChar(sEXE), nil, nil, False,
    NORMAL_PRIORITY_CLASS,
    nil, nil, aTSI, aTPI) then
    RaiseLastWin32Error;
    StatusBar1.SimpleText := 'Externer Prozess ist aktiv.';
    repeat
    iRet := MsgWaitForMultipleObjects(1, aTPI.hProcess, False, INFINITE, (QS_ALLINPUT));
    if iRet <> (WAIT_OBJECT_0) then
    begin
    // aktustische Rückmeldung zur Demonstration
    MessageBeep($FFFFFFFF);
    Application.ProcessMessages;
    end;
    until iRet = (WAIT_OBJECT_0);
    CloseHandle(aTPI.hProcess);
    StatusBar1.SimpleText := 'Externer Prozess wurde beendet.';
    end;
    </pre>

    Comment


    • #3
      Vielen Danke erstmal für die schnelle Antwort.<br>
      <br>
      Ich habe die Antwort noch nicht probiert, werde es aber gleich tun.<br>
      <br>
      Prinzipiell benötige ich den 3 Aufrufe externer Programme um ältere index-sequentielle Daten einer Dos-Application so umzustellen, das sie in eine neue index-sequentielle Struktur umgewandelt werden können, die dann unter den neuen Windows-Programmen verwendet werden können.<br>
      <br>
      Dabei werden folgende Prozesse benötigt:<br>
      1. Aufruf eines 16-Bit-Programmes zum Neuaufbau der Satz-Indexe<br>
      2. Aufruf einer 32-Bit-Konsolenanwendung die die Daten konvertiert und eine Batch-Datei erzeugt die den parameterisierten Programmaufruf enthält um die Satz-Indexe der Windows-Datei aufzubauen.<br>
      3. Aufruf der erzeugten Batch-Prozedur zum Aufbau der Satz-Indexe (Diese Batchroutine enthält auch wieder den Aufruf einer 32-Bit-Konsolenanwendung mit Parameter-Angaben)<br>
      <br>
      Da jeder Prozess neue Dateien erzeugt, muss bei jedem Process das Ende abgewartet werden, bevor der nächste gestartet wird!<br>
      <br>
      Prinzipiell stellt sich mir noch die Frage, ob das 2. Beispiel (WaitExecute) auch für den Aufruf des Kommando-Prozessors verwendet werden kann (Command.Com unter Win95/98 bzw. CMD.EXE unter WinNT) da
      ich diesen mit Parameter "/C" für die Ausführung des CreateProcess verwende.<br>
      Das 2. Beispiel würde mir prinzipiell mehr zusagen, da es auch die Botschaftsbehandlung der Application nicht unterbricht (Fenster verschieben, als Icon ablegen, etc.)<br>
      <br>
      Ein weiteres Problem, das mir gestern bei Tests unter Windows98 auffiel war das bei Ausführung des 3. Schritts unter Umständen der Neuaufbau der Indexe fehlschlug, was zu einer Meldung auf der DOS-Eingabeaufforderung führte und der Tatsache das sich das Fenster nicht automatisch beendete. <br>
      Die Meldung kam aber nicht aus der Dos-Anwendung sondern muß von Command.Com ausgelöst worden sein.<br>
      Eventuell da der Fehler innerhalb einer Batch-Routine erzeugt wurde ?<br&gt

      Comment


      • #4
        Hallo Hr. Kosch,<br>
        <br>
        Der von ihnen vorgegebene Lösungsvorschlag funktioniert unter Windows NT leider nicht.<br>
        Während unter Win95/98 die Ausführung mit WaitforSingleObject bzw. WaitforMultipleObjects ohne weiteres möglich ist, reagiert das Programm unter Windows NT 4.0 nicht darauf.<br>
        Die Konkrete Schwerpunkt des Problems scheint zu sein, den Prozess-Handle der von CreateProcess erzeugt wird mit dem Wert SYNCHRONIZE (0x100000L) zu initialisieren, was mir bisher weder über die TStartupInfo-Variable noch über die TProcessInformation-Variable gelungen ist.<br>
        Dadurch wird jeder Prozess beinahe gleichzeitig angestartet, was aber zu Problemen führt, da die für das Folgeprogramm erforderlichen Dateien erst von dem vorhergehenden Programm erzeugt werden müssen.<br>
        <br>
        Eine genaue Beschreibung der Situation und den verwendeten Quellcode habe ich ihnen per Mail zugeschickt

        Comment


        • #5
          Hi

          CreateProcess(...,...,nil,nil,False,SYNCHRONIZE,ni l,....);<br>
          Ändere also die dwCreationFlags. Falls das nicht hilft, gehst Du nach dem Aufruf von Createprocess() folgendermaßen vor:

          <pre>

          Process2 := OpenProcess(PROCESS_ALL_ACCESS or SYNCHRONIZE, False, ProcessID);
          WaitForSingleObject(Process2, INFINITE);
          CloseHandle(Process2);

          oder versuchst:

          WaitForSingleObject(ProcessInfo.hThread, INFINITE);

          </pre>

          Gruß Hage

          Comment


          • #6
            Hallo Hagen,<br>
            danke für deine Antwort.<br>
            Ich hatte damit aber ebenfalls leider nicht den gewünschten Erfolg.<br>
            <br>
            Die Varianten (1 und 3)<br>
            CreateProcess(...,...,nil,nil,False,SYNCHRONIZE, nil, ....);<br>
            und <br>
            WaitForSingleObject(ProcessInfo.hThread, INFINITE);<br>
            lieferten beide das selbe Ergebnis, das bereits bisher auftrat, <br>
            d.h. die Processe wurden gestartet und der Programmablauf <br>
            sofort fortgesetzt. SYNCHRONIZE zeigte also keine Wirkung !<br>
            <br>
            Die 2. Variante<br>
            PROCESS2 := OpenProcess(PROCESS_ALL_ACCESS or SYNCHRONIZE, <br>
            False, ProcessInfo.hProcess);<br>
            WaitForSingleObject(Process2, INFINITE);<br>
            CloseHandle(Process2);<br>
            bewirkte das der Process gar nicht mehr beendet wurde und <br>
            dadurch das Programm an dieser Stelle stehenblieb <br>
            bzw. in eine Endlos-Schleife geriet.<br>
            Ist es möglich, das hierbei der Bezug zum Programm das mit <br>
            ProcessInfo.hProcess angelegt wurde, nicht funktioniert ? <br>
            <br>
            Anmerken möchte ich noch das ich die Prozedur dahingehend<br>
            abgewandelt hatte, das ich nicht WaitForSingleObjects sondern<br> WaitForMultipleObjects verwende, da der Aufruf in einem Formular<br>
            stattfand und ich so Application.Messages berücksichtigen kann<br> (Programmabbruch, Formularaktualisierung etc.).<br>
            <br>
            <br>
            Der folgende Quelltext wurde von mir verwendet und <br>
            hat eine Endlosschleife produziert:<br>
            <br>
            function DOSCreateProcess(cApplName, cApplParam, cPfad, cShellTitel: String; var iErr_Code : LongWord): Boolean;<br>
            var<br>
            aTSI : TStartupInfo;<br>
            aTPI : TProcessInformation;<br>
            hProcess2 : THandle;<br>
            iRet : Integer;<br>
            cDosAufruf : String;<br>
            begin<br>
            cDosAufruf := Trim(cSysCommandPfad + cSysCommandName + ' ' + cSysCommandParam + ' ' + cApplName +' '+cApplParam);<br>
            FillChar(aTSI, SizeOf(aTSI), 0);<br>
            aTSI.cb := Sizeof(aTSI);<br>
            aTSI.lpTitle := PChar(cShellTitel);<br>
            aTSI.wShowWindow := SW_SHOWMINNOACTIVE; // Ohne Wirkung !!!<br>
            // (Trotz SW_SHOWMINNOACTIVE Fensteranzeige, seit ich CREATE_SEPARATE_WOW_VDM verwende !)<br>
            if not CreateProcess(nil,<br>
            PChar(cDosAufruf), // Befehlszeile<br>
            nil, // Security<br>
            nil, // Security<br>
            False,<br>
            CREATE_SEPARATE_WOW_VDM or<br>
            NORMAL_PRIORITY_CLASS, // Priorität<br>
            nil, // Environment<br>
            PChar(cPfad), // Verzeichnis<br>
            aTSI,<br>
            aTPI) then<br>
            begin // Process-Erzeugung nicht möglich<br>
            iErr_Code := GetLastError;<br>
            result := False;<br>
            exit;<br>
            end else // Process-Erzeugung erfolgreich<br>
            result := True;<br>
            <br>
            hProcess2 := OpenProcess(PROCESS_ALL_ACCESS OR SYNCHRONIZE, False, aTPI.hProcess);<br>
            try<br>
            SetConsoleTitle(PChar(cShellTitel));<br>
            repeat // Beginn der Endlos-Schleife<br>
            iRet := MsgWaitForMultipleObjects(1, hProcess2, False, INFINITE,(QS_ALLINPUT));<br>
            if iRet <> (WAIT_OBJECT_0) then begin<br>
            // Abbruch-Button berücksichtigen<br>
            Application.ProcessMessages;<br>
            // Abbruch durch Escape-Taste berücksichtigen<br>
            if GetAsyncKeyState(VK_ESCAPE) <> 0 then bAbbruch := True;<br>
            end;<br>
            until iRet = (WAIT_OBJECT_0) // Ende der Endlos-Schleife<br>
            or bAbbruch = True;<br>
            finally<br>
            CloseHandle(aTPI.hProcess);<br>
            CloseHandle(hProcess2);<br>
            end;<br>
            end;<br>
            &#10

            Comment


            • #7
              Hallo,

              normalerweise sollte das Flag SYNCHRONIZE zumindestens bei einer normalen GUI-Anwendung nicht angegeben werden müssen, da es bereits in den <i>standard access rights</i> (Combines DELETE, READ_CONTROL, WRITE_DAC, WRITE_OWNER, and SYNCHRONIZE access) enthalten ist. Mein Beispiel mit MsgWaitForMultipleObjects wartet auch unter NT 4 auf das Ende des gestarteten Prozesses (CALC.EXE). Es muss also bei den 16-Bit-Anwendungen einen Sonderfall geben, der zumindestens in der Platform SDK-Hilfe auch als solcher gekennzeichnet wird (siehe Beschreibung zu <i>lpApplicationName </i>).

              Ich würde daher folgendes probieren: <br>
              1. Unmittelbar nach dem Aufruf von <i>hProcess2 := OpenProcess(PROCESS_ALL_ACCESS OR SYNCHRONIZE, False, aTPI.hProcess) </i> zuerst den Rückgabewert in hProcess2 prüfen, und wenn dort 0 vorgefunden wird, den Grund dafür mit <b>RaiseLastWin32Error</b> erfragen. <br>
              2. Beim Aufruf von CreateProzess in <i>pProcessAttributes</i> das Flag <i>bInheritHandle</i> aktivieren, um die Zugriffsrechte zu vererben. <br>
              3. Wenn das nichts hilft, muss wohl jemand von uns die Details im SDK genauer durchlesen, die sich aus CREATE_SEPARATE_WOW_VDM ergeben ;-)
              &#10

              Comment


              • #8
                Hi

                <pre>

                hProcess2 := OpenProcess(PROCESS_ALL_ACCESS OR SYNCHRONIZE, False, aTPI.hProcess);

                </pre>

                IST falsch, <b>aTPI.hProcess</b> ist ein Process-Handle, OpenProcess erwartet da eine ProcessID, <b>aTPI.dwProcessID</b> muß dahin.<br>

                Also nächstes würde <b>CREATE_SEPARATE_WOW_VDM</b> nicht verwenden sondern <b>CREATE_SHARED_WOW_VDM</b>, damit könnte es sein das NT bei 16Bit Anwendung diese auch zugreifbar sind. Sollte eine separate Virtuelle Machine gestartet werden (bei CREATE_SHARED_WOW_VDM) gibt es eine abgeshottete VDM auf deren Processe externe 32bit Processe KEINE Zugriff haben.<br>

                Also drittes erstelle den Process (CreateProcess()) im <b>CREATE_SUSPENDED</b> Mode, und ruf danach <b>ResumeThread(aTPI.hThread)</b> auf.
                SOLLTE die Anwendung STOPPEN und nach ResumeThread() LOSLEGEN, MUSST Du auch auf die Handles hThread/hProcess warten können. D.h. WaitFor..() sollte dann auf alle Fälle funktioniern.

                Gruß Hage

                Comment


                • #9
                  Hi

                  Vielleicht solltest Du Dir überlegen zu der 16Bit DOS-EXE eine PIF-Datei anzulegen und diese mit CreateProcess() zu starten. <br>
                  Vorstellbar wäre dann folgendes: Createprocess() startet einen 32Bit PIF-Datei Process und dieser die 16Bit DOS-Zielanwendung. Der PIF process MUSS warten, also sollte Dein process auf PIF-Process warten.
                  Alternativ mal mit eine BAT-Datei probieren.

                  Gruß Hage

                  Comment

                  Working...
                  X