Zur Hauptseite ... Zum Onlinearchiv ... Zum Abonnement ... Zum Newsletter ... Zu den Tools ... Zum Impressum ... Zum Login ...

Gedrucktes Heft

Diesen Beitrag finden Sie in Ausgabe 4/2018.

Unser Angebot für Sie!

Lesen Sie diesen Beitrag und 500 andere sofort im Onlinearchiv, und erhalten Sie alle zwei Monate brandheißes Access-Know-how auf 72 gedruckten Seiten! Plus attraktive Präsente, zum Beispiel das bald erscheinende Buch 'Access 2010 - Das Grundlagenbuch für Entwickler'!

Diesen Beitrag twittern

TreeView 64bit ist da – Anwendungen umrüsten

Es ist geschehen: Jahre nach der Einführung der ersten 64bit-Version von Microsoft Access (wer erinnert sich noch?) hat Microsoft die 64bit-Version auch mit einer passenden Version der Bibliothek MSCOMCTL.ocx versehen. Das Fehlen von TreeView, ListView und Co. war bisher einer der Hauptgründe, nicht auf 64bit zu wechseln. Derweil gehen dennoch viele Unternehmen diesen Schritt – meist vermutlich einfach, um eine »modernere« Version von Access zu nutzen. Hier und da vernimmt man allerdings auch Stimmen, dass die 64bit-Version an verschiedenen Stellen Probleme vermeidet, die unter der 32bit-Version aufgetreten sind. In diesem Beitrag schauen wir uns an, was beim Einsatz von Steuerelementen der MSCOMCTL.ocx-Bibliothek zu beachten ist und wie Sie 32bit-Anwendungen fit machen für die 64bit-Version.

Als ich von einem Entwicklerkollegen hörte, dass es nunmehr endlich eine 64bit-Version der MSCOMCTL.ocx gibt, habe ich alles andere liegen lassen und eine virtuelle Maschine mit Office 2016 in der 64bit-Version ausgestattet. Anmerkung an dieser Stelle: Dies geschah mit einer Office-Version eines Office 365-Abonnements.

Experimente

Unter der 64bit-Version von Access habe ich also eine neue Datenbank erstellt, dieser ein Formular hinzugefügt und dann tatsächlich in der Liste der verfügbaren ActiveX-Steuerelemente das TreeView-Steuerelement gefunden (siehe Bild 1).

Einfügen des TreeView-Steuerelements unter 64bit

Bild 1: Einfügen des TreeView-Steuerelements unter 64bit

Das so hinzugefügte Steuer­element hat den Namen ctlTreeView bekommen. Um es zu testen, habe ich folgenden Code zum Klassenmodul des Formulars hinzugefügt:

Private Sub Form_Load()
     Dim objTreeView As MSComctlLib.TreeView
     Set objTreeView = Me!ctlTreeView.Object
     objTreeView.Nodes.Add , , , "Test"
End Sub

Das Ergebnis sehen Sie in Bild 2: Das TreeView wird angezeigt und funktioniert wie gewünscht.

Das TreeView-Steuerelement funktioniert!

Bild 2: Das TreeView-Steuerelement funktioniert!

TreeView: Von 32bit zu 64bit und zurück

Wir haben dann die unter der 64bit-Version von Access erstellte Datenbank auf ein System mit Access in der 32bit-Version von Access kopiert und gestartet. Das TreeView-Steuerelement dieser mit der 64bit-Version erstellten Datei funktioniert auch unter der 32bit-Version.

Danach haben wir die gleiche Datei unter Access in der 32bit-Version erstellt und diese dann auf das mit der 64bit-Version ausgestattete System kopiert. Auch hier gelingt die Anzeige des TreeView-Steuerelements ohne Probleme.

Anwendung umrüsten

Damit sind die Voraussetzungen gegeben, einen genaueren Blick auf die Umrüstung einer Anwendung auf Basis von 32bit-Access auf die 64bit-Version zu werfen – oder gleich auf eine Umrüstung, die mit beiden Versionen kompatibel ist. Dazu nehmen wir uns als Beispiel die Ribbon-Admin-Anwendung vor, die eine Menge API-Code enthält – und genau dieser ist in der Regel nicht kompatibel mit 64bit-Systemen, wenn er unter 32bit-Access geschrieben wurde.

