Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Win32/Win64 API (native code) (https://www.delphipraxis.net/17-win32-win64-api-native-code/)
-   -   Delphi ShellExecute() >> WindowHandle (https://www.delphipraxis.net/136375-shellexecute-windowhandle.html)

taaktaak 29. Jun 2009 14:36


ShellExecute() >> WindowHandle
 
Moin, Moin.

Ein kleines Problem, welches sich hartnäckig allen Lösungsversuchen widersetzt. Aufrufs eines Links (im Beispiel das Programm calc.exe) mit ShellExecute. Anschließend benötige ich das Fensterhandle des gestarteten Programms. Mein Lösungsansatz ist folgender:
Delphi-Quellcode:
function GetWinHandle(HInstance:LongInt):HWnd;
var TmpHWnd   : HWnd;
    WinCaption : PChar;
begin
  GetMem(WinCaption,255); // für test-anzeige

  Result :=0;
  TmpHWnd:=FindWindow(nil,nil);

  while TmpHWnd<>0 do begin

    GetWindowText(TmpHWnd,WinCaption,255); // test-anzeige
    if WinCaption<>'' then
      ShowMessage('Find=' +IntToStr(HInstance)                          +#13#13+
                  'HWnd=' +IntToStr(TmpHWnd)                            +#13+
                  'HInst='+IntToStr(GetWindowLong(TmpHWnd,GWL_HINSTANCE))+#13+
                  'WinCaption='+WinCaption);

    if GetWindowLong(TmpHWnd,GWL_HINSTANCE)=HInstance then begin

      Result:=TmpHWnd;
      Break;
      end;

    TmpHWnd:=GetWindow(TmpHWnd,GW_HWNDNEXT)
    end;

end;

procedure ExecuteFile;
var HInstance : LongInt;
    WndHnd   : HWnd;
begin
  HInstance:=ShellExecute(Self.Handle,'open',PChar('calc.exe'),nil,nil,SW_ShowNormal);
  WndHnd  :=GetWinHandle(HInstance);
  // ...
end;

begin
  ExecuteFile
end;
Laut MS gibt ShellExecute() als Funktionsergebnis das "instance handle of the application" zurück. Nach meiner Interpretation das "instance handle" des mit ShellExecute() aufgerufenen Programms. Nun muss das "window handle" gesucht werden. Dies versuche ich mit der Funktion GetWinHandle() zu erledigen. Funktioniert nur leider nicht.

GetWinHandle() findet zwar das gesuchte Fenster und ermittelt auch das korrekte Fensterhandle - aber der Vergleich mit dem Übergabe-Parameter "HINstance" wird nie wahr!? Warum? Das Funktionsergebnis von ShellExecute() ist im meinem Beispiel der Wert 42 - der korrekte Wert von HInstance ist aber laut GetWinHandle() = 16 777 216.

Hmm, wo liegt mein Denkfehler?

Popov 29. Jun 2009 14:58

Re: ShellExecute() >> WindowHandle
 
Ich glaube CreateProcess liefert das Handle zurück, also brauchst du da nicht zu triksen.

Ich würde dir gerne mit etwas Code aushelfen, aber selbst nutze ich die Funktion selten. Aber hier im Forum wirst du paar Beispiele finden.

Popov 29. Jun 2009 15:07

Re: ShellExecute() >> WindowHandle
 
Bei der Gelegenheit ein kleiner Trick. Wenn du ein Zeilenumbruch haben willst, dann ist ^j einfacher als #13#10.

Also nicht

Delphi-Quellcode:
ShowMessage('Hallo' + #13#10 + 'Welt!');
sondern einfacher

Delphi-Quellcode:
ShowMessage('Hallo' + ^j + 'Welt!');
Für zwei Zeilen: ^j^j

Delphi-Quellcode:
ShowMessage('Hallo' + ^j^j + 'Welt!');

DeddyH 29. Jun 2009 15:12

Re: ShellExecute() >> WindowHandle
 
Moin Ralph,

und wenn Du ShellExecuteEx verwendest und anschließend mit EnumThreadWindows die Fenster durchgehst? Nur so eine Idee.

[edit] Achnee, dazu brauchst Du das Threadhandle, das bekommst Du mit ShellExecuteInfo nicht (oder ich hab es übersehen). Also doch CreateProcess oder andersrum machen: mit EnumWindows und GetWindowThreadProcessId durchiterieren und vergleichen. Da dürfte die erste Möglichkeit aber vermutlich performanter sein. [/edit]

Apollonius 29. Jun 2009 16:15

Re: ShellExecute() >> WindowHandle
 
Zitat:

Zitat von taaktaak
Laut MS gibt ShellExecute() als Funktionsergebnis das "instance handle of the application" zurück. Nach meiner Interpretation das "instance handle" des mit ShellExecute() aufgerufenen Programms.

Arbeitest du noch mit vor-NT-Windows? Andernfalls sagt die Dokumentation dies:
Zitat:

The return value is cast as an HINSTANCE for backward compatibility with 16-bit Windows applications. It is not a true HINSTANCE, however. It can be cast only to an int and compared to either 32 or the following error codes below.
Daneben sind HInstances Zeiger und somit prozessspezifisch. Deinen Ansatz kannst du damit auf NT-Systemen vergessen.

Grundsätzlich gibt es drei Ansätze, für die du alle ShellExecuteEx oder CreateProcess verwenden musst: EnumWindows verwenden und jedes Mal die Prozess-ID überprüfen (hat DeddyH schon vorgeschlagen), mit der Toolhelp-API alle Threads des Prozesses ermitteln und alle Fenster der Threads ermitteln oder annehmen, dass der erste Thread das Fenster erzeugt und direkt EnumThreadWindows verwenden, wobei CreateProcess dir die ID dieses Threads gibt.

SirThornberry 29. Jun 2009 16:22

Re: ShellExecute() >> WindowHandle
 
Bei der Aufgabenstellung gibt es ein grundsätzliches Problem. Es gibt nicht DAS Fenster bei einem Prozess. Ein Prozess kann auch gar keine Fenster haben. Oder eben 2,3,4....
Entsprechend gibt es auch keine Funktion um das Fenster zu bekommen. Man kann sich also nur alle Fenster eines Prozesses besorgen und dann das heraus suchen was man wirklich will.

@Popov: ich verwende auch lieber #13#10. Der grund ist recht einfach. Ein ^j sagt nicht darüber aus was wirklich dahinter steckt. Man weiß nicht ob es einem #13#10 entspricht, einem #13 oder nur einem #10. Wenn man wirklich explizit ein #13#10 haben will ist man auf der sicheren Seite wenn man genau das hinschreibt. Andernfalls kann es einem passieren das bei der nächstne Delphiversion das ^j plötzlich für etwas anderes steht.

taaktaak 29. Jun 2009 17:07

Re: ShellExecute() >> WindowHandle
 
Vielen Dank für die Antworten!
Damit ist ShellExecute() also endgültig abserviert. Zur Aussage mit dem Instanz-Handle: Da hat mich die Delphi7-Hilfe ganz schön in die Irre geführt. Tja besser also bei MSDN nachschauen! Kernproblem ist natürlich mein "solides Halbwissen" :stupid:
Ok - also CreateProcess(), dann schauen wir mal weiter
:hi:

taaktaak 29. Jun 2009 21:35

Re: ShellExecute() >> WindowHandle
 
So, ich denke, ich habe es jetzt hinbekommen - die ersten Tests funktionieren zufriedenstellend. Zum besseren Verständnis zunächst die Aufgabenstellung etwas konkreter als bisher beschrieben:

In einem RichEdit sind Links auf beliebige Dateien placiert (Bilder, Texte o.ä.). Ein Klick auf die Links soll die Dateien mit dem verknüpften Programm anzeigen lassen. Damit kann man sich natürlich, ausreichend oft geklickt, auch große Bildschirme schnell "zumüllen" - aus diesem Grund soll optional der mehrfache Aufruf verhindert werden und stattdessen das bereits gestartete Programmfenster wieder in den Vordergrund geholt werden.

Beispielhaft habe ich das jetzt so gelöst:

Delphi-Quellcode:
uses ShellAPI;

type PEnumInfo   = ^TEnumInfo;
     TEnumInfo   = record
                      ProcessID : DWORD;
                      HWND     : THandle;
                      end;

     tCalledLinks = record
                      LinkText : String;
                      WinHandle : DWord;
                      end;

var CalledLinks : tCalledLinks; // use array later on

procedure AddProcess(LnkText:String;WinHandle:HWnd);
begin
  CalledLinks.LinkText :=LnkText;
  CalledLinks.WinHandle:=WinHandle;
  (*
  add record to array
  *)
end;

function CanSetToForeGround(LnkText:String):Boolean;
begin
  Result:=SetForeGroundWindow(CalledLinks.WinHandle);
  (*
  if not(Result) then begin
   // remove record from array
   end
  *)
end;

function GetAssociatedApp(FName:String):String;
begin
  SetLength(Result,MAX_PATH);
  if FindExecutable(PChar(FName),
                    nil,
                    PChar(Result))>32 then SetLength(Result,StrLen(PChar(Result)))
                                      else Result:=''
end;

function RunProcess(FName:String;ShowCmd:DWORD;Wait:Boolean;var ProcID:DWord):LongWord;
var StartupInfo : TStartupInfo;
    ProcessInfo : TProcessInformation;
begin
  FillChar(StartupInfo,SizeOf(StartupInfo),#0);
  StartupInfo.cb        :=SizeOf(StartupInfo);
  StartupInfo.dwFlags   :=STARTF_USESHOWWINDOW or STARTF_FORCEONFEEDBACK;
  StartupInfo.wShowWindow:=ShowCmd;
  if not(CreateProcess(nil,
                       @FName[1],
                       nil,
                       nil,
                       False,
                       CREATE_NEW_CONSOLE or
                       NORMAL_PRIORITY_CLASS,
                       nil,
                       nil,
                       StartupInfo,
                       ProcessInfo)) then Result:=WAIT_FAILED
                                     else begin
    if not(Wait) then begin
      WaitForInputIdle(ProcessInfo.hProcess,INFINITE);
      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;

function EnumWindowsProc(Wnd:DWORD;var EI:TEnumInfo):Boolean; stdcall;
var PID : DWORD;
begin
  GetWindowThreadProcessID(Wnd,@PID);
  Result:=(PID<>EI.ProcessID) or (not IsWindowVisible(WND)) or (not IsWindowEnabled(WND));
  if not(Result) then EI.HWND:=WND
end;

function FindAppWindow(PID:DWORD):DWORD;
var EI : TEnumInfo;
begin
  EI.ProcessID:=PID;
  EI.HWND    :=0;
  EnumWindows(@EnumWindowsProc,Integer(@EI));
  Result:=EI.HWND
end;

procedure CallLink(DataFile:String;AdmitMultipleCalls:Boolean);
var AppName,
    ProcessPara : String;
    ProcID     : Cardinal;
begin
  AppName   :=GetAssociatedApp(DataFile);
  ProcessPara:=AppName+' '+DataFile;

  if AdmitMultipleCalls or
     not(CanSetToForeGround(DataFile)) then
 
  if RunProcess(ProcessPara,
                SW_SHOWNORMAL,
                false,
                ProcID)=WAIT_FAILED then ShowMessage('Create process error')
                                    else

    if not(AdmitMultipleCalls) then AddProcess(DataFile,FindAppWindow(ProcID))

end;

procedure TfoTest15.buShowLinkClick(Sender:TObject);
begin
  CallLink('d:\data_p\delphi\delphiguide\bitmaps\Edit_0000_Save.bmp',
           cbAdmitMultipleCalls.Checked)
end;

Luckie 29. Jun 2009 22:52

Re: ShellExecute() >> WindowHandle
 
Zitat:

Zitat von taaktaak
Zum besseren Verständnis zunächst die Aufgabenstellung etwas konkreter als bisher beschrieben:

In einem RichEdit sind Links auf beliebige Dateien placiert (Bilder, Texte o.ä.). Ein Klick auf die Links soll die Dateien mit dem verknüpften Programm anzeigen lassen. Damit kann man sich natürlich, ausreichend oft geklickt, auch große Bildschirme schnell "zumüllen" - aus diesem Grund soll optional der mehrfache Aufruf verhindert werden und stattdessen das bereits gestartete Programmfenster wieder in den Vordergrund geholt werden.

Argh, und warum kommt das erst jetzt zum Schluss? Meist du nicht auch, dass es besser ist, sowas gleich im ersten Posting mitzuteilen?

taaktaak 30. Jun 2009 06:31

Re: ShellExecute() >> WindowHandle
 
Moin Luckie :-D

Na, ich wollte nicht zu viel schreiben. Es ist ja keine Umorientierung, sondern eher eine Konkretisierung der Ausgangsanforderung. Auch mit den eher allgemein formulierten Angaben im ersten Post bin ich ja praktisch direkt zur Verwendung von CreateProcess() "geführt" worden.

Aber du hast natürlich Recht - ich gelobe Besserung
:thumb:


Alle Zeitangaben in WEZ +1. Es ist jetzt 23:32 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-2025 by Thomas Breitkreuz