Letzte Änderung am 11. Januar 2022 by Christoph Jüngling
Vor ein paar Tagen habe ich über das Design der Instr-Funktion in VBA geschrieben. Ich hatte mich beschwert, dass die Namen der Parameter zum Teil nicht selbsterklärend sind. Ich sagte auch, dass man da nicht viel machen könne. Stimmt, nicht viel, aber ein bisschen was geht schon.
In diesem Artikel
Worum geht es?
In einem älteren Artikel habe ich bereits beschrieben, wie man Befehle durch eigene ersetzen kann. Damals ging es darum, den MsgBox-Befehl um eine Logging- oder Automatisierungs-Funktionalität zu erweitern, ohne etwas am Code ändern zu müssen. Ganz so elegant gelingt das diesmal nicht, denn die Instr-Funktion hat leider eine unangenehme Besonderheit.
Heute soll es um den Instr-Befehl gehen. Wir wollen dessen Parameter “String1” und “String2” umbenennen. Das geht nicht, denkt man sich vermutlich. Das stimmt – und auch wieder nicht. Was wir machen: Wir schieben unserem Programm einen eigenen Instr-Befehl unter, der fast so aussieht wie das Original, und auch fast genau so funktioniert. Und unter der Haube ruft dieser dann die originale Instr-Funktion aus der VBA-Bibliothek auf.
Damit die Funktion in allen unseren Programmen auch wirklich die Ersatzleistung erbringen kann, muss sie zunächst “von außen betrachtet” genauso aussehen wie das Original. “Von außen” heißt hier “aus der Sicht des aufrufenden Programmcodes”. Das heißt, dass die Parameter in der gleichen Reihenfolge und vom gleichen Typ sein müssen.
Das Original
Zunächst einmal suchen wir im Objektkatalog die Funktion Instr. Wir finden sie in der Bibliothek “VBA”:
Dort stellen wir fest, dass
- die ersten drei Parameter keine Typdeklaration haben
- alle Parameter der Funktion optional sind
- die Funktion keinen Rückgabetyp deklariert
Ich sag’s mal mit Rüdiger Hoffmann: “Kann man machen, muss man aber nicht!” Denn eigentlich wollte man wohl nur den ersten Parameter optional machen, damit die “1” als Startposition nicht immer angegeben werden muss. Leider erlaubt VBA so etwas nicht, nach dem ersten optionalen Parameter müssen alle weiteren ebenfalls optional sein.
Aber gut, Jean-Luc Picard sagt schließlich “make it so”, also machen wir es so.
Der neue Code
Aufgrund der Länge der Parameterliste im Code habe ich nun die Schreibweise mit mehreren Zeilen gewählt.
Public Function InStr( _
Optional ByVal Start = 1, _
Optional ByVal LookIn, _
Optional ByVal SearchFor, _
Optional ByVal Compare As VbCompareMethod = vbBinaryCompare _
)
InStr = VBA.InStr(Start, LookIn, SearchFor, Compare)
End Function
Ich denke, das ist einfach genug. oder? Ich habe mich für “LookIn” und “SearchFor” entschieden, die anderen beiden Parameter habe ich wie im Original belassen. Wichtig ist lediglich der Aufruf von Instr in der Funktion selbst. Warum das so ist, habe ich in dem oben verlinkten Artikel beschrieben.
Probleme
Jetzt könnten wir die Funktion einsetzen, aber sicherheitshalber formuliere ich mal ein paar Tests, um zu sehen, dass das auch wirklich klappt.
Public Sub Test_Instr_1()
' Regulärer Aufruf mit allen Parametern
Debug.Assert InStr(1, "ABCDEF", "c", vbBinaryCompare) = 0
Debug.Assert InStr(1, "ABCDEF", "c", vbTextCompare) = 3
Debug.Assert InStr(1, "ABCDEF", "X", vbTextCompare) = 0
End Sub
Public Sub Test_Instr_2()
' Regulärer Aufruf ohne "Compare" (also mit dessen Default-Wert)
Debug.Assert InStr("ABCDEF", "c") = 0 ' Ups!
Debug.Assert InStr(1, "ABCDEF", "X") = 0
End Sub
Und dabei stellen wir zunächst fest, dass nicht alles so läuft, wie wir uns das gedacht haben. Der erste Test in Test_Instr_2()
schlägt fehl, doch eigentlich darf er das nicht. Intellisense und auch der Objektkatalog sagen uns, dass der Standardwert des Parameters “Compare” der Wert vbBinaryCompare sein soll. So haben wir es in unserer eigenen Ersatzfunktion doch auch formuliert! Dieser Wert “führt einen binären Vergleich aus”, wie es in der Online-Hilfe auf microsoft.com heißt. Der Wert dieser Konstanten ist laut der Dokumentation “0”, was sich im Direktfenster leicht verifizieren lässt.
Ein weiterer Test im Direktfenster mit sehr überschaubarem Code bestätigt die Beobachtung:
?instr(1,"ABC","a") 1
Das darf eigentlich nicht sein, denn ein binärer Vergleich läuft wohl auf einen Vergleich des ASCII-Codes der Buchstaben “A” und “a” hinaus, und der ist definitiv unterschiedlich. Interessant ist nun, dass bei expliziter Angabe des Compare-Parameters alles genau so funktioniert:
?instr(1,"ABC","a",vbBinaryCompare) 0
Was geht denn hier ab?
Erkenntnis 1
Zunächst lässt sich durch einen simplen Breakpoint in der eigenen Funktion feststellen, dass diese gar nicht aufgerufen wird! Der Trick aus Befehle durch eigene ersetzen ist also ohne weiteres leider nicht anwendbar – warum auch immer. Denn “Interaction” (die Herkunft von MsgBox) und “String” (die Herkunft von Instr) sind beide Bestandteil der Bibliothek “VBA”.
Warum wird diese zum Teil vor meinem eigenen Code durchsucht, und zum Teil danach? Bei MsgBox übrigens funktioniert alles noch wie damals beschrieben!
Erkenntnis 2
Ein direkter Aufruf von Testapplikation.InStr()
führt zumindest zum gewünschten Ergebnis, dem binären Vergleich.
Diese Schreibweise ist mir aber zu umständlich, dann nenne ich meine Ersatzfunktion lieber InstrX
oder ähnlich. Jetzt muss ich leider doch meinen Code anfassen und alle Stellen editieren. Vielleicht reicht ein Find and Replace (mit der eingebauten Funktionalität oder einem separatem Tool), aber die Frage stellt sich: Ist es das alles noch wert, nur um ein paar Parameter “sprechend” zu machen?
Erkenntnis 3
Das oben beschriebene Problem mit dem Aufruf ohne Compare-Parameter lässt sich zumindest auf Microsoft Access eingrenzen. In Excel gibt es damit kein Problem. Offenbar hat innerhalb der Accsss-VBA-Umgebung die modulweite Festlegung Option Compare Database
Vorrang vor dem individuellem Aufruf der eingebauten Instr-Funktion! Ändere ich diese, ändert sich sofort das Verhalten von Instr. Das ist auch ein wenig seltsam und ich bin nicht sicher, ob das schon immer so war oder sich erst mit einem Update geändert hat.
Anscheinend ist der Default-Wert des Compare-Parameters innerhalb von Access eben nicht vbBinaryCompare
mit dem Wert 0, sondern vbUseCompareOption
mit dem Wert -1. Denn der führt wie beobachtet und auch laut Microsoft-Hilfe “mit der Einstellung der Option Compare
-Anweisung einen Vergleich aus”.
Ich kann nicht sagen, dass ich das alles besonders toll finde.
1 Kommentar
Servus Christoph, die InStr-Funktion ist in der Tat das Paradebeispiel für eine Funktion mit einer erklärungsbedürftigen Signatur. Trotz einiger Jahre Programmiererfahrung muss ich hin und wieder diese Funktion nachschlagen.
Viele Grüße
Tony