VBScript Falle "ByVAL"

Alle Skripte sind Muster ohne jede Gewährleistung oder Funktionsgarantie. für Schäden bin ich nicht verantwortlich. Achten Sie auf Zeilenumbrüche bei der Übernahme.

Wer mit VBScript entwickelt wird bei größeren Projekten mit Funktionen und Prozeduren arbeiten. Dabei werden bestimmte für sich logisch getrennte Aufgaben in eigenen Bereiche aufgegliedert. Das macht den Code übersichtlicher und wieder verwendbar.

Eine wesentliche Funktion bei der Verwendung von "SUB und FUNCTION" ist die Übergabe von Parametern und die Rückgabe von Ergebnissen. Sowohl eine FUNCTION als auch eine SUB erhalten ihre Parameter als Übergabe. Natürlich können Sie auch auf globale Variablen zugreifen, aber dies sollte nur mit Bedacht durchgeführt werden. Ideal ist die weitgehende Kapselung der Funktionen und Prozeduren.

Mit Funktionen stellt sich die Rückübergabe relativ einfach dar, da eine Funktion selbst schon ein Ergebnis zurückliefert. Bei Prozeduren ist es aber etwas umständlicher. Hierzu muss man die Übergabeform verstehen.

  • ByVAL
    Hierbei wird der Wert der Variable übergeben. Die unterroutine kann damit machen was sie will, denn Sie wird nicht in die aufrufende Routine zurückgegeben. Es ist eine Kopie der Daten
  • ByREF
    Hierbei wird nur ein "Verweis" auf den Speicherbereich übergeben. Wird in der unterroutine der Inhalt geändert, ändert dies auch den Inhalt in der aufrufenden Routine. Es ist ja auch der gleiche Inhalt, nur dass zwei verschiedene Variablennamen auf die gleiche Speicherstelle weisen.

Wird nun nichts vorgegeben, dann können Sie als Entwickler eigentlich nie sicher sein, ob die Variablen nun ByVAL oder ByREF übergeben werden. Speziell wenn Sie in einer unterroutine temporäre Variablen sparen wollen oder Veränderungen in der aufrufenden Routine ausschließen müssen, eignet sich die ByVAL Übergabe sehr gut. Ob eine Variable aber ByVAL oder ByREF übergeben wird, sollten Sie dann auch genau spezifizieren ansonsten kann schon eine Klammer beim Aufruf zuwenig oder ein "Call" zuwenig das Verhalten ändern.

Funktionsaufruf Übergabe

call unterfunktion(var1)

ByVAL

Unterfunktion var1

ByREF

Ergebnis = funktion(var1)

ByVAL

Ergebnis = funktion var1

ByREF

Erg = funktion(var1,(var2))

var1 als ByRef, var2 als ByVAL

Es ist alles nur eine Frage der "Klammern". Wenn Sie dies "sicher" ausschließen möchten, dann sollten Sie in der Funktionsdefinition die Übergabe vorschreiben. Wir bei einer Definition vor eine Variable ein "ByVAL" gesetzt, dann wird immer "byVAL übergeben. ABER ACHTUNG: Wenn man "ByREF" angibt ist das immer noch keinen Sicherheit. Daher sollte man beim erwünschten Rückgabewerten eine Funktion nutzen oder bei Prozeduren nur mit Vorsicht "klammern" setzen.

Hier eine kleine Funktion zum selbsttesten:

Function SubFunkt(var1)
	WScript.echo "-Sub vorher :"&var1
	var1=1
	WScript.echo "-Sub nachher:"&var1
End Function

var0 = 0 
WScript.echo "Vorher :"&var0
SubFunkt var0
WScript.echo "Nachher1 :"&var0
var0 = 0 
SubFunkt (var0)
WScript.echo "Nachher2 :"&var0

Nachher1 wird eine 1 enthalten, da ByREF übergeben wird und Nachher2 wird wieder ein 0 bleiben, da hier ByVAL übergeben wird. Testweise sollten Sie einfach mal in der Definition schreiben: "Function SubFunkt(ByVAL var1)". Dann wird immer ByVAL übergeben.

Die Quelle

Auf http://blogs.msdn.com/ericlippert/archive/2003/09/15/52996.aspx habe ich eine sehr ausführliche Erklärung  bei den "Klammern" gesehen.

Here's the deal: parentheses mean several different things in VB and hence in VBScript. They mean:

1. Evaluate a subexpression before the rest of the expression: Average = (First + Last) / 2

