Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Externes Programm mit ShellExecuteEx starten (https://www.delphipraxis.net/123756-externes-programm-mit-shellexecuteex-starten.html)

schwa226 7. Nov 2008 20:23


Externes Programm mit ShellExecuteEx starten
 
Hi,

ich habe ein Programm, dass ich per ShellExecuteEx starte.

mit
Delphi-Quellcode:
     repeat
       Application.ProcessMessages;
       GetExitCodeProcess(SEInfo.hProcess, ExitCode) ;
     until (ExitCode <> STILL_ACTIVE) or
Application.Terminated;
warte ich bis es beendet wurde.

Geht auch einwandfrei!

Nun möchte ich aber ein Programm starten, dass einen anderen Prozess startet und sich selber dann beendet:

Start.exe wird gestartet -> die Start.exe startet dann das MainProgramm.exe -> Start.exe wird wieder beendet.

Nun bekomme ich natürlich als ExitCode 0 zurück und mein Programm glaubt das das Programm beendet wurde (was eigentlich auch stimmt). Aber das eigentliche Hauptprogramm läuft aber noch und auf dieses will ich warten!

Was gäbe es hier für Möglichkeiten? Eventuell einfach eine Schleife mit FindWindow?

[edit=SirThornberry]Code Tags in delphi Tags geändert - nächstes mal bitte richtige Tags verwenden - Mfg, SirThornberry[/edit]
[edit=SirThornberry]Titel geändert - Mfg, SirThornberry[/edit]

Apollonius 7. Nov 2008 20:32

Re: Externes Programm mit ShellExecuteEx per starten
 
Die saubere Lösung verwendet Jobs. Jobs sind Sandkasten für Prozesse. Wenn ein Prozess in einem Job ist, sind seine Kindprozesse auch in dem Job (sofern der Besitzer des Jobs und der Prozess nicht beide sagen, dass dem nicht so sein soll). Du kannst dann die Anzahl aktiver Prozesse im Job abfragen. Wenn sie Null ist, kehrst du zurück. Für einen ereignisgesteuerten Ansatz kann man dem Job einen IO Completion Port zuweisen, welcher eine Nachricht erhält, wenn alle Prozesse beendet sind. Das muss allerdings in einem eigenen Thread geschehen, da es dann nicht möglich ist, auf Fensternachrichten zu reagieren.


Deine bisherige Lösung ist übrigens nicht optimal, da durch die Schleife unnötig CPU-Zeit verbraucht wird. Korrekt wäre es, mit MsgWaitForMultipleObjects auf das Handle zu warten.

schwa226 7. Nov 2008 21:13

Re: Externes Programm mit ShellExecuteEx per starten
 
Gibt es auch einen Info-Link zu dem Job Handling?

Suche per Google hat nichts gebracht :(

Danke!

Apollonius 7. Nov 2008 21:24

Re: Externes Programm mit ShellExecuteEx per starten
 
Job Objects

schwa226 10. Nov 2008 09:53

Re: Externes Programm mit ShellExecuteEx per starten
 
Danke für den MSDN Link!

Habe jetzt vollgendes Versucht:

Zuerst einen Job anlegen:
Code:
hJob := CreateJobObject(nil,'Test App JOB');
Dann einen Process mit CreatProcess starten:
Code:
..
..
hProcess := RunProcess('notepad.exe',SW_SHOW,False,ProcID);
..
..
function RunProcess(FileName: string; ShowCmd: DWORD; wait: Boolean; ProcID: PDWORD): Longword;
var
  StartupInfo: TStartupInfo;
  ProcessInfo: TProcessInformation;
  ExitInfo  : PExitThreadDebugInfo;
begin
  FillChar(StartupInfo, SizeOf(StartupInfo), #0);
  StartupInfo.cb         := SizeOf(StartupInfo);
  StartupInfo.dwFlags    := STARTF_USESHOWWINDOW or STARTF_FORCEONFEEDBACK;
  StartupInfo.wShowWindow := ShowCmd;


  if not CreateProcess(nil,@Filename[1],nil,nil,False,CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS,nil,nil,StartupInfo,ProcessInfo) then
  Result := WAIT_FAILED
  else
  begin

    AssignProcessToJobObject(hJob,ProcessInfo.hProcess);


    if wait = FALSE then
    begin
    //  if ProcID <> nil then ProcID^ := ProcessInfo.dwProcessId;
      exit;
    end;
    WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
    GetExitCodeProcess(ProcessInfo.hProcess, Result);
  end;
  if ProcessInfo.hProcess <> 0 then
    CloseHandle(ProcessInfo.hProcess);
  if ProcessInfo.hThread <> 0 then
    CloseHandle(ProcessInfo.hThread);
end;
Wenn es gestartet wurde mache ich mit AssignProcessToJobObject(hJob,ProcessInfo.hProcess ); den Process zu dem Job.

Wie kann ich nun überprüfen ob der Job noch Prozesse enthaltet?

Ich habe mit QueryInformationJobObject herumprobiert, jedoch ohne Erfolg!

Gibt es dazu noch ein Beispiel?

Auf jeden Fall scheint das mit dem Assignen zu funktionieren, denn wenn ich
Code:
TerminateJobObject(hJob,Exitcode);

CloseHandle(hJob);
mache schließt sich Notepad wieder!

schwa226 11. Nov 2008 13:43

Re: Externes Programm mit ShellExecuteEx per starten
 
Habe jetzt mit
Delphi-Quellcode:

type
      PJobObjectBasicProcessIDList  =  ^TJobObjectBasicProcessIDList;
      TJobObjectBasicProcessIDList  =  Record
          NumberOfAssignedProcesses  :  DWORD;
          NumberOfProcessIdsInList    :  DWORD;
          ProcessIdList                          :  Array[0..0]  of  ULONG;
      End;

var

  Info: TJobObjectBasicProcessIDList;
  Len    :  Cardinal;




Result := QueryInformationJobObject(hJob,JobObjectBasicProcessIdList,@Info,SizeOf(Info),@Len);
geschafft nachzusehen ob Prozesse im Job sind. Das geht auch! Starte ich einmal oder zweimal Notepad es ist immer in der Info.NumberOfAssignedProcesses die richtige Anzahl.

Wie ist jetzt am besten hier im Programm stehen zu bleiben bis Info.NumberOfAssignedProcesses = 0?

Und zwar so, dass das Programm aber noch andere Funktionen bearbeiten kann.

Mit einem repeat until? WaitForSingleObject?
Wenn diese beiden Methoden probiere ist ja mein Programm blockiert, oder?

Es soll ja das externe Programm gestartet werden - Check ob's läuft - wenn's läuft andere Funktionen ausführen wie z.B. Keyboard Events rüberschicken usw. - wenn das externe Programm beendet wird fährt auch mein Programm nieder.

Apollonius 11. Nov 2008 15:29

Re: Externes Programm mit ShellExecuteEx per starten
 
Es empfiehlt sich eher, über JobObjectBasicAccountingInformation und das Struktur-Element ActiveProcesses zu gehen. Bei der Prozess-ID-Liste sind vermutlich auch die terminierten Prozesse enthalten, oder zumindest jene, auf die noch jemand ein Handle hält.

Beim Warten darauf, dass alle Prozesse beenden werden, wird es kompliziert. Man kann zwar auf einen Job warten, das Objekt wird aber nicht etwa signalisiert, wenn alle Prozesse beendet sind, nein, es wird signalisiert, wenn die gesetzte Job-Zeit-Grenze erreicht ist (eigentlich sind Jobs dazu da, einer Gruppe von Prozessen Grenzen, z.B. für die Ausführungszeit und den Speicherverbrauch, zu geben, also eine Art Sandbox). Warum Microsoft das so eingerichtet hat, ist mir ein Rätsel. Um auf das Ende aller Prozess in einem Job zu warten und gleichzeitig Nachrichten zu verarbeiten, muss ein eigener Thread eingesetzt werden.
Zitat:

Zitat von Apollonius
Für einen ereignisgesteuerten Ansatz kann man dem Job einen IO Completion Port zuweisen, welcher eine Nachricht erhält, wenn alle Prozesse beendet sind. Das muss allerdings in einem eigenen Thread geschehen, da es dann nicht möglich ist, auf Fensternachrichten zu reagieren.

Für diese Technik musst du zuerst einen IO Completion Port erstellen (CreateIoCompletionPort). Mit SetInformationJobObject kannst du dann den Port für Benachrichtigungen eintragen. Auf Benachrichtigungen wartest du mit GetQueuedCompletionStatus. Und genau dort liegt das Problem: Es ist weder möglich, auf weitere Objekte zu warten, noch kann man auf Fensternachrichten oder APCs warten (letzteres geht ab Windows Vista mit GetQueuedCompletionStatusEx). Daher solltest du am Anfang deines Programms einen Thread starten, der mit unbegrenztem Timeout auf einen Completion Port wartet. Mit einem beliebigen Mechanismus kann er dem Hauptthread dann die Nachrichten zugänglich machen. Beenden kannst du den Thread, indem du mit PostQueuedCompletionStatus eine eigene Nachricht verschickst, die der Thread erkennt. Daraufhin sollte er sich beenden.

nat 11. Nov 2008 16:35

Re: Externes Programm mit ShellExecuteEx starten
 
lacht mich nicht aus, aber in welcher unit is denn CreateJobObject etc deklariert?
ich finde die nicht und selber deklarieren is doch recht viel tipperei ^^

Apollonius 11. Nov 2008 16:53

Re: Externes Programm mit ShellExecuteEx starten
 
In Windows.pas ist es tatsächlich nicht deklariert. Entweder du deklarierst es selbst oder nimmst die Units von anderen Leuten. JwaWindows von den Jedis enthält (fast) alles, was man an C-Übersetzungen benötigt.

nat 11. Nov 2008 17:13

Re: Externes Programm mit ShellExecuteEx starten
 
bin jetzt auch bei den jedis gelandet.
kannte die zwar, aber bin bisher auch immer ohne die ausgekommen.
nun bin ich mal nich drumrum gekommen ^^

schwa226 11. Nov 2008 19:33

Re: Externes Programm mit ShellExecuteEx starten
 
Delphi-Quellcode:

type

      PJobObjectBasicProcessIDList  =  ^TJobObjectBasicProcessIDList;
      TJobObjectBasicProcessIDList  =  Record
          NumberOfAssignedProcesses  :  DWORD;
          NumberOfProcessIdsInList    :  DWORD;
          ProcessIdList                          :  Array[0..0]  of  ULONG;
      End;

const
  {$IFDEF UNICODE}
  AWSuffix = 'W';
  {$ELSE}
  AWSuffix = 'A';
  {$ENDIF UNICODE}

const

  JOB_OBJECT_ASSIGN_PROCESS         = $0001;
  {$EXTERNALSYM JOB_OBJECT_ASSIGN_PROCESS}
  JOB_OBJECT_SET_ATTRIBUTES         = $0002;
  {$EXTERNALSYM JOB_OBJECT_SET_ATTRIBUTES}
  JOB_OBJECT_QUERY                  = $0004;
  {$EXTERNALSYM JOB_OBJECT_QUERY}
  JOB_OBJECT_TERMINATE              = $0008;
  {$EXTERNALSYM JOB_OBJECT_TERMINATE}
  JOB_OBJECT_SET_SECURITY_ATTRIBUTES = $0010;
  {$EXTERNALSYM JOB_OBJECT_SET_SECURITY_ATTRIBUTES}
  JOB_OBJECT_ALL_ACCESS             = STANDARD_RIGHTS_REQUIRED or SYNCHRONIZE or $1F ;
  {$EXTERNALSYM JOB_OBJECT_ALL_ACCESS}

//
// Extended Limits
//

  JOB_OBJECT_LIMIT_PROCESS_MEMORY            = $00000100;
  {$EXTERNALSYM JOB_OBJECT_LIMIT_PROCESS_MEMORY}
  JOB_OBJECT_LIMIT_JOB_MEMORY                = $00000200;
  {$EXTERNALSYM JOB_OBJECT_LIMIT_JOB_MEMORY}
  JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION = $00000400;
  {$EXTERNALSYM JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION}
  JOB_OBJECT_LIMIT_BREAKAWAY_OK              = $00000800;
  {$EXTERNALSYM JOB_OBJECT_LIMIT_BREAKAWAY_OK}
  JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK       = $00001000;
  {$EXTERNALSYM JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK}
  JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE         = $00002000;
  {$EXTERNALSYM JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE}

const

function CreateJobObjectA(lpJobAttributes: PSecurityAttributes; lpName: LPCSTR): THANDLE; stdcall;
{$EXTERNALSYM CreateJobObjectA}
function CreateJobObjectW(lpJobAttributes: PSecurityAttributes; lpName: LPCWSTR): THANDLE; stdcall;
{$EXTERNALSYM CreateJobObjectW}
function CreateJobObject(lpJobAttributes: PSecurityAttributes; lpName: LPCTSTR): THANDLE; stdcall;
{$EXTERNALSYM CreateJobObject}

function OpenJobObjectA(dwDesiredAccess: DWORD; bInheritHandle: BOOL; lpName: LPCSTR): THANDLE; stdcall;
{$EXTERNALSYM OpenJobObjectA}
function OpenJobObjectW(dwDesiredAccess: DWORD; bInheritHandle: BOOL; lpName: LPCWSTR): THANDLE; stdcall;
{$EXTERNALSYM OpenJobObjectW}
function OpenJobObject(dwDesiredAccess: DWORD; bInheritHandle: BOOL; lpName: LPCTSTR): THANDLE; stdcall;
{$EXTERNALSYM OpenJobObject}

function AssignProcessToJobObject(hJob, hProcess: THANDLE): BOOL; stdcall;
{$EXTERNALSYM AssignProcessToJobObject}

function TerminateJobObject(hJob: THANDLE; uExitCode: UINT): BOOL; stdcall;
{$EXTERNALSYM TerminateJobObject}

function IsProcessInJob(ProcessHandle, JobHandle: THANDLE; var Result_: BOOL): BOOL; stdcall;
{$EXTERNALSYM IsProcessInJob}

Function  QueryInformationJobObject(hJob  :  THandle;
                        JobObjectInformationClass  :  TJobObjectInfoClass;
                        lpJobObjectInformation  :  Pointer;
                        cbJobObjectInformationLength  :  DWORD;
                        lpReturnLength  :  PDWORD)  :  Bool;  StdCall;
                        External  Kernel32   Name  'QueryInformationJobObject';

function CreateJobObjectA; external kernel32 name 'CreateJobObjectA';
function CreateJobObjectW; external kernel32 name 'CreateJobObjectW';
function CreateJobObject; external kernel32 name 'CreateJobObject' + AWSuffix;
function OpenJobObjectA; external kernel32 name 'OpenJobObjectA';
function OpenJobObjectW; external kernel32 name 'OpenJobObjectW';
function OpenJobObject; external kernel32 name 'OpenJobObject' + AWSuffix;
function AssignProcessToJobObject; external kernel32 name 'AssignProcessToJobObject';
function TerminateJobObject; external kernel32 name 'TerminateJobObject';
function IsProcessInJob; external kernel32 name 'IsProcessInJob';

nat 12. Nov 2008 01:23

Re: Externes Programm mit ShellExecuteEx starten
 
also ich habe jetzt ma die o.g. vorgehensweise von Apollonius ausprobiert.
was ich (testweise) wollte ist eine datei mit dem windows öffnen-mit dialog öffnen
(rundll32.exe shell32.dll,OpenAs_RunDLL blabla.txt). der user wählt dann
ein programm aus (zum test notepad), bearbeitet die datei, und wenn das
programm beendet wird lädt mein programm die datei neu. da ich nich an irgendein
handle des vom user ausgewählten/gestarteten progamm komme, ich aber auf das ende
dieses programms warten möchte, habe ich das, wie oben in diesem thread beschrieben,
mit einem JobObject und einem IoCompletionPort gemacht.
klappt eigentlich auch ganz gut, bis auf eine kleinigkeit:

wie gesagt, zum test habe ich meien textdatei mit notepad geöffnet (über den öffnen-mit dialog).
in notepad habe ich dann über den öffnen-dialog eine andere datei geöffnet
(rechtsklick auf eine datei und dann z.B. senden an o.ä.). dies erzeugt dann einen
weiteren prozess der (logischerweise) dann ja auch zu dem jobobject gehört.
soweit ist ja auch alles ok.
wenn ich nun aber notepad schliesse, ist in dem job ja immer noch ein aktiver
prozess (das programm das über den öffnen dialog gestartet wurde).
der prozess (notepad) auf den ich warten wollte ist aber ja schon beendet.
von daher könnte mein programm ja nun "aufwachen" und mit seiner arbeit weiter
machen (textdatei neu laden). dies passiert nun aber erst, wenn der andere prozess auch
beendet wird (da ich auf ActiveProcesses = 0 teste).
kann ich nich irgendwie nur auf einen bestimmten prozess in dem jobobject warten
(den ich natürlich nachträglich erst auslesen muß, sprich nachdem der öffnen-mit
dialog das programm gestartet hat)?

ich hoffe ihr versteht was ich meine :)

edit:
ich habe es nun doch noch selber hingekommen (man muss nur ma in ruhe sdks lesen *g*).
wen es interessiert wie ichs gemacht hab:
Delphi-Quellcode:
procedure TForm1.Button2Click(Sender: TObject);
var
  Filename: String;
  pi: windows.TProcessInformation;
  hJob, hProc: THandle;
  aExitCode: Cardinal;
  InfoBasic: TJobObjectBasicAccountingInformation;
  InfoPIDs: TJobObjectBasicProcessIdList;
  Len: Cardinal;
  count: integer;
  PID: Cardinal;
begin
  Filename := IncludeTrailingPathDelimiter(ExtractFilePath(Paramstr(0)))+'text.txt';
  Memo1.Lines.SaveToFile(Filename);

  if not RunProcess('rundll32.exe shell32.dll,OpenAs_RunDLL ' + Filename, SW_SHOW, pi) then
    raise Exception.CreateFmt('"%s" konnte nicht gestartet werden', [Filename]);

  try
    hJob := CreateJobObject(nil, nil);
    if not AssignProcessToJobObject(hJob, pi.hProcess) then
      raise Exception.Create(SysErrorMessage(GetLastError));

    WaitForSingleObject(pi.hProcess, INFINITE);

    count := 0;
    repeat
      Sleep(100);
      if not QueryInformationJobObject(hJob, JobObjectBasicAccountingInformation, @InfoBasic, sizeof(InfoBasic), @Len) then
        raise Exception.Create(SysErrorMessage(GetLastError));
      count := InfoBasic.ActiveProcesses;
    until count >= 1;


    if not QueryInformationJobObject(hJob, JobObjectBasicProcessIdList, @InfoPIDs, sizeof(InfoPIDs), @Len) then
      raise Exception.Create(SysErrorMessage(GetLastError));

    if InfoPIDs.NumberOfProcessIdsInList <= 0 then
      raise Exception.Create('Prozess nicht gefunden!');

    PID := InfoPIDs.ProcessIdList[0];
    hProc := OpenProcess(SYNCHRONIZE, false, PID);
    if hProc = 0 then
      raise Exception.Create('Kann Prozess nicht öffnen');

    try
      WaitForSingleObject(hProc, INFINITE);
      Memo1.Lines.LoadFromFile(Filename);
    finally
      CloseHandle(hProc);
    end;
  finally
    CleanUpHandles(pi);
    CloseHandle(hJob);
  end;
end;
der code ist nicht grade optimiert. wie gesagt, ist nur zum test gewesen.
wenn ich was falsch gemacht hab oder es anders besser geht bin ich
aber für kritik offen! ;)
soweit ich das sehen kann geht das in diesem fall hier so ganz gut.
das ganze muß man dann vll nur ma in n thread packen damit die
anwendung nicht "einfriert".

schwa226 22. Jan 2009 17:57

Re: Externes Programm mit ShellExecuteEx starten
 
Habe jetzt dazu noch eine Frage!

Ich habe ja ein Programm, dass ich dem Job zuweise. Dieses Programm (start.exe) startet eine andere program.exe und beendet sich sofort wieder.

Im Job ist ersichtlich wenn alle Child Processe beendet wurden. Das funktioniert!

Nun möchte ich über die ThreadID aber das Handle von dem Fenster haben.

Ich bekomme bei CreateProcess ja die ThreadID, aber die "start.exe" beendet sich anscheinend so schnell wieder, dass ich über die ThreadID nichts herausbekomme.

Bei einem anderen Programm z.B. Notepad.exe funktoniert das ja ohne Probleme.

Kann ich irgendewie die ThreadIDs die in einem Job sind herausfinden? Oder die ThreadID die die start.exe erzeugt, wenn dieprogram.exe gestartet wird!?

Apollonius 22. Jan 2009 18:09

Re: Externes Programm mit ShellExecuteEx starten
 
Du kannst dir mit QueryInformationJobObject die Prozess-IDs besorgen. Mit der ToolHlp-API kannst du dir dann alle Threads in einem Prozess ausgeben lassen.

schwa226 22. Jan 2009 18:37

Re: Externes Programm mit ShellExecuteEx starten
 
Danke!

Habe es gerade probiert und es geht!

Nun bekomme ich eine ThreadID die richtig zu sein scheint!


Alle Zeitangaben in WEZ +1. Es ist jetzt 10:29 Uhr.

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz