APIs

Aus VBA-wiki
Wechseln zu: Navigation, Suche

APIs?!?

Application Programming Interfaces (kurz: APIs) dienen dazu, Aufgaben zu lösen, welche nicht oder nur unzureichend von VBA (bzw. den Office-Anwendungen) gelöst werden können.

Ein Beispiel:

Mithilfe von VBA können wir feststellen, ob eine Datei existiert:

If Dir(strFullName) = "" Then Exit Sub

Mithilfe von VBA und den Anwendungs-Objekten können wir ermitteln, wann eine in der Anwendung geöffnete Datei erstellt wurde (Beispiel aus PowerPoint):

Debug.Print ActivePresentation.BuiltInDocumentProperties.Item("Creation date")

Wenn wir jedoch ermitteln sollen, welche Datei in einem Verzeichnis die älteste ist, wird uns dies nur dann möglich, wenn wir die Dateien einzeln öffnen (vorausgesetzt es handelt sich zum Beispiel bei PowerPoint um Präsentationen, bei Excel um Arbeitsmappen usw.) und dann die Eigenschaft prüfen. Oder wir verwenden eine vom Betriebssystem zur Verfügung gestellte API, welche für jede beliebige Datei die geforderten Informationen ermitteln kann.


Empfohlene Vorgehensweise

Benötigte API finden

Um einen brauchbaren Ansatz für die gesuchte Aufgabe zu finden, empfehle ich die Suche im Internet.

Wichtig: Stellen Sie dazu der gesuchten Anfrage die Worte 'Windows API VB' voran, um die Ergebnisse sinnvoll einzugrenzen (VBA liefert leider oftmals keine guten Treffer):

Die Suche nach 'windows api vb datei erstelldatum' ergab mehrere Treffer, die auf die Seite vb@rchiv verweisen. Diese werden wir uns näher ansehen.

  • Der erste Treffer verspricht 'Alle Datumsangaben einer Datei ermitteln'
  • Aufgepasst: Das vb@rchiv verfügt auch über Lösungen für VisualBasic, VB.NET und weitere Sprachen. VisualBasic enthält teilweise Befehle für die Anwendungsentwicklung, welche VBA nicht enthält und VB.NET sieht auf den ersten Blick VBA sehr ähnlich, verfügt jedoch über ein völlig anderes Objektmodell, wodurch spezifische Lösungen mit Sicherheit inkompatibel sind.
  • Sie werden feststellen, dass die meisten API-Aufrufe sehr komplex und umfangreich sind, weil sie zum Beispiel
    • Weitere API-Aufrufe benötigen
    • Benutzerdefinierte Datentypen verwenden
  • Ich empfehle Ihnen, für Ihre Tests ein eigenes Modul anzulegen und alles, was Sie in einem Suchergebnis gefunden haben, dort hineinzukopieren
    • Versuchen Sie dann zu ermitteln, welche Prozedur die Steuerprozedur ist (also die Prozedur, die das Ergebnis liefert)
    • Rufen Sie diese Prozedur auf
      • Schreiben Sie dazu eine eigene Prozedur
      • Ändern Sie bitte (noch) nicht die Originalprozedur!
    • Wenn das gefundene Beispiel das gewünschte Ergebnis zurückliefert, prima!
    • Wenn nicht:
      • Sehen Sie nach, ob Sie eventuell das Problem erkennen können
      • Verwenden Sie hierauf nicht zuviel Zeit sondern versuchen Sie den nächsten Vorschlag:
      • Suchen Sie eine weitere Beispiellösung
  • Hier und da werden Sie Lösungen für Aufgaben finden, die Ihrer ähneln aber nicht entsprechen
    • Kopieren Sie trotzdem die komplette angebotene Lösung
    • Testen Sie, ob die gefundene Lösung die von Ihnen geforderte Aufgabe erledigt
    • Erst wenn Sie sicher sind, dass alles funktioniert, können Sie eventuell einzelne, überflüssig erscheinende Teile in Kommentar setzen (niemals sofort löschen!)
    • Wie schon erwähnt sind API-Aufrufe oft sehr komplex und umfangreich. Wenn Sie das Falsche entfernen, kann er danach kaputt sein ...

Lösungsbeispiel übernehmen

Bei der obigen Suche war mein erster Treffer ein Artikel auf vb@rchive namens 'Alle Datumsangaben einer Datei ermitteln'.

Hier der Lösungsvorschlag (mit Kommentaren des Orginal-Autoren Dieter Otter):

' zunächst die benötigten API-Deklarationen
 
' Datei Datum/Zeit
Private Type FileTime
  dwLowDateTime As Long
  dwHighDateTime As Long
End Type
 
Private Type SYSTEMTIME
  wYear As Integer
  wMonth As Integer
  wDayOfWeek As Integer
  wDay As Integer
  wHour As Integer
  wMinute As Integer
  wSecond As Integer
  wMilliSeconds As Integer
End Type
 
