|
Auch bei noch so sorgfältiger Programmierung kann es doch immer
wieder mal passieren, dass eine als Komponente geladene ActiveX-EXE
nach dem Beenden einer Client-Anwendung nicht terminiert, sondern
einfach weiterläuft. Dafür mag es eine ganze Reihe an Ursachen
geben - etwa nicht entladene Forms, zirkuläre Referenzen oder
einfach irreguläre Abstürze einer Client-Anwendung.
Soweit die ActiveX-EXE nicht als unsichtbarer Dienst läuft oder
ihre App.Title-Eigenschaft nicht auf einen leeren String gesetzt
wurde (dann taucht sie unter Windows 9x nicht in der
Taskliste auf), könnten Sie solche hängenden
ActiveX-EXE-Komponenten vielleicht über den Taskmanager
"abschießen". Unter Windows NT/2000 mag das
auch noch ohne weitere Auswirkungen auf das Gesamtsystem möglich
sein. Unter Windows 9x dagegen kann das das Gesamtsystem ziemlich
aus dem Tritt bringen, auch wenn es danach zunächst noch ordentlich
weiter zu laufen scheint.
Bei einer im MultiUse-Modus laufenden ActiveX-EXE haben Sie
nämlich noch die Chance, zunächst einen Versuch zu starten, die
Komponente gewissermaßen "zur Selbstaufgabe" zu bewegen.
Sie brauchen nur eine Möglichkeit, ein für diese Aufgabe
ausgelegtes Objekt der Komponente zu instanzieren und es in der noch
laufenden Komponente aufräumen zu lassen, was immer noch
aufzuräumen geht. Da wären etwa noch geladene Forms zu entladen,
oder globale Referenzen freizugeben, die von schon nicht mehr
existierenden Clients angelegt wurden, und dergleichen mehr. Was im
einzelnen zu tun ist, und was noch möglich und erreichbar ist,
hängt natürlich von der inneren Architektur der Komponente ab.
Aber es steht dem nichts im Wege, zum einen diese Architektur auf
eine solche spätere Aufräummöglichkeit hin auszulegen, als auch
zum anderen die dafür benötigte "Killer"-Klasse
vorzusehen.
Als Beispiel habe ich einmal eine kleine, simple
ActiveX-EXE-Komponente angelegt, bei der unschwer zu erkennen ist,
dass sie unweigerlich auch nach dem Terminieren des letzten Clients
munter weiter laufen wird. Jede einzelne Instanz ihres
Application-Objekts lädt nämlich eine Form-Instanz und legt eine
Referenz auf diese in einer globalen Collection ab (aus welchem
Grund auch immer...).
Der Code der Application-Klasse (das geladene Form braucht hier
keinerlei Code zu enthalten) und eines Standard-Moduls sehen
folgendermaßen aus:
' Klasse Application
Private mCounter As Long
Public Property Get Counter() As Long
mCounter = mCounter + 1
Counter = mCounter
End Property
Private Sub Class_Initialize()
Dim nFrmTest As frmTest
Set nFrmTest = New frmTest
RegForm nFrmTest
Load nFrmTest
End Sub
' Standard-Modul modGlobal
Private mForms As Collection
Public Sub RegForm(Form As Form)
If mForms Is Nothing Then
Set mForms = New Collection
End If
mForms.Add Form
End Sub
Natürlich ist dieses Beispiel ein wenig fadenscheinig. Denn eine
ordentliche Programmierung der Klasse enthielte in deren
Terminate-Ereignis sicherlich das Gegenstück zur Ablage der
Form-Referenz in der globalen Collection. Nur, wie gesagt - Pannen,
die im Kern auf einen solchen Fehler zurückzuführen sind, können
bei komplexeren Komponenten durchaus vorkommen. Sie sollten dann
allerdings vor der Freigabe der endgültig als fertiggestellt
anzusehenden Version längst eliminiert sein...
Der Test-Client für diese Komponente ist ebenfalls simpel. Er
enthält ein Form mit einem Timer, in dessen Timer-Ereignis der sich
bei jedem Aufruf erhöhende Wert der Eigenschaft Counter abgefragt
wird. Der Client wird ganz normal beendet - und mit seinem Ende
sollte auch die Instanz des Application-Objekts terminieren. Ebenso
sollte auch die ganze Komponente enden, wenn sich der letzte Client
verabschiedet hat.
Private mApp As Application
Private Sub Form_Load()
Set mApp = New Application
lbl.Caption = mApp.Counter
tmr.Enabled = True
End Sub
Private Sub tmr_Timer()
lbl.Caption = mApp.Counter
End Sub
Die konkrete Aufgabe für eine "Killer"-Klasse der
ActiveX-EXE-Komponente bestünde nun darin, die in der globalen
Forms-Collection enthaltenen Forms zu entladen und die Collection
aufzulösen. Denn es könnte ja sein, dass in den Unload-Ereignissen
der Forms noch einige abschließende Arbeiten zu erledigen wären,
etwa irgendeinen Status in die Registrierung zu schreiben, oder
einen noch geöffneten Datensatz samt noch geöffneter Datenbank
ordentlich zu schließen, oder was auch immer.
Allerdings reicht auch das nicht immer aus, um die Komponente nun
endgültig zur Selbstaufgabe zu bringen. In diesem seltenen Fall ist
ausnahmsweise (!) einmal die Verwendung der
End-Anweisung angebracht und unumgänglich. Ich kann es gar nicht
oft genug betonen, dass die End-Anweisung ansonsten nichts, aber
auch gar nichts in einer Anwendung zu suchen hat. Bei einer
ordentlichen Programmierung und Programm-Architektur ist sie
vollkommen überflüssig. Sie sollte lediglich ganz kalkuliert in
Ausnahmefällen wie diesem hier mit äußerster Vorsicht eingesetzt
werden, nur als allerletzter Notanker, wenn alle Möglichkeiten
ausgereizt und alle möglichen Fehlerquellen ausgemerzt sind, eine
Anwendung zum Terminieren zu bewegen!
Damit aufgrund des Einsatzes der End-Anweisung der Hilfs-Client,
der die "Killer"-Klasse instanziert, nicht davon in
Mitleidenschaft gezogen wird, instanziert und lädt die Klasse ein
Hilfs-Form mit einem Timer, in dessen Timer-Ereignis die
Aufräumarbeiten vorgenommen und schließlich die End-Anweisung
ausgeführt werden. Bis zum Aufruf des Timer-Ereignisses ist
nämlich der Aufruf des Hilfs-Clients bereits zurückgekehrt und die
Arbeit des Hilfs-Clients abgeschlossen. Probieren Sie einmal, den
Code zu ändern und rufen Sie die End-Anweisung anstelle der
Instanzierung des Hilfs-Forms auf - der Effekt sollte Ihnen deutlich
zeigen, was ich Ihnen zu vermitteln versuche.
' Klasse KillInstance
Private mFrmKillInstance As frmKillInstance
Public Sub Kill()
Set mFrmKillInstance = New frmKillInstance
mFrmKillInstance.tmr.Enabled = True
End Sub
' Hilfs-Form frmKillInstance
Private Sub tmr_Timer()
ReleaseForms
End
End Sub
' zusätzliche Aufräum-Prozedur im Standard-Modul modGlobal
Public Sub ReleaseForms()
If Not (mForms Is Nothing) Then
Do While mForms.Count
Unload mForms(1)
mForms.Remove 1
Loop
Set mForms = Nothing
End If
End Sub
Der Hilfs-Client, der die Komponente zur Selbstaufgabe bewegen
soll, besteht nur aus einem Standard-Modul mit einer kurzen Sub
Main-Prozedur:
Public Sub Main()
Dim nKill As KillInstance
Set nKill = New KillInstance
nKill.Kill
End Sub
Sie sehen, dass Sie mit recht geringem Aufwand noch retten
können, was zu retten ist, ehe Sie zur rigiden Maßnahme eines
Abschusses durch den Taskmanager greifen müssen. Dieses Verfahren
hat sogar den Vorteil, dass Sie damit auch auf einfache Weise auf
einem entfernten Server laufende, hängengebliebene Komponenten
entsorgen können. Denn das Verfahren bedient sich ganz regulärer
COM-Technik und ist vollkommen transparent in Bezug auf den
Ausführungsort der Komponente. Und wenn es sein muss, können Sie
auf diesem Wege auch kaskadierende Entrümpelungsaktionen anstoßen:
Hat die Komponenten wiederum andere Komponenten instanziert, und
sind die Referenzen auf diese für die Killer-Klasse noch greifbar,
brauchen auch diese weiteren Komponenten lediglich auf ähnliche
Weise aufräumbar vorbereitet zu sein...
Sie können den Code der Test-Komponente, des Test-Clients und
des "Killer"-Clients einschließlich kompilierter
EXE-Dateien für die Visual Basic-Versionen 5 und 6 separat
herunterladen - allerdings ohne Runtime-Dateien. Starten Sie die
EXE-Datei im jeweiligen Verzeichnis ActiveXExe einmal kurz, um die
Komponente zu registrieren. Starten Sie dann einmal ein paar
Instanzen des Clients und beenden diese wieder in willkürlicher
Reihenfolge. Sie sollten dann im Taskmanager auch nach dem Beenden
des letzten Clients immer noch die Komponente sehen können. Starten
Sie nun einmal den Killer-Client - und das Problem sollte sich wie
vorgesehen erledigt haben.
|