APIs
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
Sinnvolle Anpassungen
Ptrsafe