Private Declare Function CreateFile Lib "kernel32" _
  Alias "CreateFileA" ( _
  ByVal lpFilename As String, _
  ByVal dwDesiredAccess As Long, _
  ByVal dwShareMode As Long, _
  ByVal lpSecurityAttributes As Long, _
  ByVal dwCreationDisposition As Long, _
  ByVal dwFlagsAndAttributes As Long, _
  ByVal hTemplateFile As Long) As Long
 
Private Declare Function CloseHandle Lib "kernel32" ( _
  ByVal hObject As Long) As Long
 
Private Declare Function GetFileTime Lib "kernel32" ( _
  ByVal hFile As Long, _
  lpCreationTime As FileTime, _
  lpLastAccessTime As FileTime, _
  lpLastWriteTime As FileTime) As Long
 
Private Declare Function SetFileTime Lib "kernel32" ( _
  ByVal hFile As Long, _
  lpCreationTime As FileTime, _
  lpLastAccessTime As FileTime, _
  lpLastWriteTime As FileTime) As Long
 
Private Declare Function FileTimeToLocalFileTime Lib "kernel32" ( _
  lpFileTime As FileTime, _
  lpLocalFileTime As FileTime) As Long
 
Private Declare Function FileTimeToSystemTime Lib "kernel32" ( _
  lpFileTime As FileTime, _
  lpSystemTime As SYSTEMTIME) As Long
 
Private Declare Function SystemTimeToFileTime Lib "kernel32" ( _
  lpSystemTime As SYSTEMTIME, _
  lpFileTime As FileTime) As Long
 
Private Declare Function LocalFileTimeToFileTime Lib "kernel32" ( _
  lpLocalFileTime As FileTime, _
  lpFileTime As FileTime) As Long
 
Private Const GENERIC_READ = &H80000000
Private Const GENERIC_WRITE = &H40000000
Private Const OPEN_EXISTING = 3
 
' Datum/Zeit einer Datei ermitteln
Public Function ReadFileTime(ByVal lpFilename As String, _
  tCreation As Date, tLastAccess As Date, _
  tLastWrite As Date) As Boolean
 
  Dim fHandle As Long
 
  Dim ftCreation As FileTime
  Dim ftLastAccess As FileTime
  Dim ftLastWrite As FileTime
  Dim LocalFileTime As FileTime
  Dim LocalSystemTime As SYSTEMTIME
 
  ReadFileTime = False
  fHandle = CreateFile(lpFilename, GENERIC_READ, 0, _
    0, OPEN_EXISTING, 0, 0)
  If fHandle <> -1 Then
    ' Zeitinformationen auslesen
    If GetFileTime(fHandle, ftCreation, ftLastAccess, _
      ftLastWrite) <> 0 Then
 
      ' Erstellungsdatum
      FileTimeToLocalFileTime ftCreation, LocalFileTime
      FileTimeToSystemTime LocalFileTime, LocalSystemTime
      With LocalSystemTime
        tCreation = CDate(Format$(DateSerial(.wYear, _
          .wMonth, .wDay), "Short Date") & " " & _
          Format$(.wHour) & ":" & _
          Format$(.wMinute, "00") & ":" & _
          Format$(.wSecond, "00"))
      End With
 
      ' Letzter Zugriff
      FileTimeToLocalFileTime ftLastAccess, LocalFileTime
      FileTimeToSystemTime LocalFileTime, LocalSystemTime
      With LocalSystemTime
        tLastAccess = CDate(Format$(DateSerial(.wYear, _
          .wMonth, .wDay), "Short Date") & " " & _
          Format$(.wHour) & ":" & _
          Format$(.wMinute, "00") & ":" & _
          Format$(.wSecond, "00"))
      End With
 
      ' Letzte Änderung
      FileTimeToLocalFileTime ftLastWrite, LocalFileTime
      FileTimeToSystemTime LocalFileTime, LocalSystemTime
      With LocalSystemTime
        tLastWrite = CDate(Format$(DateSerial(.wYear, _
          .wMonth, .wDay), "Short Date") & " " & _
          Format$(.wHour) & ":" & _
          Format$(.wMinute, "00") & ":" & _
          Format$(.wSecond, "00"))
      End With
 
      ReadFileTime = True
    End If
    CloseHandle fHandle
  End If
End Function

Diesen kopieren Sie bitte in ein neues Modul, in dem wir den Lösungsvorschlag untersuchen und anpassen werden. Wenn manche Zeilen im VBA-Editor rot hervorgehoben werden, ignorieren Sie das bitte vorerst. Darum kümmern wir uns sehr bald ...

Im Originalartikel wurde ebenfalls ein Beispiel-Aufruf mitgegeben:

Dim tCreation As Date
Dim tLastAccess As Date
Dim tLastWrite As Date
Dim sFilename As String
 
sFilename = "c:\eigene dateien\MeineDatei.txt"
 
ReadFileTime sFilename, tCreation, _
  tLastAccess, tLastWrite
 
