Jeżeli chcemy korzystać w 64 bitowej wersji Microsoft Office z rozwiązań opartych na funkcjach API
(ang.) Application Programming Interface, to musimy zaktualizować wszystkie
istniejące 32 bitowe odwołania do wskaźników
(address pointers) i uchwytów
(handlers)
odwołujących się do określonych miejsc w pamięci fizycznej, do 64 bitowych
odwołań tak, by pracowały prawidłowo w wersji 64 bitowej.
Dla wersji 32 lub 64 bitowej wersji rozmiar wskaźnika lub uchwytu (w bajtach) będzie różny. W aplikacjach 64 bitowych do przechowywania wskaźników i uchwytów używana jest 64 bitowa liczba całkowita typu LongLong lub specjalny typ LongPtr. Typ ten w zależności od wersji jest liczbą typu LongLong w 64 bitowej wersji pakietu Office (VBA7) i 4 bajtową liczbą typu Long w wersji 32 bitowej pakietu Office (VBA7).
• Visual Basic for Applications 7.0
Wraz z wprowadzeniem 64 bitowej wersji pakietu MS Office, wprowadzona została nowa wersja Visual Basic for Applications 7.0 (VBA 7). Podstawowe zmiany to 64-bitowe uchwyty (handlers) i wskaźniki (address pointers). W związku z tym prowadzono nowe typy danych, nowy znak deklaracji zmiennych (^), nowe funkcje konwersji danych, nowe, uniwersalne funkcje zwracające wskaźniki VarPtr, ObjPtr, StrPtr, stałą vbLongLong = 20 określającą typ zmiennej LongLong oraz dwie stałe kompilacji warunkowej VBA7 i Win64.
Poniżej tabelka opisująca dokładniej nowości w VBA 7
Typ | Pozycja | Opis |
---|---|---|
Kwalifikator | PtrSafe | Kwalifikator ten określa, że deklaracja jest zgodna z 64 bitowymi środowiskami programistycznymi. Kwalifikator ten jest obowiązkowy w systemach 64 bitowych. |
Typ danych | LongPtr | 8 bajtowa liczba (LongLong) w 64 bitowej wersji MS Office i 4 bajtowa (Long) w wersji 32 bitowej wersji VBA 7 pakietu MS Office. Typ ten jest zalecany przy deklarowaniu wskaźników i uchwytów w obu środowiskach (zarówno w 32 bitowym jak i 64 bitowym). Typ ten jest obsługiwany tylko w VBA 7 (zarówno w 32 bitowym jak i 64 bitowym środowisku). Korzystanie z typu LongPtr umożliwia pisanie przenośnego kodu, który będzie dział zarówno 32 jak i 64-bitowym środowisku VBA7. |
Typ danych | LongLong | 8 bajtowy typ danych. Przechowuje liczby w przedziale wartości od -9.223.372.036.854.775.808 do 9.223.372.036.854.775.807. Typ ten jest dostępny tylko w 64 bitowym środowisku (w 64 bitowej wersji pakietu MS Office.) |
Znak deklaracji | ^ | Znak deklaracji zmiennej typu LongLong. |
Operator konwersji | CLngPtr | Konwertuje przekazywane wyrażenie do typu LongPtr. |
Operator konwersji | CLngLng | Konwertuje przekazywane wyrażenie do typu LongLong. |
Funkcja | VarPtr | Zwraca wskaźnik typu LongPtr w wersji 64 bitowych do przekazanej zmiennej, a typu Long w 32 bitowym środowisku. |
Funkcja | ObjPtr | Zwraca wskaźnik typu LongPtr w wersji 64 bitowych do przekazanego obiektu, a typu Long w 32 bitowym środowisku. |
Funkcja | StrPtr | Zwraca wskaźnik typu LongPtr w wersji 64 bitowych do przekazanego ciągu znaków, a typu Long w 32 bitowym środowisku. |
Stała | vbLongLong | = 20 stała określająca typ zmiennej LongLong (tylko w 64 bitowym środowisku). |
Pakiet Microsoft Office w wersji 2010+ lub nowszej zawiera dwie nowe stałe kompilacji warunkowej: | ||
Stała kompilacji | VBA7 | Umożliwia sprawdzenie, czy uruchomiona aplikacja używa nowego VBA 7 (64 bitowego lub 32 bitowego),
czy poprzedniej 32 bitowej wersji VBA (6.0). Zwracana wartość VBA7 = TRUE informuje, że uruchomiona została aplikacja pakietu Office w wersji 2010+ lub nowszej. |
Stała kompilacji | Win64 | Umożliwia sprawdzenie, czy uruchomiona została 32 bitowa, czy 64 bitowa wersja pakietu Microsoft Office. |
Stałe kompilacji warunkowej VBA7 i Win64.
' Sprawdź wersję VBA #If VBA7 Then ' 64-bitowe środowisko VBA7. #If Win64 Then MsgBox "Kod VBA 7 uruchomiony w 64-bitowej wersji MS Office +2010" ' 32-bitowe środowisko VBA7. #Else MsgBox "Kod VBA 7 uruchomiony w 32-bitowej wersji MS Office +2010" #End If ' To nie jest środowisko VBA 7. #Else MsgBox "Kod VBA uruchomiony w wersji VBA 6 lub niższej." #End If
Windows API i 64 bitowe środowisko VBA 7.
Atrybut PtrSafe - deklarowania procedur i funkcji z zewnętrznych bibliotek DLL w 64-bitowym VBA 7.
W 64 bitowym środowisku VBA7 za każdą instrukcją Declare tworzącą odwołanie do zewnętrznej procedury w bibliotece
dołączanej dynamicznie (DLL) należy umieścić atrybut (kwalifikator, słowo kluczowe) PtrSafe
i zmienić 32 bitowe odwołania do wskaźników i uchwytów na zgodne z typem LongPtr,
a 64-bitowym argumentom przypisać nowy typ danych LongLong.
Atrybut PtrSafe nie jest wymagany w 32 bitowym środowisku VBA7, co zapewnia wsteczną kompatybilność z VBA w wersji 6.
Brak atrybutu PtrSafe w 64 bitowym środowisku spowoduje błąd kompilacji.
' Sprawdź wersję VBA #If VBA7 Then ' Deklaracja dla 32 bitowej lub 64-bitowej wersji MS Office 2010 lub wyższej Declare PtrSafe Sub... #Else ' Deklaracja dla 32 bitowej wersji MS Office 2007 lub niższej Declare Sub... #End If
Popatrzmy jak zaleca deklarowanie funkcji API w środowisku VBA7 Microsoft. W oparciu o przykład stronie Working with Different Versions of VBA utworzyy funkcję zwracającą położenie i wymiary okna aplikacji Excel.
' Struktura przechowująca dane o położeniu okna Type RECT Left As Long Top As Long Right As Long Bottom As Long End Type ' Sprawdź, która wersja VBA jest używana #If VBA7 Then ' Funkcja API szukająca okno (po nazwie klasy, lub tytule okna. Declare PtrSafe Function FindWindow Lib "user32" _ Alias "FindWindowA" ( _ ByVal lpClassName As String, _ ByVal lpWindowName As String) As LongPtr ' Funkcja API zwracające w strukturze lpRect dane o położeniu okna. Declare PtrSafe Function GetWindowRect Lib "user32" ( _ ByVal hwnd As LongPtr, _ lpRect As RECT) As Long #Else ' Funkcja API szukająca okno (po nazwie klasy, lub tytule okna). Declare Function FindWindow Lib "user32" _ Alias "FindWindowA" ( _ ByVal lpClassName As String, _ ByVal lpWindowName As String) As Long ' Funkcja API zwracające w strukturze lpRect dane o położeniu okna. Declare Function GetWindowRect Lib "user32" ( _ ByVal hwnd As Long, _ lpRect As RECT) As Long #End If
Jak na razie wszystko jest zrozumiałe. Struktura RECT i funkcje API zostały zadeklarowane. Poniżej krótkie wyjaśnienie dotyczące zadeklarowanych funkcji:
- funkcja FindWindow (lpClassName, lpWindowName) w środowisku VBA 7 zwraca wartość liczbową LongPtr (typu LongLong w 64 bitowym środowisku i typu Long w środowisku 32 bitowym. W 32 bitowym środowisku VBA 6 zwraca wartość typu Long.
- funkcja GetWindowRect (hwnd, lpRect) w środowisku VBA 7 pobiera uchwyt hwnd jako wartość liczbową
LongPtr (typu LongLong w 64 bitowym środowisku i typu Long w środowisku
32 bitowym. W 32 bitowym środowisku VBA 6 pobiera uchwyt hwnd typu Long.
W obu środowiskach VBA funkcja zwraca ByRef w argumencie lpRect strukturę RECT oraz informacje o powodzeniu typu Long
Sub DisplayExcelWindowSize() Dim hwnd As Long, uRect As RECT ' Pobierz uchwyt głównego okna Excel. hwnd = FindWindow("XLMAIN", Application.Caption) ' Pobierz dane o położeniu okna do struktury UDT. GetWindowRect hwnd, uRect ' Pokaż położenie i wymiary okna. MsgBox "The Excel window has these dimensions:" & _ vbCrLf & " Left: " & uRect.Left & _ vbCrLf & " Right: " & uRect.Right & _ vbCrLf & " Top: " & uRect.Top & _ vbCrLf & " Bottom: " & uRect.Bottom & _ vbCrLf & " Width: " & (uRect.Right - uRect.Left) & _ vbCrLf & " Height: " & (uRect.Bottom - uRect.Top) End Sub
Po przeglądnięciu kodu funkcji DisplayExcelWindowSize mam pewne wątpliwości, czy dobrze wszystko zrozumiałem ? Załóżmy, że uruchamiamy funkcję w 64 bitowym środowisku Excel 2010, w którym stała VBA7 = TRUE. Moim zdaniem deklaracja uchwytu hwnd w omawianej funkcji jest niepoprawna i powoduje kolejne błędy w dalszej części kodu :
- Dim hwnd As Long - z opisów powyżej wynika, że uchwyty i wskaźniki w 64 bitowym środowisku VBA 7 są typu LongLong. I zostały zadeklarowane jako liczba typu LongPtr, która w tym środowisku jest właśnie typu LongLong.
- hwnd = FindWindow(...) - to samo, uchwyty i wskaźniki w 64 bitowym środowisku VBA 7
są typu LongLong.
I tak została zadeklarowana zwracana wartość: Declare PtrSafe Function FindWindow(...) As LongPtr - GetWindowRect hwnd, uRect - i tutaj podobnie, przekazywany uchwyt hwnd powinien być typu LongLong
I tak została zadeklarowana funkcja:
Declare PtrSafe Function GetWindowRect (ByVal hwnd As LongPtr, ...) As Long
Wklejenie kodu do modułu aplikacji Excel i próba jego skompilowania, kończy się komunikatem o błędzie kompilacji.
Podświetlona zostaje linijka w której występuje przypisanie do błędnie zadeklarowanego uchwytu hwnd, który powinien być w środowisku VBA7 zadeklarowany jako LongPtr, a w środowisku VBA6 jako Long.
Sub DisplayExcelWindowSize() ' poprawiona deklaracja uchwytu okna #If VBA7 Then ' w 64 bitowym środowisku VBA 7 uchwyt hwnd będzie typu LongLong. ' w 32 bitowym środowisku VBA 7 uchwyt hwnd będzie typu Long. Dim hwnd As LongPtr #Else ' w 32 bitowym środowisku VBA 6 uchwyt hwnd jest zawsze typu Long. Dim hwnd As Long #End If Dim uRect As RECT ' Pobierz uchwyt głównego okna Excel. hwnd = FindWindow("XLMAIN", Application.Caption) ' Pobierz dane o położeniu okna do struktury uRect. GetWindowRect hwnd, uRect ' Pokaż położenie i wymiary okna. MsgBox "The Excel window has these dimensions:" & _ vbCrLf & " Left: " & uRect.Left & _ vbCrLf & " Right: " & uRect.Right & _ vbCrLf & " Top: " & uRect.Top & _ vbCrLf & " Bottom: " & uRect.Bottom & _ vbCrLf & " Width: " & (uRect.Right - uRect.Left) & _ vbCrLf & " Height: " & (uRect.Bottom - uRect.Top) End Sub
Teraz kod się kompiluje i działa prawidłowo.
Położenie i wymiary okna Excel.
W przypadku tak prostej funkcji oraz gdy uchwyt hwnd nie jest potrzebny w dalszej procedury możemy, (kosztem czytelności i wbrew niektórym purystom), można znacznie uprościć kod poprzez bezpośrednie przekazanie zwracanego uchwytu przez funkcję FindWindow(...) do funkcji GetWindowRect(...)
Sub DisplayExcelWindowSize() Dim uRect As RECT ' Pobierz dane o położeniu okna do struktury uRect. GetWindowRect FindWindow("XLMAIN", Application.Caption), uRect ' Pokaż położenie i wymiary okna. MsgBox "The Excel window has these dimensions:" & _ vbCrLf & " Left: " & uRect.Left & _ vbCrLf & " Right: " & uRect.Right & _ vbCrLf & " Top: " & uRect.Top & _ vbCrLf & " Bottom: " & uRect.Bottom & _ vbCrLf & " Width: " & (uRect.Right - uRect.Left) & _ vbCrLf & " Height: " & (uRect.Bottom - uRect.Top) End Sub