Probleme im Code

Die offensichtlichen Probleme tauchen schon auf, wenn Sie die Moduls mit API-Deklarationen einer 32bit-Datenbank öffnen – die API-Funktionen werden komplett rot markiert, was kein gutes Zeichen ist (siehe Bild 3).

Fehlerhaft markierte API-Funktionen

Bild 3: Fehlerhaft markierte API-Funktionen

Der Menübefehl Debuggen|Kompilieren liefert dann genauere Informationen in Form einer Meldung (siehe Bild 4). Die Erläuterung ist eindeutig – wir sollen die Deklarationen mit dem Schlüsselwort PtrSafe erweitern. Nur wo? Das ist nach kurzem Experimentieren beantwortet – nämlich zwischen Declare und Function/Sub:

Meldung beim Versuch, das Projekt zu debuggen

Bild 4: Meldung beim Versuch, das Projekt zu debuggen

Private Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, _
ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long

Dies manuell zu erledigen ist natürlich eine langweilige Aufgabe. Also ziehen wir den Suchen/Ersetzen-Dialog zur Unterstützung heran und geben im Suchen nach-Textfeld den Ausdruck Declare Function und im Ersetzen durch-Textfeld den Ausdruck Declare PtrSafe Function (siehe Bild 5).

Suchen und Ersetzen

Bild 5: Suchen und Ersetzen

Der folgende Versuch, die Anwendung zu debuggen, liefert die Declare Sub-Deklarationen als fehlerhafte Zeilen. Also führen wir den Suchen/Ersetzen-Vorgang nochmals durch, nur diesmal mit dem Suchbegriff Declare Sub und dem Ersetzungsausdruck Declare PrtSafe Sub.

Danach versuchen wir erneut, die Anwendung zu kompilieren. Nun tauchen Fehler wie der in Bild 6 auf. Was ist hier das Problem?

Unverträgliche Typen

Bild 6: Unverträgliche Typen

Um dies herauszufinden, schauen wir uns über den Kontextmenü-Eintrag Definition die Definition des markierten Elements an, hier der Funktion VarPtr (Bild 7). Diese erwartet als Parameter einen Wert des Typs Any und liefert einen Wert des Typs LongPtr zurück.

Prüfen der Datentypen von Parameter und Rückgabewert

Bild 7: Prüfen der Datentypen von Parameter und Rückgabewert

Der Datentyp LongPtr ist 64bit-kompatibel und umfasst einen Wertebereich von 2 hoch 64, während der 32bit-kompatible Datentyp Long nur 2 hoch 32 Werte umfasst.

Warum aber macht die Funktion VarPtr nun Probleme? Dazu schauen wir uns die Deklaration der API-Funktion StringFromGUID2 an, der wir das Ergebnis dieser Funktion als Parameter übergeben. Diese sieht wie folgt aus:

Private Declare PtrSafe Function StringFromGUID2 Lib "ole32.dll" (rGUID As Any, _
ByVal lpstrClsId As Long, ByVal cbMax As Long) As Long

Wir sehen, dass der zweite Parameter dieser Funktion in der Deklaration den Datentyp Long hat. Im Aufruf der Funktion übergeben wir aber den Datentyp LongPtr. Warum ist das nun ein Fehler, obwohl es ja zuvor unter der 32bit-Version von Access funktioniert hat? Hier sind LongPtr-Variablen mit Long-Variablen kompatibel. Unter der 64bit-Version ist das allerdings nicht der Fall – hier könnten ja die LongPtr-Variablen mit Werten gefüllt sein, die nicht mehr in den Wertebereich einer Long-Variablen passen.

Also müssen wir überall, wo eine LongPtr-Variable vorkommt, die in einer API-Deklaration als Parameter verwendet wird, in der API-Deklaration auch von Long auf LongPtr umstellen. Also ändern wir hier den Datentyp des Parameters lpstrClsId von Long zu LongPtr:

Private Declare PtrSafe Function StringFromGUID2 Lib "ole32.dll" (rGUID As Any, _
ByVal lpstrClsId As LongPtr, ByVal cbMax As Long) As Long

Auf diese Weise schreiten wir weiter voran und ersetzen die Parameter des Datentyps Long durch den Datentyp LongPtr, wo es beim Kompilieren zu Problemen kommt.

