Lies diesen Artikel und viele weitere mit einem kostenlosen, einwöchigen Testzugang.
Eine umfangreiche Access-Anwendung kann in ihrem VBA-Projekt einige API-Deklarationen enthalten. Je länger diese Anwendung bereits entwickelt wird, desto mehr solcher Deklarationen haben sich im Laufe der Zeit in vielen verschiedenen Modulen angesammelt. Und umso mehr dieser APIs werden vielleicht gar nicht mehr verwendet, weil man sie grundsätzlich nicht mehr braucht oder sie durch andere Funktionen oder DLLs ersetzt hat. Daher ist es grundsätzlich interessant, nicht mehr verwendete Deklarationen von API-Funktionen aus der Anwendung zu entfernen. Noch interessanter wird dies, wenn die Migration einer für 32-Bit-Access ausgelegten Anwendung zu einer Anwendung ansteht, die auch unter 64-Bit-Access ihren Dienst tun soll. Je weniger API-Funktionen dann deklariert sind, umso weniger Anpassungen sind notwendig. Im vorliegenden Beitrag schauen wir uns zunächst an, wie Sie die API-Deklarationen und die gegebenenfalls benötigten Konstanten und Typen überhaupt finden.
Spätestens, wenn man Funktionen nutzen möchte, die nicht typischerweise von den eingebundenen Bibliotheken oder anderen, über den Verweise-Dialog hinzugefügten Bibliotheken bereitgestellt werden, benötigt man API-Funktionen.
Gängige Sätze solcher Funktionen dienen beispielsweise für folgende und viele weitere Anwendungszwecke:
- Anzeige von Dialogen zum Auswählen von Dateien und Ordnern
- Arbeiten mit Bildern
- Einsatz von FTP
- Verwenden von Verschlüsselungstechniken
- Erzeugen von GUIDs
Für manche Szenarien fügt man dazu umfangreiche Module mit vielen Deklarationen von API-Funktionen zu einem VBA-Projekt hinzu. In vielen Fällen verwendet die Anwendung jedoch nur einen Bruchteil der in den Modulen enthaltenen Befehle – zum Beispiel bei Modulen mit Funktionen für den Umgang mit Bildern per GDI.
Überblick über die API-Deklarationen verschaffen
Bevor man sich daran machen kann, die API-Funktionen in der mit 32-Bit-Access kompatiblen Fassung nach 64-Bit zu konvertieren, verschafft man sich erstmal einen Überblick über alle in einem Projekt enthaltenen APIs. Nun meinen wir damit nicht, dass Sie alle enthaltenen Module öffnen und diese manuell nach API-Deklarationen durchsuchen. Und wir wollen auch nicht die Suchfunktion nutzen, sondern uns selbst eine Routine schreiben, mit der wir alle Deklarationen von API-Funktionen finden.
Dazu nutzen wir die Klassen, Eigenschaften und Methoden der Bibliothek Microsoft Visual Basic for Applications 5.3 Extensibility, die wir über den Verweise-Dialog (Menüpunkt Extras|Verweise im VBA-Editor) zum Projekt hinzufügen (siehe Bild 1).
Bild 1: Verweis auf die Bibliothek Microsoft Visual Basic for Applications 5.3 Extensibility
Aktuelles VBA-Projekt identifizieren
Als Erstes benötigen wir einen Verweis auf das aktuelle VBA-Projekt. Es kann nämlich sein, dass der VBA-Editor mehr als ein Projekt gleichzeitig im Projekt-Explorer anzeigt – zum Beispiel, wenn gerade ein Access-Add-In geöffnet ist oder wenn der Benutzer eine Bibliotheksdatenbank als Verweis eingebunden hat.
Um dieses VBA-Projekt zu ermitteln, verwenden wir die Hilfsfunktion GetCurrentVBProject. Es durchläuft alle Elemente der VBProjects-Auflistung des VBA-Editors, den wir mit der Klasse VBE referenzieren, in einer For Each-Schleife.
Dabei vergleicht die Funktion den Dateinamen des VBProject-Objekts mit dem der aktuell geöffneten Access-Datenbank.
Stimmen diese überein, enthält objVBProject einen Verweis auf das VBA-Projekt der aktuellen Datenbank-Datei und die Funktion liefert diesen als Funktionsergebnis zurück:
Public Function GetCurrentVBProject() As VBProject Dim objVBProject As VBProject Dim objVBComponent As VBComponent For Each objVBProject In VBE.VBProjects If (objVBProject.FileName = CurrentDb.Name) Then Set GetCurrentVBProject = objVBProject Exit Function End If Next objVBProject End Function
Suche nach den API-Deklarationen
Dann beginnt der spannende Teil: die Suche nach den Deklarationen. Wie finden wir API-Deklarationen Müssen wir dazu jede einzelne Codezeile durchsuchen – und wonach genau suchen wir dabei
Der erste Hinweis ist: Jede Deklaration einer API-Funktion enthält das Schlüsselwort Declare. Dieses finden wir in keiner anderen Prozedur oder Funktion.
Der zweite Hinweis lautet: Jedes Modul ist unterteilt in den oberen Bereich mit den Deklarationen und den unteren Bereich mit den Funktionen und Prozeduren. Vielleicht haben Sie schon einmal festgestellt, dass Sie die Deklaration einer Variable oder auch einer API-Funktion in einem VBA-Modul nicht unterhalb der ersten Routine platzieren können.
Probieren Sie dies dennoch, erhalten Sie beim Kompilieren des Projekts die Fehlermeldung aus Bild 2. Genau genommen ist der Text der Meldung nicht ganz korrekt, denn hinter End Sub und so weiter können natürlich auch noch weitere Sub-, Function– oder Property-Funktionen stehen.
Bild 2: Fehler beim Deklarieren einer Variablen hinter einer Routine
Das ist jedoch hier irrelevant – es geht nur darum, dass jegliche Deklaration von Variablen, Kontanten und API-Funktionen immer vor der ersten Sub-, Function– oder Property-Prozedur stehen muss.
Alle Module durchlaufen
Wir müssen auf jeden Fall alle Module durchlaufen, um alle Deklarationen von API-Funktionen zu finden.
Das erledigen wir in einer einfachen For Each-Schleife über alle Elemente des Typs VBComponent der Auflistung VBComponents des aktuellen VBProject-Elements:
Public Sub FindAPIDeclares() Dim objVBProject As VBProject Dim objVBComponent As VBComponent Set objVBProject = GetCurrentVBProject For Each objVBComponent In objVBProject.VBComponents Debug.Print objVBComponent.Name Next objVBComponent End Sub
In diesem Fall geben wir die Namen aller Module im Direktbereich des VBA-Editors aus.
Das Durchsuchen der Codezeilen eines VBComponent-Elements umfasst einige Anweisungen, daher erstellen wir dafür direkt eine eigene Routine, die wir innerhalb der Schleife aufrufen:
...
For Each objVBComponent In objVBProject.VBComponents
FindAPIDeclaresInModule objVBComponent
Next objVBComponent
...
Alle Deklarationszeilen untersuchen
Die Prozedur FindAPIDeclaresInModule nimmt den Verweis auf das VBComponent-Objekt entgegen und weist das enthaltene CodeModule-Objekt der Variablen objCodeModule hinzu:
Public Sub FindAPIDeclaresInModule( objVBComponent As VBComponent) Dim objCodeModule As CodeModule Dim lngLine As Long Set objCodeModule = objVBComponent.CodeModule For lngLine = 1 To objCodeModule.CountOfDeclarationLines Debug.Print objCodeModule.Lines(lngLine, 1) Next lngLine End Sub
Für die danach folgende For…Next-Schleife muss man wissen, dass das CodeModule-Objekt eine eigene Eigenschaft bereitstellt, welche das Ende des Deklarationsbereichs des jeweiligen Moduls in Form der Zeilennummer liefert. Diese heißt CountOfDeclarationLines.
Wir brauchen also nur alle Zeilen von der ersten bis zu der mit CountOfDeclarationLines angegebenen Zeile zu durchlaufen, um alle möglichen Zeilen mit API-Deklarationen zu erwischen.
Aufgabe: Zeilenumbrüche
API-Deklarationen findet man oft in der mehrzeiligen Darstellung. Das ist übersichtlicher, wenn man beispielsweise jeden Parameter in einer neuen Zeile anzeigt:
Declare Function GetDiskFreeSpace Lib "kernel32" Alias "GetDiskFreeSpaceA" ( _ ByVal lpRootPathName As String, _ lpSectorsPerCluster As Long, _ lpBytesPerSector As Long, _ lpNumberOfFreeClusters As Long, _ lpTotalNumberOfClusters As Long) _ As Long
Die Untersuchung wird dadurch jedoch erschwert. Um jegliche interessante Idee von Entwicklern auszuschließen, wollen wir vor der Untersuchung einer Zeile auf eine API-Funktion zunächst die mit dem Unterstrich-Zeichen gesplitteten Zeilen in einer Variablen zusammenführen. Das gelingt in der Prozedur wie in Listing 1.
... For lngLine = 1 To objCodeModule.CountOfDeclarationLines strLine = objCodeModule.Lines(lngLine, 1) Do While Not InStr(1, objCodeModule.Lines(lngLine, 1), " _") = 0 strLine = VBA.Replace(strLine, " _", " ") strLine = VBA.Replace(strLine, " ", " ") strLine = VBA.Replace(strLine, " ", " ") strLine = VBA.Replace(strLine, " ", " ") strLine = VBA.Replace(strLine, "( ", "(") strLine = VBA.Replace(strLine, " )", ")") lngLine = lngLine + 1 strLine = strLine & objCodeModule.Lines(lngLine, 1) Loop If Not InStr(1, strLine, "''") = 0 Then strLine = Mid(strLine, InStr(1, strLine, "''")) End If Debug.Print strLine Next lngLine ...
Listing 1: Einlesen von mehrzeiligen Anweisungen als einzeilige Anweisungen
Hier durchlaufen wir nach wie vor alle Zeilen des Deklarationsbereichs, also bis zur Zeile aus CountOfDeclarationLines. Dabei schreiben wir zuerst den Inhalte der aktuellen Zeile in die Variable strLine.
In der folgenden Do While-Schleife prüfen wir, ob die Anweisung der aktuellen Zeile mit einem Unterstrich endet, was bedeutet, dass diese in der folgenden Zeile fortgesetzt wird. In diesem Fall nehmen wir einige Änderungen vor:
- Wir ersetzen Unterstriche, die auf Leerzeichen folgen, durch Leerzeichen.
- Wir ersetzen doppelte Leerzeichen durch einfache Leerzeichen, um die Einrückungen folgender Zeilen zu entfernen.
- Wir ersetzen öffnende Klammern mit folgendem Leerzeichen durch öffnende Klammern und schließende Klammern mir vorangestelltem Leerzeichen durch schließende Klammern.
Schließlich erhöhen wir innerhalb des Do While-Schleifendurchlaufs den Wert der Zählervariablen lngZeile um 1, damit die gleiche Zeile nicht beim nächsten Durchlauf der For…Next-Schleife erneut untersucht wird.
Außerdem fügen wir den Inhalt der aktuellen Zeile an den Inhalt von strLine an.
Danach prüfen wir noch, ob die Zeile noch weitere Hochkommata enthält, was auf angehängte Kommentare hinweist. Hier schneiden wir den Kommentar ab dem Hochkomma ab:
If Not InStr(1, strLine, "''") = 0 Then strLine = Mid(strLine, InStr(1, strLine, "''")) End If
Das Schlüsselwort Declare finden
Als Ergebnis landen mehrzeilige Anweisungen als einzeilige Anweisung in der Variablen strLine, wo wir diese dann weiter untersuchen können. In diesem Fall wollen wir wissen, ob die Zeile das Schlüsselwort Declare enthält. Dieses Schlüsselwort kann sich allerdings an verschiedenen Positionen befinden. Die Erste ist der Anfang der Zeile:
Declare Function CloseClipboard Lib "user32" () As Long
Oder es folgt auf eines der Schlüsselwörter Private oder Public:
Private Declare Function CloseClipboard Lib "user32" () As Long
Es könnte sich auch in einem Routinen- oder Parameternamen verstecken:
Public Sub FindDeclares()
Und zu guter Letzt kann eine Declare-Zeile auch auskommentiert sein:
''Declare Function CloseClipboard Lib "user32" () As Long
Wir machen also nichts verkehrt, wenn wir eine kleine Funktion programmieren, die prüft, ob es sich tatsächlich um eine Declare-Zeile für eine API-Funktion handelt und die wir jeweils nach dem Einlesen einer vollständigen, gegebenenfalls auch aus mehreren Zeilen bestehenden Anweisung aufrufen. Hier zunächst der Aufruf der Funktion aus der Prozedur FindAPIDeclaresInModule heraus:
For lngLine = 1 To objCodeModule.CountOfDeclarationLines ... If Not InStr(1, strLine, "Declare ") = 0 Then If IsDeclare(strLine) Then Debug.Print strLine End If End If Next lngLine
Die Funktion IsDeclare finden Sie in Listing 2. Die Funktion entfernt mit der Trim-Funktion alle führenden und folgenden Leerzeichen der Zeile aus strLine. Dann prüft sie, ob das erste Zeichen ein Kommentarzeichen ist (“). Falls nicht, untersucht sie, ob die Zeile die Zeichenfolge Declare direkt zu Beginn aufweist oder ob die Zeichenfolge Declare mit führendem und folgendem Leerzeichen weiter hinten folgt.
Public Function IsDeclare(strLine As String) As Boolean strLine = Trim(strLine) If Not Left(strLine, 1) = "''" Then If InStr(1, strLine, "Declare") = 1 Or Not InStr(1, strLine, " Declare ") = 0 Then IsDeclare = True End If End If End Function
Listing 2: Ermitteln, ob eine Zeile eine Declare-Anweisung enthält
Damit können wir innerhalb der If IsDeclare(strLine) Then-Bedingung per Debug.Print alle tatsächlichen API-Deklarationen ausgeben.
API-Funktionsdeklarationen in Tabelle speichern
Damit ist die Arbeit allerdings noch nicht zu Ende. Wir wollen die API-Deklarationen zunächst in einer Tabelle namens tblAPIDeclarations speichern (siehe Bild 3).
Bild 3: Entwurf der Tabelle zum Speichern der API-Deklarationen
Diese Tabelle soll die folgenden Felder enthalten:
- APIDeclarationID: Primärschlüsselwert der API-Deklaration
- APIDeclaration: Komplette Deklaration
- APICall: Aufruf der API-Funktion
- APIReturnType: Typ des Rückgabewerts der API-Funktion
- Module: Name des Moduls, in dem sich die Deklaration der API-Funktion befindet
Außerdem legen wir noch eine weitere Tabelle namens tblAPIParameters an. Diese soll die einzelnen Parameter der Deklaration der API-Funktionen speichern:
Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...
Testzugang
eine Woche kostenlosen Zugriff auf diesen und mehr als 1.000 weitere Artikel
diesen und alle anderen Artikel mit dem Jahresabo