API-Funktionen finden und speichern

Eine umfangreiche Access-Anwendung kann in ihrem VBA-Projekt einige API-Deklarationen enthaltenen. 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 suchen. 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).

Verweis auf die Bibliothek Microsoft Visual Basic for Applications 5.3 Extensibility

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.

Fehler beim Deklarieren einer Variablen hinter einer Routine

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 Moduls 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.

Sie haben das Ende des frei verfügbaren Textes erreicht. Möchten Sie ...

TestzugangOder haben Sie bereits Zugangsdaten? Dann loggen Sie sich gleich hier ein:

Schreibe einen Kommentar