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 1. Instanz nach vorn bringen, wenn 2. geöffnet wird - fensterunabhängig! (https://www.delphipraxis.net/173567-1-instanz-nach-vorn-bringen-wenn-2-geoeffnet-wird-fensterunabhaengig.html)

RSE 4. Mär 2013 15:00

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.

mjustin 4. Mär 2013 15:04

AW: 1. Instanz nach vorn bringen, wenn 2. geöffnet wird - fensterunabhängig!
 
Zitat:

Zitat von RSE (Beitrag 1205887)
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.

Die Jedi Code Library enthält eine hilfreiche Klasse (TJclAppInstances), die diese Funktion implementiert. Als Basis für eigenen Code ist diese Klasse eventuell einen Blick wert (Open Source).

CCRDude 4. Mär 2013 15:45

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.

RSE 4. Mär 2013 16:31

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!

Uwe Raabe 4. Mär 2013 16:40

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.

RSE 5. Mär 2013 11:27

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:
  • Nur die Erste Instanz startet normal - nur dann ist CreateNamedPipe mit FILE_FLAG_FIRST_PIPE_INSTANCE erfolgreich - Jede weitere Instanz erkennt, dass bereits eine andere Instanz läuft und kann darauf reagieren
  • Eine zweite Instanz kann die erste Instanz folgendermaßen ermitteln: Sie verbindet sich mit CreateFile mit den Parametern GENERIC_READ und OPEN_EXISTING auf die Pipe und erhält das Pipe-Handle. Damit kann sie mittels GetNamedPipeServerProcessId die Prozess-ID der ersten Instanz ermitteln
  • Eine dritte Instanz braucht keine Meldung mehr ausgeben, dass bereits eine Instanz läuft, da das bereits die zweite Instanz tut. Dieser Fall könnte z.B. eintreten, wenn bereits eine Instanz hinter anderen Fenstern verborgen läuft und das Programm ungeschickt gleich mehrfach geöffnet wird. Dieser Fall wird als Nebeneffekt erkannt, wenn das Verbinden auf die bestehende Pipe nicht gelingt (weil Instanz 2 bereits verbunden ist). Das Programm kann in diesem Fall ohne weitere Handlungen beendet werden.
Offen bleibt also jetzt nur noch das In-den-Vordergrund-bringen der ersten Instanz durch die zweite Instanz. Dazu benötige ich offenbar ein Fenster-Handle der ersten Instanz, welches ich dann an SetForegroundWindow übergeben kann. Gefunden habe ich bei einer ersten Suche die GetGUIThreadInfo Funktion (benötigt die Thread-ID, welche man mittels der Prozess-ID ermitteln können sollte), welche mir eine GUITHREADINFO structure mit dem Handle des im Thread aktiven Windows liefert. Ist das ein guter Ansatz, oder gibt es bessere/bevorzugenswertere?

Ich sehe folgende Probleme incl. Lösungen bei meinem Ansatz:
  • Der Zielprozess hat wahrscheinlich mehrere Threads. In diesem Fall würde ich alle Threads durchsuchen, bis ich einen finde, der ein aktives Fenster besitzt und dieses in den Vordergrund bringen.
  • Zwischen der Ermittlung des aktiven Fensters und dem Aufruf/Umsetzung von SetForegroundWindow könnte das Fenster wieder verschwunden sein (Aufruf von Hide), falls es sich dummerweise gerade um mein "Bitte warten Sie, bis die lange Operation beendet wurde"-Fenster handelt. Ich nehme an, dass SetForegroundWindow in diesem Fall einen entsprechenden Rückgabewert liefert, damit ich den Vorgang auf einem neuen aktiven Fenster wiederholen kann.

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!

RSE 5. Mär 2013 14:39

AW: 1. Instanz nach vorn bringen, wenn 2. geöffnet wird - fensterunabhängig!
 
Fast geschafft... Fast! Hier ist mein Code:
Delphi-Quellcode:
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.
Ich habe mein Bestes gegeben, Google und MSDN gelesen, aber an den 3 Punkten oben, die mit ##### gekennzeichnet sind, komme ich nicht weiter.
  • Punkt1: In keinem der untersuchten Threads gibt es ein aktives Fenster. Der Thread, in dem die 1. Instanz zum Testzeitpunkt definitiv ein Fenster anzeigt, ist dabei der zuerst durchsuchte Thread - hier müsste also ein Handle eingetragen sein, wenn ich MSDN richtig verstanden habe. Zitat: "A handle to the active window within the thread." - offenbar aber doch nur für den fall, dass dieses Fenster gerade das eine im System aktive Fenster ist, das den Eingabefokus besitzt. Was gibt es für Alternativen, um an ein sichtbares oder minimiertes Fenster des Prozesses zu kommen?
  • Punkt2: Nach meinem Wissen gibt es 2 Threads in meinem Programm: Den Delphi-Hauptthread und einen von mir erstellten LogThread. Weitere Threads könnten z.B. im Hintergrund von irgendwelchen (DB)Komponenten erstellt worden sein. Warum aber plötzlich die Parameter nicht mehr stimmen sollen, ist mir schleierhaft.
  • Punkt3: Ich wäre davon ausgegangen, dass die Pipe wieder frei ist, wenn die Verbindung durch das Freigeben des Handles beendet wurde. Was muss ich tun, um die Pipe wieder freizumachen?

RSE 5. Mär 2013 17:02

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:
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.
Dadurch erübrigen sich 2 der 3 Punkte, aber einer bleibt bestehen:
Zitat:

Zitat von RSE (Beitrag 1205979)
  • Punkt3: Ich wäre davon ausgegangen, dass die Pipe wieder frei ist, wenn die Verbindung durch das Freigeben des Handles beendet wurde. Was muss ich tun, um die Pipe wieder freizumachen?

An diesem Punkt bitte ich um eure Hilfe, es fällt mir dazu nichts weiter ein.

ASM 5. Mär 2013 17:49

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:
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.
// in jeder einzelnen Form-Unit (hier exemplarisch "FormX"):
Delphi-Quellcode:
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;
// im Projektfile:
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;

CCRDude 6. Mär 2013 07:04

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 MSDN-Library durchsuchenCreateMutex :)

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).

