![]() |
1. Instanz nach vorn bringen, wenn 2. geöffnet wird - fensterunabhängig!
Hallo,
unser Programm darf nur 1x geöffnet werden. Dazu verwenden wir ein benanntes Mutex-Objekt (Win32-API-Call CreateMutex). Für den Fall, dass ich von GetLastError = ERROR_ALREADY_EXISTS zurückbekomme, möchte ich gerne die erste Instanz des Programms nach vorne holen - unabhängig davon, was da gerade für Fenster offen sind. Allerdings scheitere ich schon daran, die Prozess-ID oder irgendetwas anderes von dem Prozess, der das Mutex-Objekt besitzt (also die erste Instanz des Programms), herauszubekommen. Der Grund ist einfach meine Unkenntnis der Win-API. Wahrscheinlich verwende ich nicht die richtigen Suchbegriffe. Ich möchte nicht nach einem Fenster mit einem bestimmten Caption suchen, dann würde ich es lieber lassen. Allerdings bin ich mir sicher, dass es durchaus elegantere Wege gibt als diesen. |
AW: 1. Instanz nach vorn bringen, wenn 2. geöffnet wird - fensterunabhängig!
Zitat:
|
AW: 1. Instanz nach vorn bringen, wenn 2. geöffnet wird - fensterunabhängig!
Fenster kann man nicht nur anhand der Caption, sondern auch anhand des Klassennamens suchen, das ist schon deutlich eindeutiger.
Ansonsten: ne Pipe? Darüber können ggfls. auch gleich Parameter übermittelt werden (etwa wenn die zweite Instanz mit nem Dateinamen zum Öffnen als Parameter gestartet wird - soll dann ja die erste Instanz diese Datei wahrscheinlich öffnen), und die erste Instanz kann sich einfach selbst wieder in den Vordergrund holen. |
AW: 1. Instanz nach vorn bringen, wenn 2. geöffnet wird - fensterunabhängig!
@mjustin:
TJclAppInstances war prinzipiell eine gute Idee zum Nachforsten, aber ich steige da überhaupt nicht durch. Da wird irgendetwas mit File Mapping gemacht - kenne ich bisher nicht, scheint aber eine gemeinsame Datei vorauszusetzen, die von mehreren Prozessen gleichzeitig benutzt wird und in der dann Informationen gesammelt werden. Das Ganze erscheint mir zu aufwendig für meinen Einsatzzweck. @CCRDude: Von Pipes habe ich bisher auch nichts gewusst, aber folgendes scheint ein einfacher und zielführender Weg zu sein: Bei Programmstart versuche ich eine Pipe mit dem Parameter FILE_FLAG_FIRST_PIPE_INSTANCE zu erstellen. Gelingt das, so ist es die erste Programminstanz. Schlägt das fehl, so verbinde ich zu der bestehenden Pipe und informiere die erste Instanz, dass sie sich nach vorn bringen soll (Application.BringToFront) und beende die aktuelle (zweite) Instanz. Ich werde mich morgen mit dem Handling der Pipes genauer auseinandersetzen, habe jetzt nur überflogen, was Pipes sind ;-) Wenn dabei für mich unlösbare Probleme auftreten, melde ich mich nochmal, ansonsten: Danke für die Lösung! |
AW: 1. Instanz nach vorn bringen, wenn 2. geöffnet wird - fensterunabhängig!
Der Ansatz, daß sich die erste Instanz selbst in den Vordergrund bringt, wird in den meisten Fällen schief gehen. Windows erlaubt es nämlich i.A. nicht, daß ein Prozess sich selbst in den Vordergrund drängelt (da könnte ja jeder kommen). Dies ist unter anderem aber dem Prozess erlaubt, der gerade gestartet wurde - also der zweiten Instanz. Ich empfehle daher, das so zu implementieren, daß doch die zweite Instanz die erste in den Vordergrund holt.
|
AW: 1. Instanz nach vorn bringen, wenn 2. geöffnet wird - fensterunabhängig!
Also das mit den Pipes habe ich jetzt implementiert. Selbst ohne Kommunikation durch die Pipes kann ich folgendes erreichen:
Ich sehe folgende Probleme incl. Lösungen bei meinem Ansatz:
So, jetzt habe ich euch erstmal mit aktuellen Infos versorgt und werde mittagessen gehen. Wenn bis danach keine Einwände gegen das Vorgehen gepostet wurden, werde ich die Umsetzung dann angehen ;-) Mahlzeit! |
AW: 1. Instanz nach vorn bringen, wenn 2. geöffnet wird - fensterunabhängig!
Fast geschafft... Fast! Hier ist mein Code:
Delphi-Quellcode:
Ich habe mein Bestes gegeben, Google und MSDN gelesen, aber an den 3 Punkten oben, die mit ##### gekennzeichnet sind, komme ich nicht weiter.
var
PID: ULONG; // Process-ID von 1. Instanz HPipe: THandle = INVALID_HANDLE_VALUE; // wird für Server (= 1. Instanz, CreateNamedPipe) und für Client (= 2. Instanz, CreateFile) verwendet HSnap: THandle = INVALID_HANDLE_VALUE; ThreadEntry: TThreadEntry32; // aus Unit TlHelp32 GUIThreadInfo: TGUIThreadInfo; function GetNamedPipeServerProcessId(hNamedPipe: THandle; out ServerProcessId: ULONG): BOOL; stdcall; external kernel32 name 'GetNamedPipeServerProcessId'; initialization HPipe := CreateNamedPipe(PipeName, PIPE_ACCESS_OUTBOUND or FILE_FLAG_FIRST_PIPE_INSTANCE, PIPE_TYPE_BYTE or PIPE_READMODE_BYTE or PIPE_WAIT, 1, 4, 4, 0, nil); // erste Pipe-Instanz erstellen if HPipe = INVALID_HANDLE_VALUE then begin // wenn Pipe bereits besteht HPipe := CreateFile(PipeName, GENERIC_READ, 0, nil, OPEN_EXISTING, 0, 0); // auf Pipe verbinden if HPipe <> INVALID_HANDLE_VALUE then begin if GetNamedPipeServerProcessId(HPipe, PID) then // Prozess-ID des Pipe-Servers erfragen (= 1. Instanz) begin FillChar(ThreadEntry, SizeOf(ThreadEntry), 0); ThreadEntry.dwSize := SizeOf(ThreadEntry); HSnap := CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, PID); // Schnappschuss aller Threads erstellen if (HSnap <> INVALID_HANDLE_VALUE) and Thread32First(HSnap, ThreadEntry) then repeat // Threads durchlaufen if ThreadEntry.th32OwnerProcessID = PID then begin // wenn Thread zu Prozess der 1. Instanz gehört FillChar(GUIThreadInfo, SizeOf(GUIThreadInfo), 0); GUIThreadInfo.cbSize := SizeOf(GUIThreadInfo); // bis hierhin klappt alles wunderbar if GetGUIThreadInfo(ThreadEntry.th32ThreadID, GUIThreadInfo) then // GUIThreadInfo beschaffen begin if (GUIThreadInfo.hwndActive > 0) then begin // wenn aktives Fenster gesetzt ist - ist immer null - Warum? ##### SetForegroundWindow(GUIThreadInfo.hwndActive); // Fenster in Vordergrund holen Break; end; end else // beim 3. Durchlauf/Thread kommt ERROR_INVALID_PARAMETER - Warum? ##### ShowMessage('GetGUIThreadInfo is unsuccessful: ' + IntToStr(GetLastError)); end; until not Thread32Next(HSnap, ThreadEntry); // nächsten Thread untersuchen end; end; // wenn Verbindung zur Pipe erfolgreich, Meldung anzeigen -> aktuelle Instanz ist die 2. // wenn Verbindung zur Pipe nicht erfolgreich, dann Meldung anzeigen wenn GetLastError <> ERROR_PIPE_BUSY -> keine Meldung für die 3. Instanz, die gleichzeitig geöffnet ist if (HPipe <> INVALID_HANDLE_VALUE) or (GetLastError <> ERROR_PIPE_BUSY) then ShowMessage('Das Programm läuft bereits'); Halt; end; finalization // Pipe-Handle freigeben - Server und Client if (HPipe <> INVALID_HANDLE_VALUE) and CloseHandle(HPipe) then HPipe := INVALID_HANDLE_VALUE; // Falls es sich um Client/2. Instanz handelt, dann ist die Pipe trotzdem weiterhin besetzt (weitere/spätere Verbindungsversuche verursachen ERROR_PIPE_BUSY) - Warum? ##### end.
|
AW: 1. Instanz nach vorn bringen, wenn 2. geöffnet wird - fensterunabhängig!
Um die offenen Fragen aus meinem letzten Post zu umgehen, habe ich nun umgebaut:
Delphi-Quellcode:
Dadurch erübrigen sich 2 der 3 Punkte, aber einer bleibt bestehen:
var
PID: ULONG; // Process-ID von 1. Instanz HPipe: THandle = INVALID_HANDLE_VALUE; // wird für Server (= 1. Instanz, CreateNamedPipe) und für Client (= 2. Instanz, CreateFile) verwendet function GetNamedPipeServerProcessId(hNamedPipe: THandle; out ServerProcessId: ULONG): BOOL; stdcall; external kernel32 name 'GetNamedPipeServerProcessId'; function EnumWindowsProcCallback(HWnd: THandle; PID: LPARAM): BOOL; stdcall; var WinPID: DWORD; begin GetWindowThreadProcessId(HWnd, WinPID); Result := WinPID <> (PULONG(PID))^; if not Result then SetForegroundWindow(HWnd); end; initialization HPipe := CreateNamedPipe(PipeName, PIPE_ACCESS_OUTBOUND or FILE_FLAG_FIRST_PIPE_INSTANCE, PIPE_TYPE_BYTE or PIPE_READMODE_BYTE or PIPE_WAIT, 1, 4, 4, 0, nil); // erste Pipe-Instanz erstellen if HPipe = INVALID_HANDLE_VALUE then begin // wenn Pipe bereits besteht HPipe := CreateFile(PipeName, GENERIC_READ, 0, nil, OPEN_EXISTING, 0, 0); // auf Pipe verbinden if HPipe <> INVALID_HANDLE_VALUE then begin if GetNamedPipeServerProcessId(HPipe, PID) then // Prozess-ID des Pipe-Servers erfragen (= 1. Instanz) EnumWindows(@EnumWindowsProcCallback, LPARAM(@PID)); // Fenster durchforsten end; // wenn Verbindung zur Pipe erfolgreich, Meldung anzeigen -> aktuelle Instanz ist die 2. // wenn Verbindung zur Pipe nicht erfolgreich, dann Meldung anzeigen wenn GetLastError <> ERROR_PIPE_BUSY -> keine Meldung für die 3. Instanz, die gleichzeitig geöffnet ist if (HPipe <> INVALID_HANDLE_VALUE) or (GetLastError <> ERROR_PIPE_BUSY) then MessageBox(0, 'Das Programm läuft bereits', '', MB_SYSTEMMODAL or MB_SETFOREGROUND or MB_TOPMOST); Halt; end; finalization // Pipe-Handle freigeben - Server und Client if (HPipe <> INVALID_HANDLE_VALUE) and CloseHandle(HPipe) then HPipe := INVALID_HANDLE_VALUE; // Falls es sich um Client/2. Instanz handelt, dann ist die Pipe trotzdem weiterhin besetzt (weitere/spätere Verbindungsversuche verursachen ERROR_PIPE_BUSY) - Warum? ##### end. Zitat:
|
AW: 1. Instanz nach vorn bringen, wenn 2. geöffnet wird - fensterunabhängig!
Anstelle mit Hilfe von Pipes lässt sich das Problem auch grundsätzlich anders lösen:
Man kann in den diversen Formfenstern des Programms individuelle Hooks der jeweiligen WndProc setzen. Dann ist es möglich, dort die beim Start der 2.Instanz abgesetzte HWND_BROADCAST-Message abzufangen und geeignet zu behandeln: // in der MainForm:
Delphi-Quellcode:
// in jeder einzelnen Form-Unit (hier exemplarisch "FormX"):
Interface
const // z.B. mit GUI-Code zweifelsfrei individualisieren UniqueAppTitle = 'MyApp#21218E21-EF54-45D9-AAA0-8F4E7455D5AE'; var UniqueAppMsg: DWord; Implementation {...} initialization UniqueAppMsg := RegisterWindowMessage(pChar(UniqueAppTitle)); end.
Delphi-Quellcode:
// im Projektfile:
Implementation
uses Mainunit; // unit der Mainform var OriginalWndProc: Pointer; CurrentFormHandle: hWnd; function FormX_HookedWndProc(FormHandle: hWnd; MessageID: LongInt; ParamW: LongInt; ParamL: LongInt): LongInt stdcall; begin if MessageID = UniqueAppMsg then begin SendMessage(Application.Handle, WM_SYSCOMMAND, SC_RESTORE, 0); SetForegroundWindow(Application.Handle); SendMessage(CurrentFormHandle, WM_SYSCOMMAND, SC_RESTORE, 0); Result := 0; end else Result := CallWindowProc(OriginalWndProc, FormHandle, MessageID, ParamW, ParamL); end; procedure TFormX.FormCreate(Sender: TObject); begin CurrentFormHandle:=FormX.Handle; OriginalWndProc := Pointer(SetWindowLong(CurrentFormHandle, GWL_WNDPROC, LongInt(@FormX_HookedWndProc))); end; procedure TFormX.FormDestroy(Sender: TObject); begin SetWindowLong(CurrentFormHandle, GWL_WNDPROC, LongInt(OriginalWndProc)); end;
Delphi-Quellcode:
Uses Windows, ...;
var Mutex: THandle; begin Mutex := CreateMutex(nil, True, UniqueAppTitle); if (Mutex = 0) or (GetLastError = ERROR_ALREADY_EXISTS) then begin SendMessage(HWND_BROADCAST, UniqueAppMsg, 0, 0); Halt(0); end else try Application.Initialize; {...} finally if Mutex <> 0 then CloseHandle(Mutex); end; |
AW: 1. Instanz nach vorn bringen, wenn 2. geöffnet wird - fensterunabhängig!
@ASM: Uwe Raabe hat dazu oben etwas wichtiges gesagt: evtl. kann sich ein Programm gar nicht selber in den Vordergrund holen (ich mache das auch nicht so, dachte nur, dass das noch praktischer wäre, deswegen schrieb ich das als Vorschlag).
Aus eigener Erfahrung möchte ich noch ein paar wichtige Sachen ergänzen: Fast User Switching und Terminal Services / Remote-Desktop-Zugriff ermöglichen es, dass theoretisch verschiedene Benutzer (oder verschiedene Instanzen desselben Benutzers ein Programm auf demselben Rechner nutzen wollen. Wäre blöd, wenn Benutzer A ein Dokument öffnen will und es bei Benutzer B aufgeht, weil der die erste Instanz offen hat. Deswegen reicht ein UniqueAppTitle wie von ASM beschrieben auf keinen Fall aus! Session-ID und Username gehören da mit rein. Am besten noch den Namen des Desktops. Zusätzlich könnte der Prefix Local\ helfen, um das Mutex definitiv innerhalb der Session zu erzeugen (kommt halt drauf an, wofür man es will). Steht alles in der Hilfe zu ![]() Gleiches gilt natürlich auch für den Namen der Pipe - sicherstellen, dass da keine Übergriffe stattfinden können! Negativbeispiel: Adobe Reader (über mehrere Versionen, nicht sicher ob noch in aktueller). Admin A startet den Adobe Reader, um ein PDF zu lesen. DAU D, ein eingeschränkter Benutzeraccount, surft im Netz und öffnet im Browser ein PDF. Das Adobe Reader-Plugin ist so "intelligent", sich an den von A gestarteten Reader anzuflanschen. Jetzt hat D über die Öffnen-Funktion vollen Administrator-Zugriff. Und wenn das Programm u.U. auch elevated sein kann, muss man planen, wie man damit umgeht, weil der BROADCAST u.U. nicht in beide Richtungen funktioniert (Programm, die elevated sind, sollen ja eben nicht per Window Messages von nicht elevateten Programmen "gesteuert" werden können). |
Alle Zeitangaben in WEZ +1. Es ist jetzt 14:48 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