2. Dereference the index of an array: Item = MyArray(Index)

3. Call a function or subroutine: Limit = uBound(MyArray)

4. Pass an argument which would normally be byref as byval: Result = MyFunction(Arg1, (Arg2)) ' Arg1 is passed byref, arg2 is passed byval

That's confusing enough already. Unfortunately, VB and hence VBScript has some weird rules about when #3 applies. The rules are:
3.1) An argument list für a function call with an assignment to the returned value must be surrounded by parens: Result = MyFUNC(MyArg)
3.2) An argument list für a subroutine call (or a function call with no assignment) that uses the Call keyword must be surrounded by parens: Call MySub(MyArg)
3.3) If 3.1 and 3.2 do not apply then the list must NOT be surrounded by parens.
And finally there is the byref rule: arguments are passed byref when possible. If there are “extra” parens around a variable then the variable is passed byval, not byref.

Pointer oder Wert

Übrigens ist nicht nur "ByVal" eine mögliche Fehlerquelle. Auch die Übergabe von Werten als "Werte" oder als "Pointer" sind mögliche Fehlerquellen. Folgendes Beispiel soll das verdeutlichen:

' String Variablen "byVal"
a = "string1"
b = a
a = "string2"
wscript.echo "a=" & a
wscript.echo "b=" & b

Dieser Code ist ja noch ganz einfach und zu verstehen. Der Variablen B wird der Inhalt von A zu gewiesen und danach A geändert. Die Ausgabe ist sicher für jeden klar verständlich

Nun wir das Beispiel etwas verzwickter. Als Speicher dient nun keine einfache Variable, sondern ein Objekt vom Typ "Dictionary". Dem Dictionary A wird ein "Key1" mit dem Inhalt "Item1" hinzugefügt und dann wird B auf A gesetzt.

Dann wird in A der Wert von "Key1" auf "Item2" gesetzt

set a = CreateObject("Scripting.dictionary")
a.add "Key1", "Item1"
wscript.echo "Content of a(Key1) = " & a.item("Key1")
set b = a
wscript.echo "Content of b(Key1) = " & b.item("Key1")
a.item("Key1") = "Item2"
wscript.echo "Content of a(Key1) = " & a.item("Key1")
wscript.echo "Content of b(Key1) = " & b.item("Key1")

Die Ausgabe ist hier dann nicht mehr so einfach zu verstehen oder ?

Obwohl nur der Inhalt des Dictionary A geändert wurde, hat sich auch der Inhalt von B geändert. Es handelt sich gar nicht um zwei eigenständige Dictionaries, sondern A und B sind nur Pointer auf das gleiche Objekt. Wenn man also ein komplettes Objekt kopieren will, dann muss man sich genau überlegen, was man dabei kopiert.

Der gleiche Effekt kann auch bei Zugriffen z.B.: auf Datenbanken auftreten:

' Das Recordset wird durch eine Abfrage gefüllt

Do until objRecordSet.EOF

    ' Weise der Variable strDN den Wert zu
    strDN = objRecordSet.Fields("distinguishedName").Value

    ' Weise der Variable DN zu
    DN = objRecordSet.Fields("distinguishedName")

    ' Ausgabe der beiden Variablen
    wscript.echo "strDN :" & strDN
    wscript.echo "DN    :" & DN
    ' Beide Ausgaben enthalten anscheinend den gleichen Inhalt !

    Auf zum nächsten Feld
    objRecordSet.MoveNext

    ' und erneut ausgeben
    wscript.echo "strDN :" & strDN
    wscript.echo "DN    :" & DN
    ' Die Ausgaben sind nun unterschiedlich
Loop

Die Variable "strDN" enthält den DistinguishedName als String und bleibt unverändert. Hingegen enthält DN nicht den String, sondern nur einen Verweis auf den String. Die erste Ausgabe gibt daher das erste Objekt aus, während nach dem "objRecordSet.MoveNext" schon das zweite Objekt ausgegeben.

Ich merke mir das immer so:
Ordinale Variablen (Integer, String etc.) werden so verwendet, wie man es vielleicht erwarten würde.
Komplexe Konstruktionen (Arrays, Recordset, Klassen etc.) sind Pointer auf Speicherstellen. Wenn man hier den Wert verwenden möchte, sollte man ".Value" nutzen, sofern das Objekt dies unterstützt.

Sie sehen also dass es nicht immer die offensichtlichen Fehler sind, die ein Programm das Falsche tun lassen.