RSE 6. Mär 2013 09:31

AW: 1. Instanz nach vorn bringen, wenn 2. geöffnet wird - fensterunabhängig!
 
Also auf zum nächsten Versuch:

Ich werde die Pipe wieder gegen einen Mutex ersetzen, um herauszubekommen, ob die aktuelle Instanz die einzige ist. Die Pipe ist, so wie ich sie momentan im Code habe, sowieso ziemlich zweckentfremdet. Wenn ich nun eine weitere Instanz finde (= der Mutex bereits reserviert/in Benutzung ist), dann sende ich mit BroadcastSystemMessage eine Message an alle Applications, die ein Antwort-Handle enthält. Die 1. Instanz reagiert darauf und schickt eine Message an das Antworthandle, welches die Prozess-ID enthält. Der Rest läuft dann wie bisher. Vielleicht finde ich auch noch Optimierungspotenzial - habe da eine Option BSF_ALLOWSFW gesehen.

Ich poste auf alle Fälle den finalen Code hier.

@ASM: Danke für die Idee mit den Broadcast-Messages!

@CCRDude: Das sind gute Hinweise, die du da bringst. In meinem Fall geht es allerdings nicht um das Öffnen von Dateien, sondern um ein reines Arbeitswerkzeug, das mit Daten aus einer Datenbank versorgt wird. Wenn dieses Programm mit dem selben Rechnernamen mehrfach ausgeführt wird, dann kommt die Organisation in diversen Tabellen durcheinander. Außerdem wird das Programm nur firmenintern eingesetzt, und da kann man schon einige Annahmen über den Einsatz machen ;-) Zumindest soweit, dass man keine Klimmzüge macht, um Fälle abzudecken, die sowieso nicht vorkommen werden. Der Admin wird sich z.B. nie im Remotezugriff auf einem Rechner selbst (zusätzlich) einloggen und dort dieses Programm starten. Bei Programmen, die die Firma verlassen würden, müsste man deine Hinweise natürlich beachten, keine Frage!

Uwe Raabe 6. Mär 2013 09:49

AW: 1. Instanz nach vorn bringen, wenn 2. geöffnet wird - fensterunabhängig!
 
Sidenote: Du kannst dir ja mal die madCollection und dort die Processes ansehen. Macht die Sache leichter.

RSE 6. Mär 2013 10:47

AW: 1. Instanz nach vorn bringen, wenn 2. geöffnet wird - fensterunabhängig!
 
@Uwe Raabe: Könntest du mir noch einen Hinweis geben, was genau ich mir dort ansehen sollte? Bei einem groben Überfliegen ist mir noch nichts speziell aufgefallen.

RSE 6. Mär 2013 12:20

AW: 1. Instanz nach vorn bringen, wenn 2. geöffnet wird - fensterunabhängig!
 
Es klappt! Endlich! Folgender Code hat gewonnen:
Delphi-Quellcode:
const
  ProgUID = 'geheim :-p';
  BSF_ALLOWSFW = $00000080; // fehlt in Unit Windows neben z.B. BSF_QUERY

var
  s: string;
  HMutex: THandle = 0;
  PBroadcastRecipients: PDWORD;
  WM_CCCSingleInstanceBroadcast: UINT = 0;

