API-Funktionen finden und speichern

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

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

Entwurf der Tabelle zum Speichern der API-Deklarationen

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

Schreibe einen Kommentar