1 | unit OneInst; |
---|
2 | |
---|
3 | interface |
---|
4 | |
---|
5 | uses |
---|
6 | Windows, Messages; |
---|
7 | |
---|
8 | var |
---|
9 | { Mit dieser MessageId meldet sich dann eine zweite Instanz } |
---|
10 | SecondInstMsgId: UINT = 0; |
---|
11 | |
---|
12 | function ParamBlobToStr(lpData: Pointer): string; |
---|
13 | function ParamStrToBlob(out cbData: DWORD): Pointer; |
---|
14 | |
---|
15 | implementation |
---|
16 | |
---|
17 | const |
---|
18 | { Maximale Zeit, die auf die Antwort der ersten Instanz gewartet wird (ms) } |
---|
19 | TimeoutWaitForReply = 5000; |
---|
20 | |
---|
21 | var |
---|
22 | { Der Text den diese Variable hat sollte bei jedem neuen Programm geändert } |
---|
23 | { werden und möglichst eindeutig (und nicht zu kurz) sein. } |
---|
24 | UniqueName: array [0..MAX_PATH] of Char = 'EindeutigerNameFuerDasProgramm'#0; |
---|
25 | MutexHandle: THandle = 0; |
---|
26 | |
---|
27 | { kleine Hilfsfunktion die uns die Kommandozeilenparameter entpackt } |
---|
28 | function ParamBlobToStr(lpData: Pointer): string; |
---|
29 | var |
---|
30 | pStr: PChar; |
---|
31 | begin |
---|
32 | Result := ''; |
---|
33 | pStr := lpData; |
---|
34 | while pStr[0] <> #0 do |
---|
35 | begin |
---|
36 | Result := Result + string(pStr) + #13#10; |
---|
37 | pStr := @pStr[lstrlen(pStr) + 1]; |
---|
38 | end; |
---|
39 | end; |
---|
40 | |
---|
41 | { kleine Hilfsfunktion die uns die Kommandozeilenparameter einpackt } |
---|
42 | function ParamStrToBlob(out cbData: DWORD): Pointer; |
---|
43 | var |
---|
44 | Loop: Integer; |
---|
45 | pStr: PChar; |
---|
46 | begin |
---|
47 | cbData := Length(ParamStr(1)) + 3; { gleich inklusive #0#0 } |
---|
48 | for Loop := 2 to ParamCount do |
---|
49 | cbData := cbData + DWORD(Length(ParamStr(Loop)) + 1); |
---|
50 | Result := GetMemory(cbData); |
---|
51 | ZeroMemory(Result, cbData); |
---|
52 | pStr := Result; |
---|
53 | for Loop := 1 to ParamCount do |
---|
54 | begin |
---|
55 | lstrcpy(pStr, PChar(ParamStr(Loop))); |
---|
56 | pStr := @pStr[lstrlen(pStr) + 1]; |
---|
57 | end; |
---|
58 | end; |
---|
59 | |
---|
60 | procedure HandleSecondInstance; |
---|
61 | var |
---|
62 | Run: DWORD; |
---|
63 | Now: DWORD; |
---|
64 | Msg: TMsg; |
---|
65 | Wnd: HWND; |
---|
66 | Dat: TCopyDataStruct; |
---|
67 | begin |
---|
68 | // MessageBox(0, 'läuft schon', nil, MB_ICONINFORMATION); |
---|
69 | {----------------------------------------------------------------------------} |
---|
70 | { Wir versenden eine Nachricht an alle Hauptfenster (HWND_BROADCAST) mit der } |
---|
71 | { eindeutigen Message-Id, die wir zuvor registriert haben. Da nur eine } |
---|
72 | { Instanz unseres Programms läuft sollte auch nur eine Anwendung antworten. } |
---|
73 | { } |
---|
74 | { (Broadcast sollten _NUR_ mit registrierten Nachrichten-Ids erfolgen!) } |
---|
75 | {----------------------------------------------------------------------------} |
---|
76 | |
---|
77 | SendMessage(HWND_BROADCAST, SecondInstMsgId, GetCurrentThreadId, 0); |
---|
78 | |
---|
79 | { Wir warten auf die Antwort der ersten Instanz } |
---|
80 | { Für die, die es nicht wußten - auch Threads haben Message-Queues ;o) } |
---|
81 | Wnd := 0; |
---|
82 | Run := GetTickCount; |
---|
83 | while True do |
---|
84 | begin |
---|
85 | if PeekMessage(Msg, 0, SecondInstMsgId, SecondInstMsgId, PM_NOREMOVE) then |
---|
86 | begin |
---|
87 | GetMessage(Msg, 0, SecondInstMsgId, SecondInstMsgId); |
---|
88 | if Msg.message = SecondInstMsgId then |
---|
89 | begin |
---|
90 | Wnd := Msg.wParam; |
---|
91 | Break; |
---|
92 | end; |
---|
93 | end; |
---|
94 | Now := GetTickCount; |
---|
95 | if Now < Run then |
---|
96 | Run := Now; { Überlaufschutz - passiert nur alle 48 Tage, aber naja } |
---|
97 | if Now - Run > TimeoutWaitForReply then |
---|
98 | Break; |
---|
99 | end; |
---|
100 | |
---|
101 | if (Wnd <> 0) and IsWindow(Wnd) then |
---|
102 | begin |
---|
103 | { Als Antwort haben wir das Handle bekommen, an das wir die Daten senden. } |
---|
104 | |
---|
105 | {-------------------------------------------------------------------------} |
---|
106 | { Wir verschicken nun eine Message mit WM_COPYDATA. Dabei handelt es sich } |
---|
107 | { eine der wenigen Nachrichten, bei der Windows Daten aus einem Prozeß in } |
---|
108 | { einen anderen einblendet. Nach Behandlung der Nachricht werden diese } |
---|
109 | { wieder aus dem Adreßraum des Empfängers ausgeblendet, sodaß derjenige, } |
---|
110 | { der die Nachricht erhält und die Daten weiter verwenden will, sich die } |
---|
111 | { Daten kopieren muß. } |
---|
112 | {-------------------------------------------------------------------------} |
---|
113 | |
---|
114 | { Zur Absicherung schreiben wir nochmal die eindeutige Nachrichten-Id in } |
---|
115 | { das Tag-Feld, das uns die Nachricht bietet. } |
---|
116 | { Ansonsten schreiben wir die Kommandozeilenparameter als } |
---|
117 | { durch #0 getrennte und durch #0#0 beendete Liste in den Datenblock } |
---|
118 | Dat.dwData := SecondInstMsgId; |
---|
119 | Dat.lpData := ParamStrToBlob(Dat.cbData); |
---|
120 | SendMessage(Wnd, WM_COPYDATA, 0, LPARAM(@Dat)); |
---|
121 | FreeMemory(Dat.lpData); |
---|
122 | end; |
---|
123 | end; |
---|
124 | |
---|
125 | procedure CheckForSecondInstance; |
---|
126 | var |
---|
127 | Loop: Integer; |
---|
128 | begin |
---|
129 | {-----------------------------------------------------------------------------} |
---|
130 | { Wir versuchen ein systemweit eindeutiges benanntes Kernelobjekt, ein Mutex } |
---|
131 | { anzulegen und prüfen, ob dieses Objekt schon existiert. } |
---|
132 | { Der Name zum Anlegen eines Mutex darf nicht länger als MAX_PATH (260) sein } |
---|
133 | { und darf alle Zeichen außer '\' enthalten. } |
---|
134 | { } |
---|
135 | { (Einzige Ausnahme sind die beiden Schlüsselwörter 'Global\' und 'Local\' } |
---|
136 | { mit denen ein Mutexname auf einem Terminalserver beginnen darf, damit der } |
---|
137 | { Mutex nicht nur oder expliziet für eine Session dient. Das wird aber nur } |
---|
138 | { sehr selten benötigt, wenn, dann meist bei Diensten auf Terminalservern.) } |
---|
139 | { } |
---|
140 | { Windows kennt nur einen Namensraum für Events, Semaphoren und andere } |
---|
141 | { benannte Kernelobjekte. Das heißt es kommt zum Beispiel zu einem Fehler bei } |
---|
142 | { dem Versuch mit dem Namen eines existierenden benannten Events einen Mutex } |
---|
143 | { zu erzeugen. (da gewinnt das Wort 'Sonderfall' fast eine neue Bedeutung ;o) } |
---|
144 | {-----------------------------------------------------------------------------} |
---|
145 | |
---|
146 | for Loop := lstrlen(UniqueName) to MAX_PATH - 1 do |
---|
147 | begin |
---|
148 | MutexHandle := CreateMutex(nil, False, UniqueName); |
---|
149 | if (MutexHandle = 0) and (GetLastError = INVALID_HANDLE_VALUE) then |
---|
150 | { Es scheint schon ein Kernelobjekt mit diesem Namen zu geben. } |
---|
151 | { Wir versuchen das Problem durch Anhängen von '_' zu lösen. } |
---|
152 | lstrcat(UniqueName, '_') |
---|
153 | else |
---|
154 | { es gibt zumindest keinen Konflikt durch den geteilten Namensraum } |
---|
155 | Break; |
---|
156 | end; |
---|
157 | |
---|
158 | case GetLastError of |
---|
159 | 0: |
---|
160 | begin |
---|
161 | { Wir haben den Mutex angelegt; sind also die erste Instanz. } |
---|
162 | end; |
---|
163 | ERROR_ALREADY_EXISTS: |
---|
164 | begin |
---|
165 | { Es gibt also schon eine Instanz - beginnen wir mit dem Prozedere. } |
---|
166 | try |
---|
167 | HandleSecondInstance; |
---|
168 | finally |
---|
169 | { was auch immer passiert, alles endet hier ;o) } |
---|
170 | { Die 183 ist nicht ganz zufällig, kleiner Spaß } |
---|
171 | Halt(183); |
---|
172 | end; |
---|
173 | end; |
---|
174 | else |
---|
175 | { Keine Ahnung warum wir hier landen sollten, } |
---|
176 | { außer Microsoft hat wiedermal die Regeln geändert. } |
---|
177 | { Wie auch immer - wir lassen das Programm starten. } |
---|
178 | end; |
---|
179 | end; |
---|
180 | |
---|
181 | initialization |
---|
182 | |
---|
183 | { Wir holen uns gleich zu Beginn eine eindeutige Nachrichten-Id die wir im } |
---|
184 | { Programm zur eindeutigen Kommunikation zwischen den Instanzen brauchen. } |
---|
185 | { Jedes Programm bekommt, wenn es den gleichen Text benutzt, die gleiche } |
---|
186 | { Id zurück (zumindest innerhalb einer Windows Sitzung) } |
---|
187 | SecondInstMsgId := RegisterWindowMessage(UniqueName); |
---|
188 | |
---|
189 | { Auf eine schon laufende Instanz überprüfen. } |
---|
190 | CheckForSecondInstance; |
---|
191 | |
---|
192 | finalization |
---|
193 | |
---|
194 | { Den Mutex wieder freigeben, was eigentlich nicht nötig wäre, da Windows NT } |
---|
195 | { Alle angeforderten Kernel-Objekte zum Prozeßende freigibt. Aber sicher ist } |
---|
196 | { sicher (Windows 95/98 kann nur 65535 Objekte verwalten - jaja 32-Bit ;o). } |
---|
197 | if MutexHandle <> 0 then |
---|
198 | begin |
---|
199 | ReleaseMutex(MutexHandle); |
---|
200 | MutexHandle := 0; { hilft beim Debuggen } |
---|
201 | end; |
---|
202 | |
---|
203 | end. |
---|