function TIrgendeineKlasse.SingleInstanceBroadcastReceiver(var m: TMessage): Boolean; // muss zwecks Akzeptanz durch Application.HookMainWindow als Methode einer Klasse implementiert sein
begin
  Result := False;
  if m.Msg = WM_CCCSingleInstanceBroadcast then
    Application.BringToFront;
end;

initialization

  WM_CCCSingleInstanceBroadcast := RegisterWindowMessage(ProgUID); // eindeutige Message-ID holen
  HMutex := CreateMutex(nil, True, ProgUID);
  if (HMutex = 0) or (GetLastError = ERROR_ALREADY_EXISTS) or
     (GetLastError = ERROR_ACCESS_DENIED) then
  begin
    // dieser Code läuft nur in der 2. Instanz des Programms
    New(PBroadcastRecipients);
    try
      PBroadcastRecipients^ := BSM_APPLICATIONS;
      BroadcastSystemMessage(BSF_ALLOWSFW or BSF_IGNORECURRENTTASK or
        BSF_POSTMESSAGE, PBroadcastRecipients, WM_CCCSingleInstanceBroadcast, 0, 0); // an alle: hier ist noch einer
      SwitchToThread; // Rest der Zeitscheibe verwerfen, damit die Instanz sich nach vorn bringen kann (falls das durch Race-Condition nicht klappt: Pech gehabt)
    finally
      Dispose(PBroadcastRecipients);
    end;
    MessageBox(0, 'Das Programm läuft bereits', '',
      MB_SYSTEMMODAL or MB_SETFOREGROUND or MB_TOPMOST); // über die erste Instanz legen
    Halt;
  end;
  Application.HookMainWindow(TIrgendeineKlasse.SingleInstanceBroadcastReceiver); // Klassenmethode sollte gehen - dieser Teil ist bei mir ganz anders umgesetzt

finalization

  if HMutex > 0 then
    CloseHandle(HMutex);

end.

Uwe Raabe 6. Mär 2013 12:31

AW: 1. Instanz nach vorn bringen, wenn 2. geöffnet wird - fensterunabhängig!
 
Zitat:

Zitat von RSE (Beitrag 1206080)
@Uwe Raabe: Könntest du mir noch einen Hinweis geben, was genau ich mir dort ansehen sollte? Bei einem groben Überfliegen ist mir noch nichts speziell aufgefallen.

Nichts Spezielles - es macht lediglich das Arbeiten mit den Kernel-Objekten leichter.

Uwe Raabe 6. Mär 2013 12:41

AW: 1. Instanz nach vorn bringen, wenn 2. geöffnet wird - fensterunabhängig!
 
Zitat:

Zitat von RSE (Beitrag 1206109)
Es klappt!

Hier nicht! Die Instanz kommt nicht in den Vordergrund - es blinkt lediglich das Taskbar-Icon.

Dieser Code funktioniert hier allerdings ganz gut:

Delphi-Quellcode:
uses
  madKernel;

procedure CheckFirstInstance;
var
  prcs: IProcesses;
begin
  prcs := Processes(CurrentProcess.ExeFile);
  if prcs.ItemCount > 1 then begin
    prcs[0].Windows_[0].BringToForeground();
    Halt;
  end;
end;

initialization
  CheckFirstInstance;
end.
Ich würde allerdings trotzdem den Mutex als Sentinel verwenden, und die Indizierung auf 0 in den Listen muss auch nicht immer richtig sein.

Der Knackpunkt ist hier, daß die zweite Instanz hier die erste in den Vordergrund bringt und nicht die erste sich selbst. Das Privileg, das ForegroundWindow zu ändern hat nämlich nicht jeder. Der gerade im Vordergrund liegende Prozess allerdings schon.

RSE 6. Mär 2013 14:20

AW: 1. Instanz nach vorn bringen, wenn 2. geöffnet wird - fensterunabhängig!
 
@Uwe Raabe: Ich war davon ausgegangen, dass das während der Verarbeitung der Message mit BSF_ALLOWSFW erlaubt sei, denn genau so ist diese Option definiert. Damit ist sie also komplett sinnfrei. Darf ich fragen, auf welcher Windows-Version du getestet hast? Bei mir läuft es unter Win7 32bit und 64bit gleichermaßen wie gewünscht.

Ich werde also nochmals umbauen (never ending story...) und der zweiten Instanz antworten, welches Fenster sie in den Vordergrund holen soll.

RSE 6. Mär 2013 15:11

AW: 1. Instanz nach vorn bringen, wenn 2. geöffnet wird - fensterunabhängig!
 
Letzter Stand (2. Instanz bringt die erste nach vorn):
Delphi-Quellcode:
const
  ProgUID = 'geheim :-p';
  WM_AnswerToSecondInstance = WM_USER + 1;