MsgBox "Erstellungsdatum: " & _
  Format$(tCreation, "dd.mm.yyyy hh:mm:ss") & _
  vbCrLf & "Letzter Zugriff am: " & _
  Format$(tLastAccess, "dd.mm.yyyy hh:mm:ss") & _
  vbCrLf & "Letzter Schreibvorgang: " & _
  Format$(tLastWrite, "dd.mm.yyyy hh:mm:ss")

Dieser ist eindeutig als Inhalt einer Prozedur gedacht. Erstellen Sie also eine Prozedur hierfür und kopieren Sie das Beispiel dort hinein:

Sub Test()

    Dim tCreation As Date
    Dim tLastAccess As Date
    Dim tLastWrite As Date
    Dim sFilename As String
     
    sFilename = "c:\eigene dateien\MeineDatei.txt"
     
    ReadFileTime sFilename, tCreation, _
      tLastAccess, tLastWrite
     
    MsgBox "Erstellungsdatum: " & _
      Format$(tCreation, "dd.mm.yyyy hh:mm:ss") & _
      vbCrLf & "Letzter Zugriff am: " & _
      Format$(tLastAccess, "dd.mm.yyyy hh:mm:ss") & _
      vbCrLf & "Letzter Schreibvorgang: " & _
      Format$(tLastWrite, "dd.mm.yyyy hh:mm:ss")
End Sub

Anmerkung: Das Erstellen einer eigenen Prozedur ist nur dann nötig, wenn der Beispiel-Aufruf wie hier ohne eine Prozedur zur Verfügung gestellt wurde.

Erste Anpassungen und oberflächliche Analyse

  • Benutzerdefinierte Datentypen 'Type' (optional)
    • Für API-Aufrufe werden oftmals benutzerdefinierte Datentypen verwendet, um ganze Informationsblöcke austauschen zu können
    • Diese müssen oberhalb der Prozedur, bei der sie verwendet werden, deklariert sein
    • Wir werden sie wie im Beispiel oben stehen lassen
  • Deklaration der API-Prozeduren
    • In unserem Beispiel werden mehrere Funktionen mit 'Private Declare Function' deklariert
    • Wenn Sie Office 64 Bit verwenden, werden diese Zeilen rot markiert und verursachen beim Kompilieren einen
    • Um trotzdem vorerst testen zu können, ersetzen Sie jedes 'Declare' mit 'Declare PtrSafe' (mehr dazu später)
Private Declare Function CreateFile ...

wird zu

Private Declare PtrSafe Function CreateFile ...
  • Konstanten (optional)
    • Die im Beispiel verwendeten API-Aufrufe benötigen neben den benutzerdefinierten Datentypen auch mehrere Konstanten
    • Diese müssen lediglich vor der ersten Prozedur deklariert werden, können also auch weiter oben erscheinen
  • Prozeduren (optional)
    • Unserem Beispiel wurde eine Prozedur mitgegeben, welche den Aufruf der verschiedenen API-Prozeduren bündelt und die für das Beispiel geforderte Aufgabe erfüllt.
    • Die Funktion 'ReadFileTime' erwartet die Übergabe eines Dateinamens und drei Datumsangaben für Die Erzeugung, den letzten Zugriff auf und das letzte Speichern der Datei
    • Die drei Datumswerte werden nicht in der Funktion verwendet, sondern 'ByRef' übernommen, das heißt, sie enthalten nach dem Aufruf der Funktion die gewünschten Werte
    • Die Funktion selbst hat den Datentyp 'Boolean', was darauf hindeutet, dass auf diesem Wege mitgeteilt wird, ob die Ermittlung der Daten erfolgreich war
  • Beispielprozedur (optional)
    • Der Autor dieses Beispieles hat uns zusätzlich auch noch den Code einer Steuerprozedur mitgegeben, womit wir seinen Lösungsvorschlag testen können :-)

Bevor Sie zum Test übergehen, sollten Sie das Testprojekt, in das Sie die Beispielprogrammierung kopiert haben, kompilieren, um sicher zu gehen, dass keine offensichtlichen Fehler enthalten sind.

Erste Tests

Nun können Sie die Prozedur 'Test' bzw. die Prozedur, in welche Sie den Beispielcode kopiert haben, mit 'F5' ausführen.

API Test 01.png

Der Beispielcode scheint zu funktionieren, allerdings ist das Ergebnis (alle drei Zeitangaben enthalten den Tag Null, also den 30. Dezember 1899) nicht befriedigend.

Der Autor hatte im Beispiel folgende Dateivorgabe gemacht:

    sFilename = "c:\eigene dateien\MeineDatei.txt"

Diese sollte auf eine Datei geändert werden, die tatsächlich existiert. Suchen Sie sich dazu eine beliebige Datei und geben Sie sie komplett mit Pfad an:

   sFilename = "c:\temp\test.txt"        ' Geben Sie hier eine Datei an, die Sie tatsächlich auf Ihrem Rechner finden

API Test 02.png

Nun entsprechen die Angaben tatsächlich den Angaben im Eigenschaften-Dialog:

API Test 03.png

Sinnvolle Anpassungen

Ptrsafe

Fertige Beispiellösung 'Älteste Datei in Verzeichnis'

Struktur eines API-Aufrufes