|
"Der Letzte macht das Licht aus!" - dieser Spruch impliziert wohl auch, dass niemand das Licht ausmachen soll, solange noch jemand im Raum ist. Programmiertechnisch bedeutet dies, dass beispielsweise eine Sperrvariable (Flag) von mehreren Stellen aus im Code gesetzt werden kann, die Freigabe aber erst dann erfolgen soll, nachdem alle Stellen, die zuvor eine Sperrung veranlasst haben, die Sperrung auch tatsächlich wieder zurück genommen haben. Wichtig wird eine solche "sichere" Sperrung bei mehrstufig verschachtelten Aufrufen. Das folgende einfache Beispiel zeigt, dass es bei zunehmender Komplexität eines Programmes leicht zu unbeabsichtigten Seiteneffekten kommen kann, wenn eine solche Sperrvariable einfach direkt gesetzt oder freigegeben würde:
Private mFlag As Boolean
Private Sub ProzedurA()
mFlag = True
List1.ListIndex = List1.ListCount
mFlag = False
End Sub
Private Sub List1_Click()
If mFlag Then
MsgBox "OK"
End If
End Sub
Beabsichtigt ist, dass die MessageBox nur dann erscheint, wenn der ListIndex der ListBox List1 per Code, beispielsweise in der Prozedur ProzedurA geändert worden ist.
Es könnte aber auch noch eine zweite Prozedur (ProzedurB) existieren, in der ebenfalls der ListIndex jener ListBox geändert werden soll (hier sogar mehrfach), und die MessageBox im Click-Ereignis der MessageBox ebenfalls erscheinen soll. Nun bringt die Komplexität des Programms es mit sich, dass zwischen dem mehrfachen Setzen des ListIndexes in dieser Prozedur eine ProzedurC aufgerufen wird, in der "zufällig" auch die ProzedurA aufgerufen wird.
Private Sub ProzedurB()
mFlag = True
List1.ListIndex = -1
ProzedurC
List1.ListIndex = 10
mFlag = False
End Sub
Private Sub ProzedurC()
' ...
ProzedurA
' ...
End Sub
Der unbeabsichtigte Effekt ist, dass durch den verdeckten Aufruf der ProzedurA die Sperrvariable bereits zurückgesetzt worden ist, wenn in ProzedurB die weitere Änderung des ListIndexes der ListBox erfolgt. Da die Sperrung ja jetzt aufgehoben ist, wird die MessageBox fälschlicherweise nun nicht mehr angezeigt.
Wie wäre denn Abhilfe zu schaffen? Beispielsweise könnten Sie zur Absicherung bei jedem Setzen der Sperrvariablen prüfen, ob sie bereits gesetzt ist, dies dann festhalten und dadurch ein versehentliches Zurücksetzen vermeiden:
Private mFlag As Boolean
Sub XY()
Dim nFlagPrevious As Boolean
nFlagPrevious = mFlag
mFlag = True
' ...
If Not nFlagPrevious Then
mFlag = False
End If
End Sub
Das ist natürlich nicht sehr komfortabel, da Sie diesen zusätzlichen Code an jeder möglichen Stelle, an der die Sperrvariable gesetzt bzw. freigegeben werden soll, einbringen müssen. Alternativ könnten Sie jedoch auch eine Zählvariable verwenden:
Private mFlag As Long
Sub XY()
mFlag = mFlag + 1
' ...
mFlag = mFlag - 1
End Sub
Der erste Unterschied besteht schon darin, dass Sie die Sperrvariable nicht mehr so schön eindeutig als Boolean deklarieren können. Der zweite Unterschied liegt in der Prüfung:
If CBool(mFlag) Then ...
Sicher könnten Sie sich das CBool schenken. Aber statt dessen wäre es sinnvoll zu prüfen, ob der Wert der Sperrvariablen noch über 0 liegt - sie könnte ja "zufällig" öfter freigegeben worden sein, als sie gesetzt worden ist (was ja unterm Strich trotzdem eine Freigabe bedeuten dürfte):
If mFlag > 0 Then ...
Die Zähl-Variante ist schon erheblich sicherer. Und eine komfortable Handhabung ist auch möglich, wenn Sie die Variable als Eigenschaft verwalten (bei globaler Gültigkeit in einem Standard-Modul, z.B. modSecureFlagSimple.bas).
Private mSecureFlag As Long
Public Property Get SecureFlag() As Boolean
SecureFlag = CBool(mSecureFlag)
End Property
Public Property Let SecureFlag(New_SecureFlag As Boolean)
Select Case New_SecureFlag
Case False
mSecureFlag = mSecureFlag - 1
If mSecureFlag < 0 Then
mSecureFlag = 0
End If
Case True
mSecureFlag = mSecureFlag + 1
End Select
End Property
Sicherheitshalber können Sie noch die Prozedur ResetSecureFlag hinzu fügen, die die Sperrvariable in jedem Fall zurück setzt.
Public Sub ResetSecureFlag()
mSecureFlag = 0
End Sub
Beachten Sie, dass Sie selbstverständlich für mehrere, zu unterschiedlichen Zwecken verwendete Sperrvariablen die oben stehende Verwaltung jeweils separat mit unterschiedlichen Variablen- und Prozedurennamen anlegen müssen.
Die Verwendung selbst ist nun wieder ganz einfach:
Sub ABC()
SecureFlag = True
' ...
SecureFlag = False
End Sub
Sub XY()
SecureFlag = True
' ...
ABC
' ...
SecureFlag = False
End Sub
Auch wenn diese Lösung schon sehr sicher ist und in den meisten Fällen auch ausreichen sollte - so ganz befriedigend ist sie jedoch noch nicht. Denn bei zunehmender Komplexität des Geschehens könnte ja auch versehentlich eine Freigabe vergessen bzw. übersprungen werden - etwa durch ein vorzeitiges Verlassen einer Prozedur vor der Freigabe.
Abhilfe schafft hier ein Objekt (Klasse clsSecureFlag), das Sie für den Gültigkeitsbereich, in dem das Flag gesetzt werden soll, instanzieren. Dieses Objekt sorgt zunächst selbsttätig für das Erhöhen des Flagzählers. Beim Verlassen des Gültigkeitsbereiches wird das Objekt wieder zerstört und erniedrigt damit auch wieder selbsttätig den, wie oben dargestellt, als Eigenschaft (nun auf jeden Fall in einem Standard-Modul, z.B. modSecureFlag.bas) verwalteten Flagzähler.
Die Klasse clsSecureFlag sieht so aus:
Private Sub Class_Initialize()
SecureFlag = True
End Sub
Private Sub Class_Terminate()
SecureFlag = False
End Sub
Der Einsatz der Klasse erfolgt folgendermaßen:
Sub XY()
Dim nFlag As clsSecureFlag
' ...
Set nFlag = New clsSecureFlag
' ...
End Sub
Wie gesagt, wird die in nFlag abgelegte Instanz der Klasse clsSecureFlag mit dem Verlassen der Prozedur automatisch zerstört. Diese gibt damit die Sperrvariable aus ihrer Sicht frei. Natürlich können Sie die Sperrung auch vorzeitig, also vor dem Verlassen der Prozedur und dem damit verbundenen automatischen Zerstören des Objekts aufheben - Sie brauchen die Variable (nFlag) lediglich auf Nothing zu setzen:
' ...
Set nFlag = Nothing
' ...
End Sub
Die verschachtelte Verwendung bereitet auch keine Probleme mehr:
Sub ABC()
Dim nFlag As clsSecureFlag
Set nFlag = New clsSecureFlag
' ...
End Sub
Sub XY()
Dim nFlag As clsSecureFlag
Set nFlag = New clsSecureFlag
' ...
ABC
' ...
End Sub
Hüten Sie sich jedoch davor, die explizite Instanzierung sparen zu wollen und die Klasse clsSecureFlag gleich in der Deklaration instanzieren zu wollen - etwa:
Dim nFlag = New clsSecureFlag
So wird nämlich die gewünschte Funktion ausbleiben. Das Initialize-Ereignis der Klasse wird bei dieser Form der Instanzierung nicht automatisch aufgerufen, sondern erst beim ersten Zugriff auf eine Methode oder Eigenschaft - und clsSecureFlag verfügt weder über Methoden noch über Eigenschaften!
|