var
  HMutex: THandle = 0;
  PBroadcastRecipients: PDWORD;
  WM_CCCSingleInstanceBroadcast: UINT = 0;

function TIrgendeineKlasse.SingleInstanceBroadcastReceiver(var m: TMessage): Boolean; // muss zwecks Akzeptanz durch Application.HookMainWindow als Methode einer Klasse implementiert sein
var
  LHandle: HWnd;
  TopWindow: HWnd;
begin
  Result := False;
  if m.Msg = WM_CCCSingleInstanceBroadcast then
  begin
    // Logic copied from Application.BringToFront
    if Application.MainFormOnTaskBar and (Application.MainForm <> nil) then
      LHandle := Application.MainForm.Handle
    else
      LHandle := Application.Handle;
    if LHandle <> 0 then
    begin
      TopWindow := GetLastActivePopup(LHandle);
      if (TopWindow <> 0) and (TopWindow <> Application.Handle) and
        IsWindowVisible(TopWindow) and IsWindowEnabled(TopWindow) then
        PostMessage(m.WParam, m.LParam, TopWindow, 0);
    end;
  end;
end;

function TIrgendeineKlasse.SecondInstanceReceiver(var m: TMessage): Boolean; // muss zwecks Akzeptanz durch Application.HookMainWindow als Methode einer Klasse implementiert sein
begin
  Result := False;
  case m.Msg of
    WM_AnswerToSecondInstance:
      SetForegroundWindow(m.WParam);
  end;
end;

initialization

  WM_CCCSingleInstanceBroadcast := RegisterWindowMessage(ProgUID); // eindeutige Message-ID holen
  HMutex := CreateMutex(nil, True, ProgUID);
  if (HMutex = 0) or (GetLastError = ERROR_ALREADY_EXISTS) or
     (GetLastError = ERROR_ACCESS_DENIED) then
  begin
    // dieser Code läuft nur in der 2. Instanz des Programms
    Application.HookMainWindow(TIrgendeineKlasse.SecondInstanceReceiver); // Klassenmethode sollte gehen - dieser Teil ist bei mir ganz anders umgesetzt
    New(PBroadcastRecipients);
    try
      PBroadcastRecipients^ := BSM_APPLICATIONS;
      BroadcastSystemMessage(BSF_IGNORECURRENTTASK, PBroadcastRecipients,
        WM_CCCSingleInstanceBroadcast, MessageDistributor.Handle,
        WM_AnswerToSecondInstance); // an alle: hier ist noch einer
    finally
      Dispose(PBroadcastRecipients);
    end;
    SwitchToThread; // Rest der Zeitscheibe verwerfen, damit die 1. Instanz antworten kann (falls das durch Race-Condition nicht klappt: Pech gehabt)
    Application.ProcessMessages; // Antwort verarbeiten
    MessageBox(0, 'Das Programm läuft bereits', '',
      MB_SYSTEMMODAL or MB_SETFOREGROUND or MB_TOPMOST); // über die erste Instanz legen
    Halt;
  end;
  Application.HookMainWindow(TIrgendeineKlasse.SingleInstanceBroadcastReceiver); // Klassenmethode sollte gehen - dieser Teil ist bei mir ganz anders umgesetzt

finalization

  if HMutex > 0 then
    CloseHandle(HMutex);

end.
Mann, ist das viel Code geworden, aber letztlich steckt ja gar nicht so viel dahinter...

Uwe Raabe 6. Mär 2013 15:55

AW: 1. Instanz nach vorn bringen, wenn 2. geöffnet wird - fensterunabhängig!
 
Zitat:

Zitat von RSE (Beitrag 1206162)
@Uwe Raabe: Ich war davon ausgegangen, dass das während der Verarbeitung der Message mit BSF_ALLOWSFW erlaubt sei, denn genau so ist diese Option definiert. Damit ist sie also komplett sinnfrei. Darf ich fragen, auf welcher Windows-Version du getestet hast? Bei mir läuft es unter Win7 32bit und 64bit gleichermaßen wie gewünscht.

Win7 x64 - ganz einfach getestet:

- neue VCL-Anwendung
- deinen Code in die Unit aufgenommen
- noch 'ne Dummy-Klasse mit der entsprechenden Klassenmethode dazu
- compiliert
- aus dem Explorer 1. Instanz gestartet
- Delphi aktiviert (1. Instanz verliert den Focus und wird verdeckt)
- aus dem Explorer die 2. Instanz gestartet

exp: 1. Instanz kommt nach vorn
act: 1. Instanz bleibt verdeckt, aber Taskbar-Icon blinkt


Alle Zeitangaben in WEZ +1. Es ist jetzt 08:34 Uhr.

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