Das kann beispielsweise auch einmal in einer Variablen auf Basis eines Type-Elements geschehen (siehe Bild 8). Die Ursache des Problems liegt in den folgenden beiden Type-Deklarationen:

Noch mehr unverträgliche Typen, diesmal in einem Type

Bild 8: Noch mehr unverträgliche Typen, diesmal in einem Type

Private Type EncoderParameters
     count As Long
     Parameter As EncoderParameter
End Type
Private Type EncoderParameter
     UUID As GUID
     NumberOfValues As Long
     type As Long
     Value As Long
End Type

Hier müssen wir zumindest einmal den Datentyp des Elements Value von Long auf LongPtr ändern.

Interessanterweise kommen wir nach wenigen Änderungen schon an dem Punkt an, wo das Kompilieren ohne Probleme gelingt.

Also starten wir die Anwendung, die gleich zu Beginn beispielsweise einige der GDI-API-Funktionen nutzt, um die Bilder für Kontextmenüs und TreeView bereitzustellen. Leider stürzt Access dabei komplett ab – und das, obwohl wir doch erfolgreich kompiliert haben.

Also bleibt uns nichts über, als schrittweise durch die beim Starten ausgeführten Anweisungen der Anwendung zu laufen und zu schauen, welche der Anweisungen den Fehler auslöst, der zum Absturz führt.

Hier hat sich folgende Vorgehensweise bewährt, wenn viel Code durchlaufen wird: Sie durchlaufen immer ein paar Anweisungen und setzen dann, bevor Sie fortfahren, einen fixen Haltepunkt in Form der Anweisung Stop. Danach das Speichern nicht vergessen, denn wenn sie nun weiterlaufen und die den Absturz auslösende Anweisung ausführen, wird eine nicht gespeicherte Änderung am Code natürlich auch wieder verworfen. Am besten fügen Sie also immer eine Stop-Anweisung ein, wenn die nächsten Anweisungen den Aufruf einer oder mehrerer API-Funktionen enthalten.

Wenn Sie dann auf den Fehler stoßen, können Sie beim nächsten Start gleich schon einmal einige Stop-Anweisungen löschen, die gegebenenfalls schon ohne Fehler durchlaufen werden konnten.

In diesem Fall stellte sich heraus, dass der Aufruf der Funktion GdipCreateHBITMAPFromBitmap zum Absturz führt. Also schauen wir uns an, welche Parameter diese enthält und welche Datentypen diese haben. Dann gleichen wir diese mit den Datentypen der übergebenen Variablen ab. Die Definition der API-Funktion sieht so aus:

Private Declare PtrSafe Function GdipCreateHBITMAPFromBitmap Lib "gdiplus" (ByVal bitmap As Long, _
hbmReturn As Long, ByVal background As Long) As Long

Wir haben also drei Long-Parameter und auch einen Rückgabewert vom Typ Long. Der Aufruf lautet wie folgt:

GdipCreateHBITMAPFromBitmap lBitmap, hBmp, 0&

lBitmap hat den Datentyp Long, hBmp hat den Datentyp Long. Wo also ist das Problem? lBitmap hat aktuell den Wert 2.064.200.912, hBmp den Wert 0.

Stellen wir versuchsweise die Datentypen der beiden Variablen um:

Dim lBitmap As LongPtr
Dim hBmp As LongPtr

Auch in der API-Deklaration ändern wir die Datentypen der entsprechenden Parameter:

Private Declare PtrSafe Function GdipCreateHBITMAPFromBitmap Lib "gdiplus" (ByVal bitmap As LongPtr, _
hbmReturn As LongPtr, ByVal background As Long) As Long

Damit ist alles gut? Weit gefehlt. Natürlich wird die Funktion GdipCreateHBITMAPFromBitmap auch noch von anderen Stellen aus aufgerufen, und auch dort müssen wir die Datentypen ändern. Dummerweise werden die Variablen, deren Datentypen wir nun ändern, auch noch als Parameter für den Aufruf weiterer API-Funktionen genutzt. Also haben wir eine Menge Arbeit vor uns und hangeln uns nun durch die Änderungen von Datentypen von Variablen und Parametern in den API-Deklarationen.

