{ Downloaded from: http://www.michael-puff.de/Developer/Delphi/Importe/Nico/oneinst.zip } unit OneInst; interface uses Windows, Messages; var { Mit dieser MessageId meldet sich dann eine zweite Instanz } SecondInstMsgId: UINT = 0; function ParamBlobToStr(lpData: Pointer): string; function ParamStrToBlob(out cbData: DWORD): Pointer; implementation const { Maximale Zeit, die auf die Antwort der ersten Instanz gewartet wird (ms) } TimeoutWaitForReply = 5000; var { Der Text den diese Variable hat sollte bei jedem neuen Programm geändert } { werden und möglichst eindeutig (und nicht zu kurz) sein. } UniqueName: array [0..MAX_PATH] of Char = 'Oni Un/Packer'#0; MutexHandle: THandle = 0; { kleine Hilfsfunktion die uns die Kommandozeilenparameter entpackt } function ParamBlobToStr(lpData: Pointer): string; var pStr: PChar; begin Result := ''; pStr := lpData; while pStr[0] <> #0 do begin Result := Result + string(pStr) + #13#10; pStr := @pStr[lstrlen(pStr) + 1]; end; end; { kleine Hilfsfunktion die uns die Kommandozeilenparameter einpackt } function ParamStrToBlob(out cbData: DWORD): Pointer; var Loop: Integer; pStr: PChar; begin cbData := Length(ParamStr(1)) + 3; { gleich inklusive #0#0 } for Loop := 2 to ParamCount do cbData := cbData + DWORD(Length(ParamStr(Loop)) + 1); Result := GetMemory(cbData); ZeroMemory(Result, cbData); pStr := Result; for Loop := 1 to ParamCount do begin lstrcpy(pStr, PChar(ParamStr(Loop))); pStr := @pStr[lstrlen(pStr) + 1]; end; end; procedure HandleSecondInstance; var Run: DWORD; Now: DWORD; Msg: TMsg; Wnd: HWND; Dat: TCopyDataStruct; begin // MessageBox(0, 'läuft schon', nil, MB_ICONINFORMATION); {----------------------------------------------------------------------------} { Wir versenden eine Nachricht an alle Hauptfenster (HWND_BROADCAST) mit der } { eindeutigen Message-Id, die wir zuvor registriert haben. Da nur eine } { Instanz unseres Programms läuft sollte auch nur eine Anwendung antworten. } { } { (Broadcast sollten _NUR_ mit registrierten Nachrichten-Ids erfolgen!) } {----------------------------------------------------------------------------} SendMessage(HWND_BROADCAST, SecondInstMsgId, GetCurrentThreadId, 0); { Wir warten auf die Antwort der ersten Instanz } { Für die, die es nicht wußten - auch Threads haben Message-Queues ;o) } Wnd := 0; Run := GetTickCount; while True do begin if PeekMessage(Msg, 0, SecondInstMsgId, SecondInstMsgId, PM_NOREMOVE) then begin GetMessage(Msg, 0, SecondInstMsgId, SecondInstMsgId); if Msg.message = SecondInstMsgId then begin Wnd := Msg.wParam; Break; end; end; Now := GetTickCount; if Now < Run then Run := Now; { Überlaufschutz - passiert nur alle 48 Tage, aber naja } if Now - Run > TimeoutWaitForReply then Break; end; if (Wnd <> 0) and IsWindow(Wnd) then begin { Als Antwort haben wir das Handle bekommen, an das wir die Daten senden. } {-------------------------------------------------------------------------} { Wir verschicken nun eine Message mit WM_COPYDATA. Dabei handelt es sich } { eine der wenigen Nachrichten, bei der Windows Daten aus einem Prozeß in } { einen anderen einblendet. Nach Behandlung der Nachricht werden diese } { wieder aus dem Adreßraum des Empfängers ausgeblendet, sodaß derjenige, } { der die Nachricht erhält und die Daten weiter verwenden will, sich die } { Daten kopieren muß. } {-------------------------------------------------------------------------} { Zur Absicherung schreiben wir nochmal die eindeutige Nachrichten-Id in } { das Tag-Feld, das uns die Nachricht bietet. } { Ansonsten schreiben wir die Kommandozeilenparameter als } { durch #0 getrennte und durch #0#0 beendete Liste in den Datenblock } Dat.dwData := SecondInstMsgId; Dat.lpData := ParamStrToBlob(Dat.cbData); SendMessage(Wnd, WM_COPYDATA, 0, LPARAM(@Dat)); FreeMemory(Dat.lpData); end; end; procedure CheckForSecondInstance; var Loop: Integer; begin {-----------------------------------------------------------------------------} { Wir versuchen ein systemweit eindeutiges benanntes Kernelobjekt, ein Mutex } { anzulegen und prüfen, ob dieses Objekt schon existiert. } { Der Name zum Anlegen eines Mutex darf nicht länger als MAX_PATH (260) sein } { und darf alle Zeichen außer '\' enthalten. } { } { (Einzige Ausnahme sind die beiden Schlüsselwörter 'Global\' und 'Local\' } { mit denen ein Mutexname auf einem Terminalserver beginnen darf, damit der } { Mutex nicht nur oder expliziet für eine Session dient. Das wird aber nur } { sehr selten benötigt, wenn, dann meist bei Diensten auf Terminalservern.) } { } { Windows kennt nur einen Namensraum für Events, Semaphoren und andere } { benannte Kernelobjekte. Das heißt es kommt zum Beispiel zu einem Fehler bei } { dem Versuch mit dem Namen eines existierenden benannten Events einen Mutex } { zu erzeugen. (da gewinnt das Wort 'Sonderfall' fast eine neue Bedeutung ;o) } {-----------------------------------------------------------------------------} for Loop := lstrlen(UniqueName) to MAX_PATH - 1 do begin MutexHandle := CreateMutex(nil, False, UniqueName); if (MutexHandle = 0) and (GetLastError = INVALID_HANDLE_VALUE) then { Es scheint schon ein Kernelobjekt mit diesem Namen zu geben. } { Wir versuchen das Problem durch Anhängen von '_' zu lösen. } lstrcat(UniqueName, '_') else { es gibt zumindest keinen Konflikt durch den geteilten Namensraum } Break; end; case GetLastError of 0: begin { Wir haben den Mutex angelegt; sind also die erste Instanz. } end; ERROR_ALREADY_EXISTS: begin { Es gibt also schon eine Instanz - beginnen wir mit dem Prozedere. } try HandleSecondInstance; finally { was auch immer passiert, alles endet hier ;o) } { Die 183 ist nicht ganz zufällig, kleiner Spaß } Halt(183); end; end; else { Keine Ahnung warum wir hier landen sollten, } { außer Microsoft hat wiedermal die Regeln geändert. } { Wie auch immer - wir lassen das Programm starten. } end; end; initialization { Wir holen uns gleich zu Beginn eine eindeutige Nachrichten-Id die wir im } { Programm zur eindeutigen Kommunikation zwischen den Instanzen brauchen. } { Jedes Programm bekommt, wenn es den gleichen Text benutzt, die gleiche } { Id zurück (zumindest innerhalb einer Windows Sitzung) } SecondInstMsgId := RegisterWindowMessage(UniqueName); { Auf eine schon laufende Instanz überprüfen. } CheckForSecondInstance; finalization { Den Mutex wieder freigeben, was eigentlich nicht nötig wäre, da Windows NT } { Alle angeforderten Kernel-Objekte zum Prozeßende freigibt. Aber sicher ist } { sicher (Windows 95/98 kann nur 65535 Objekte verwalten - jaja 32-Bit ;o). } if MutexHandle <> 0 then begin ReleaseMutex(MutexHandle); MutexHandle := 0; { hilft beim Debuggen } end; end.