Alte API-Deklarationen fit für 64 Bit machen

Sie haben ein altes VBA-Projekt geerbt, das Funktionen der Windows-API aufruft, und unter 64-Bit-Access meldet der Compiler Fehler in den Declare-Zeilen? Dann müssen die Deklarationen angepasst werden. Wie das Schritt für Schritt gelingt, welche Rolle die Compilerkonstanten VBA7 und Win64 dabei spielen und an welchen Stellen die Prüfung wirklich nötig ist, zeigt der vorliegende Beitrag.

Was sich mit 64 Bit ändert

Lange Zeit war das ein Randthema, denn Office wurde standardmäßig in der 32-Bit-Version installiert. Ab Office 2019 und Microsoft 365 ist laut Microsoft die 64-Bit-Version die Vorgabe. Wer Anwendungen weitergibt, trifft deshalb immer häufiger auf 64-Bit-Access, und der Code muss damit zurechtkommen. Geändert werden muss er übrigens nur dort, wo er unter 64 Bit läuft. Wer weiterhin ausschließlich 32 Bit einsetzt, merkt nichts.

Der Kern des Problems: Die Windows-API kennt Handles und Zeiger. Ein Fensterhandle, wie es FindWindow liefert, ist im Kern eine Adresse, und die ist unter 64-Bit-Windows 64 Bit breit. Der VBA-Datentyp Long hat dagegen immer 32 Bit. Weil VBA 6 keinen eigenen Datentyp für Zeiger kannte, nahm man dort einfach Long. Passt ein 64-Bit-Wert nicht hinein, wird er abgeschnitten, und das kann bis zum Absturz der Anwendung führen.

Microsoft hat dafür mit VBA 7 (Office 2010) drei Neuerungen eingeführt. Erstens muss jede Declare-Anweisung in 64-Bit-Office das Schlüsselwort PtrSafe enthalten. Zweitens gibt es den Datentyp LongPtr. Er entspricht in 32-Bit-Office einem Long und in 64-Bit-Office einem LongLong, wächst also mit der Plattform.

Drittens gibt es den Datentyp LongLong selbst, eine vorzeichenbehaftete 64-Bit-Ganzzahl, die es nur in 64-Bit-Office gibt. Die Hintergründe zu den VBA-Versionen erläutert der Beitrag 32-Bit, 64-Bit, VBA-Version und Co. (www.access-im-unternehmen.de/1322). Hier geht es um die praktische Umrüstung.

Die Compilerkonstanten im Überblick

Damit ein Projekt in mehreren Umgebungen kompiliert werden kann, gibt es vordefinierte Compilerkonstanten. Sie werden ausschließlich mit der Direktive #If … Then … #Else verwendet und gelten im ganzen Projekt. Eigene Konstanten mit denselben Namen lassen sich nicht anlegen. Wichtig sind diese:

  • VBA7: Die Entwicklungsumgebung ist VBA 7 (Office 2010 oder neuer). Nur dann kennt der Compiler PtrSafe und LongPtr.
  • VBA6: Die Umgebung ist mit VBA 6 kompatibel. Das gilt auch unter VBA 7.
  • Win64: Die Umgebung ist 64-Bit-Office. Nur hier gibt es LongLong.
  • Win32: Die Umgebung ist mit 32 Bit kompatibel. Das ist auch in 64-Bit-Office der Fall.
  • Win16 und Mac: 16-Bit-Umgebungen und Macintosh. Für Access unter Windows sind beide immer False.

Die beiden Zeilen mit VBA6 und Win32 sind die eigentlichen Stolpersteine. Weil Win32 auch unter 64 Bit True liefert, würde eine Abfrage auf Win32 vor der Abfrage auf Win64 den 64-Bit-Zweig nie erreichen.

Dasselbe gilt für VBA6 und VBA7. Die richtige Reihenfolge lautet deshalb immer vom Neuen zum Alten. Die Funktion Plattform im Modul mdlKonstanten zeigt es:

Public Function Plattform() As String
    #If Win64 Then
        Plattform = "64 Bit"
    #ElseIf Win32 Then
        Plattform = "32 Bit"
    #Else
        Plattform = "16 Bit"
    #End If
End Function

Auf unserem Testrechner mit 32-Bit-Access liefert Debug.Print Plattform() im Direktbereich den Wert 32 Bit. Die Funktion KonstantenText im selben Modul gibt alle fünf Konstanten aus:

VBA6=True; VBA7=True; Win32=True; Win64=False; Mac=False

Zwei Dinge fallen auf: VBA6 ist auch unter VBA 7 True, und Win32 ist wahr, und das bliebe es auch unter 64 Bit. Wer die Konstanten also in der falschen Reihenfolge abfragt, landet im falschen Zweig, ohne dass der Compiler etwas bemängelt.

Die Beispieldatenbank

In der Beispieldatenbank ApiDeklarationenFuer64Bit.accdb finden Sie vier Module. Das Modul mdlApiAlt enthält vier typische Deklarationen im alten Stil, das Modul mdlApiNeu dieselben Deklarationen in der angepassten Form. Dazu kommen mdlKonstanten mit den Compilerkonstanten und mdlApiBeispiele mit weiteren Beispielen. Die vier ersten Funktionen haben wir bewusst unterschiedlich gewählt: GetTickCount liefert nur eine Zahl, FindWindow gibt ein Handle zurück, GetWindowRect erwartet ein Handle und eine Struktur, und FlashWindowEx enthält ein Handle innerhalb einer Struktur.

So treffen wir die Fälle, die in der Praxis am häufigsten vorkommen. Die alten Deklarationen sehen so aus (siehe Listing 1).

Private Declare Function GetTickCount Lib "kernel32" () As Long
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, _
    ByVal lpWindowName As String) As Long
Private Declare Function GetWindowRect Lib "user32" (ByVal hWnd As Long, lpRect As RECT) As Long
Private Declare Function FlashWindowEx Lib "user32" (pfwi As FLASHWINFO) As Long

Listing 1: Die vier Deklarationen im alten Stil

Schritt 1: PtrSafe ergänzen

Der erste Schritt ist mechanisch: Vor Function oder Sub kommt in jeder Declare-Zeile das Schlüsselwort PtrSafe. Das reicht allein aber nicht, und hier liegt die eigentliche Gefahr. Microsoft zeigt es an der Funktion GetActiveWindow (siehe Listing 2).

Declare Function GetActiveWindow Lib "user32" () As Long
Declare PtrSafe Function GetActiveWindow Lib "user32" () As Long
Declare PtrSafe Function GetActiveWindow Lib "user32" () As LongPtr

Listing 2: Drei Stufen derselben Deklaration (Beispiel aus der Microsoft-Dokumentation)

Die erste Zeile ist der alte Stand. Unter 64 Bit meldet der Compiler, dass das Schlüsselwort PtrSafe fehlt. Die zweite Zeile kompiliert dort ohne Fehler, liefert aber einen abgeschnittenen Wert zurück, denn PtrSafe sagt dem Compiler nur, dass Sie die Zeile für 64 Bit gedacht haben.

Erst die dritte Zeile ist richtig. Das ist die gefährlichste Stufe: Der Compiler schweigt, und der Fehler zeigt sich erst zur Laufzeit.

Schritt 2: Handles und Zeiger als LongPtr

Jetzt fragen wir bei jedem Parameter und jedem Rückgabewert: Ist das eine Zahl oder ein Handle beziehungsweise ein Zeiger? Die Dokumentation der API-Funktion verrät es. Typen wie HWND, HANDLE oder LPVOID werden zu LongPtr. Typen wie DWORD oder int bleiben Long.

Daraus folgt für unsere vier Funktionen: GetTickCount bekommt nur PtrSafe, denn die Rückgabe ist eine Zahl. Bei FindWindow wird die Rückgabe zu LongPtr, bei GetWindowRect der Parameter hWnd.

Der Rückgabewert von GetWindowRect ist ein Wahrheitswert und bleibt Long. Die neue Fassung zeigt Listing 3.

Private Declare PtrSafe Function GetTickCount Lib "kernel32" () As Long
Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, _
    ByVal lpWindowName As String) As LongPtr
Private Declare PtrSafe Function GetWindowRect Lib "user32" (ByVal hWnd As LongPtr, lpRect As RECT) As Long
Private Declare PtrSafe Function FlashWindowEx Lib "user32" (pfwi As FLASHWINFO) As Long

Listing 3: Die angepassten Deklarationen

Damit ist die Deklaration fertig, aber nicht der Aufruf. Die Variable, die das Handle aufnimmt, muss ebenfalls LongPtr sein. Die Funktion FensterBreite im Modul mdlApiNeu ermittelt die Breite des Access-Fensters:

Public Function FensterBreite() As Long
    Dim hWnd As LongPtr
    Dim udtRect As RECT
    hWnd = FindWindow("OMain", vbNullString)
    GetWindowRect hWnd, udtRect
    FensterBreite = udtRect.Right - udtRect.Left
End Function

Der Klassenname OMain steht für das Hauptfenster von Access. Wer die Variable hWnd weiter als Long deklariert, erhält in 32-Bit-Access kein Problem und merkt den Fehler erst, wenn die Anwendung auf 64 Bit läuft. Genau das ist der heimtückischste Stolperstein bei dieser Umstellung. Rufen wir die Funktion im Direktbereich auf, erhalten wir die Breite des Fensters in Pixeln: Debug.Print FensterBreite().

Strukturen mit Handles

Schwieriger wird es bei Strukturen. Die Struktur RECT besteht nur aus vier Long-Werten und bleibt unverändert. Die Struktur FLASHWINFO für FlashWindowEx enthält dagegen ein Fensterhandle. Das Element hWnd muss deshalb LongPtr sein:

Private Type FLASHWINFO
    cbSize As Long
    hWnd As LongPtr
    dwFlags As Long
    uCount As Long
    dwTimeout As Long
End Type

Das Element cbSize verlangt die Größe der Struktur in Byte. Hier steckt eine Falle: Mit einem LongPtr im Inneren kann sich die Größe der Struktur zwischen 32 und 64 Bit ändern. Eine feste Zahl wäre falsch.

Deshalb ermitteln wir die Größe mit Len (udtFlash.cbSize = Len(udtFlash)). In der Prozedur FensterBlinken ist das bereits so umgesetzt. Dasselbe gilt für jede andere Struktur, die Handles oder Zeiger enthält.

Weitere Beispiele aus der Praxis

Das Modul mdlApiBeispiele enthält fünf weitere Deklarationen, an denen sich die Regeln gut erkennen lassen (siehe Listing 4).

Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Private Declare PtrSafe Function GetUserName Lib "advapi32.dll" _
    Alias "GetUserNameA" (ByVal lpBuffer As String, nSize As Long) As Long
Private Declare PtrSafe Function GetModuleHandle Lib "kernel32" _
    Alias "GetModuleHandleA" (ByVal lpModuleName As String) As LongPtr
Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, _
    ByVal Length As LongPtr)
Private Declare PtrSafe Function EnumWindows Lib "user32" _
    (ByVal lpEnumFunc As LongPtr, ByVal lParam As LongPtr) As Long
Private Declare PtrSafe Function IsWindowVisible Lib "user32" (ByVal hWnd As LongPtr) As Long

Listing 4: Weitere angepasste Deklarationen

Bei Sleep ist nichts zu tun außer PtrSafe, denn die Wartezeit in Millisekunden ist eine Zahl. Auch bei GetUserName bleibt alles Long: Der Puffer ist ein String, und nSize wird als Referenz übergeben, was automatisch die Adresse der Variablen liefert. Das ist ein wichtiger Punkt. Nicht jeder Parameter, der in C ein Zeiger ist, wird in VBA zu LongPtr.

Access im Unternehmen

Unser exklusives Angebot für Dich!

Access im Unternehmen
13,25 € im Monat*

(Gilt für den Abschluss eines Jahres-Abonnements im ersten Jahr, danach 189,-/Jahr)

Hier geht’s weiter →

Die ersten 4 Wochen kostenlos testen – voller Zugriff auf alle Artikel, vollständigen Code und Beispieldatenbanken. Kein Risiko: Wenn es nicht passt, kündigst Du einfach innerhalb der ersten vier Wochen.

PayPal VISA Mastercard SEPA
Kostenlos & unverbindlich

Hast Du eine konkrete Frage zu Deiner eigenen Access-Anwendung?

Vielleicht stellt Deine Anwendung Dich vor eine Herausforderung, zu der Du bisher keine Lösung findest. Schlechte Performance, kein ausreichender Zugriffsschutz, Du bist unsicher über Dein Datenmodell oder Dein Code liefert unerklärliche Fehler?

In unserem kostenlosen Access-Audit schaut sich André Minhorst persönlich gemeinsam mit Dir Deine Lösung per Zoom an – und zeigt Dir, wo Datenmodell, VBA-Code, Ergonomie und Sicherheit Optimierungspotenzial bieten.

Jetzt kostenloses Access-Audit anfordern →