Nach ein paar Änderungen von Long in LongPtr wird die Kompilierung dann anstandslos durchgeführt und wir können erneut einen Start der Anwendung wagen. Im Falle der API-Funktion GdipCreateHBITMAPFromBitmap gelingt die Ausführung nun. Also lassen wir die Anwendung nun weiterlaufen und setzen hier und da Stop-Anweisungen, um uns an den nächsten Absturz heranzutasten.

Schließlich gelingt es dann, die Anwendung komplett zum Laufen zu bringen. Allerdings sollte man dann auch noch einmal alle Aspekte der Anwendung testen.

32bit- und 64bit-kompatibel?

Die interessante Frage ist nun, ob die für 64bit vorbereitete Anwendung auch noch unter 32bit läuft. Also kopieren wir diese Anwendung wieder auf ein 32bit-System und probieren es dort aus.

Interessanterweise funktioniert unsere Anwendung mit der Ersetzung der Datentypen Long durch LongPtr und die Erweiterung der API-Deklaration um das Schlüsselwort PtrSafe. Auch das Kompilieren funktioniert ohne Probleme.

Dies gilt allerdings nur für die Verwendung von VBA ab Version 7. Da dies allerdings auch schon mindestens sechs Jahre alt ist, behandeln wir an dieser Stelle das Thema Kompatilität mit VBA 6 so knapp wie möglich.

Es gibt sogenannte Kompilierungskonstanten sowie beispielsweise If...Then...Else-Konstrukte, die nur beim Kompilieren ausgeführt werden. Diese markieren Sie durch ein vorangestelltes Raute-Zeichen (#). Wenn Sie eine Anweisung also kompatibel für VBA 6 und VBA 7 bereitstellen wollen, verwenden Sie etwa die folgende If...Then...Else-Bedingung:

#If VBA7 Then
     '64bit-Anweisungen
#Else
     '32bit-Anweisungen
#End If

Wenn Sie also etwa eine API-Deklaration für beide VBA-Versionen bereitstellen wollen, gelingt dies etwa so:

#If VBA7 Then
     Private Declare PtrSafe Function _
         CreateStreamOnHGlobal Lib "ole32" (ByVal hGlobal _
         As LongPtr, ByVal fDeleteOnRelease As Long, _
         ByRef ppstm As Any) As Long
#Else
     Private Declare Function _
         CreateStreamOnHGlobal Lib "ole32" (ByVal hGlobal _
         As Long, ByVal fDeleteOnRelease As Long, _
         ByRef ppstm As Any) As Long
#End If

Bei der Variablendeklaration erledigen Sie das auf die gleiche Weise:

#If VBA7 Then
     Dim lBitmap As LongPtr
#Else
     Dim lBitmap As Long
#End If

Versionen identifizieren

Wenn Sie nicht auf Anhieb wissen, welche Version von Access oder VBA Sie verwenden, können Sie dies schnell testen. Um die Access-Version zu ermitteln und speziell, ob Sie die 32bit- oder 64bit-Version nutzen, betätigen Sie den Ribbon-Tab Datei|Konto und klicken dort auf die Schaltfläche Info zu Access. Der nun erscheinende Dialog aus Bild 9 liefert die Access-Version samt Hinweis auf die 32bit- oder 64bit-Version.

Access-Version ermitteln

Bild 9: Access-Version ermitteln

Um die Version von VBA zu ermitteln, öffnen Sie den VBA-Editor und betätigen dort den Menü-Eintrag ?|Microsoft Visual Basic for Applications-Info... (siehe Bild 10).

VBA-Version ermitteln

Bild 10: VBA-Version ermitteln

Kompilieren

Wenn Sie eine .accde-Version Ihrer Anwendung erstellen wollen, müssen Sie diese gegen die jeweilige Version kompilieren – also entweder 32bit oder 64bit. Sie müssen also jeweils zwei Versionen für die unterschiedlichen Access-Versionen bereithalten.

Zusammenfassung und Ausblick

Das es nun eine Version der Bibliothek MSCOMCTL.ocx für die 64bit-Version von Access gibt, ist eine gute Nachricht. Endlich müssen Entwickler ihren Auftraggeber nicht mehr erklären, warum es keine gute Idee ist, die 64bit-Version von Office zu installieren.

© 2003-2018 André Minhorst Alle Rechte vorbehalten.