niedziela, 10 stycznia 2016

• Wywołanie funkcje API w 64-bitowym VBA 7.0

Z postu z dnia 25 grudnia 2015 r. • VBA 7.0 i MS Access 2010+  „mniej więcej wiemy” (co nie znaczy, że mniej niż więcej , że w pakiecie Office 2010+ wprowadzono nową wersję Microsoft Visual Basic for Applications (VBA) oznaczoną jako VBA 7.0. Wersja ta poprawia wydajność aplikacji w 64 bitowych środowiskach. Równocześnie umożliwia tworzenie aplikacji zgodnych z wcześniejszymi wersjami pakietu Office (2007 i wersje niższe). Aby umożliwić przystosowanie kodu do konkretnej wersji pakietu Office wprowadzono dwie nowe stałe kompilacji warunkowej:

VBA7
• pozwala ustalić, czy używane jest nowe środowisko VBA 7.0, czy też starsze wersje VBA.
Win64
• umożliwia sprawdzenie, czy środowisko VBA jest 64 bitowe, czy 32 bitowe.

oraz nowy kwalifikator:

PtrSafe
• używany do deklarowania procedur i funkcji z zewnętrznych bibliotek DLL w VBA 7 (zawsze w 64 bitowym środowisku VBA7, opcjonalnie w 32 bitowym)

• Jak zadeklarować i uruchomić funkcje API w MS Access 2010+

API - Application Programming Interface jest to zbiór funkcji i procedur znajdujących się w plikach dll (Dynamic Link Library). Inaczej mówiąc są to biblioteki dołączane dynamicznie.

Podstawowe biblioteki w systemie Windows to:
• USER32.dll - funkcje zarządzania środowiskiem Windows
• KERNEL32.dll - funkcje zarządzania funkcjami systemu operacyjnego
• GDI32.dll - Graphics Device Interface - funkcje zarządzające wyprowadzaniem danych na zewnętrzne urządzenia.

Jak zadeklarować i wywołać funkcje API, tak by prawidłowo działały zarówno w 32-bitowym, jak i 64-bitowym środowisku VBA 7.0 oraz w wersjach niższych VBA możemy dowiedzieć się na portalu MSDN (Microsoft Developer Network), na stronie: Working with VBA in the 32-bit and 64-bit Versions of Office 2010. Skorzystajmy więc z rad Microsoftu:

Applies to: Microsoft Office 2010
Published: May 2010
Provided by: Frank Rice, Microsoft Corporation

Jak używać stałej kompilacji VBA7 dowiemy się na podstawie przykładowej funkcji DisplayExcelWindowSize zwracającej położenie i wymiary okna głównego programu Excel 2010. W tym celu musimy zadeklarować strukturę RECT oraz dwie funkcje API:

• funkcję FindWindow znajdującą okno o określonej klasie i (lub) tytule,
• funkcję GetWindowRect określającą położenie i wymiary okna.

Ponieważ deklaracje tych funkcji są różne w wersji 32 i 64  bitowej, należy zastosować stałą kompilacji warunkowej VBA7 przekierowującą kompilator do odpowiedniej sekcji kodu.
Funkcja DisplayExcelWindowSize wywołuje FindWindowi GetWindowRect i wyświetla okno komunikatu z położeniem i wymiarami okna programu Excel 2010.

' A user-defined type to store the window dimensions.
Type RECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type

' Test which version of VBA you are using.
#If VBA7 Then
   ' API function to locate a window.
   Declare PtrSafe Function FindWindow Lib "user32" _
      Alias "FindWindowA" ( _
      ByVal lpClassName As String, _
      ByVal lpWindowName As String) As LongPtr
    
   ' API function to retrieve a window's dimensions.
   Declare PtrSafe Function GetWindowRect Lib "user32" ( _
      ByVal hwnd As LongPtr, _
      lpRect As RECT) As Long
#Else
   ' API function to locate a window.
   Declare Function FindWindow Lib "user32" _
      Alias "FindWindowA" ( _
      ByVal lpClassName As String, _
      ByVal lpWindowName As String) As Long
    
   ' API function to retrieve a window's dimensions.
   Declare Function GetWindowRect Lib "user32" ( _
      ByVal hwnd As Long, _
      lpRect As RECT) As Long
#End If


Sub DisplayExcelWindowSize()
   Dim hwnd As Long, uRect As RECT
   
   ' Get the handle identifier of the main Excel window.
   hwnd = FindWindow("XLMAIN", Application.Caption)
   
   ' Get the window's dimensions into the RECT UDT.
   GetWindowRect hwnd, uRect
   
   ' Display the result.
   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 zapoznaniu się z kodem ze strony Microsoftu, pozostało tylko przekopiowanie kodu do modułu programu Excel 2010 (wersja 64 bitowa) i uruchomienie funkcji DisplayExcelWindowSize (...), by uzyskać informacje o położeniu i wymiarach głównego okna programu Excel 2010. W dobrym zwyczaju jest skompilowanie kodu przed jego uruchomieniem.
No i  ZONK !

Kompilator zgłasza błąd „Niezgodność typów”. I co najgorsze (nie dla mnie), kompilator ma rację.
Zgodnie z deklaracją, funkcja FindWindow (...) zwraca typ LongPtr, który w 64 bitowym środowisku jest liczbą typu LongLong. „Zamiatając pod dywan”, szybko zmieniamy deklarację uchwytu okna hwnd:

Dim hwnd As Long
  na
Dim hwnd As LongPtr
  lub
Dim hwnd As LongLong

i możemy cieszyć się wynikiem:

Wszystko by było dobrze, gdyby nie pakiet Office w wersjach 2007 i niższych. Próba kompilacji poprawionego kodu w środowisku VB6 kończy się niepowodzeniem.

Kompilator wyświetla okno komunikatu informujące, że napotkał „Niezdefiniowany typ użytkownika” i podświetla linię kodu zawierającą deklarację uchwytu okna: Dim hwnd As LongPtr. Podobny błąd wystąpi, gdybyśmy zadeklarowali uchwyt okna hwnd jako typ LongLong. I nie ma się czemu dziwić, ponieważ wersja VBA 6.0 Office 2007 i wersji niższych nie obsługuje typów LongPtrLongLong.
Aby kod działał prawidłowo w VBA 7.0 i wersjach niższych VBA musimy więc poprawić deklarację uchwytu okna hwnd, tak by był prawidłowego typu w obu wersjach VBA. W tym celu powinniśmy skorzystać ze stałej kompilacji warunkowej Win64 i typu LongLong

Sub DisplayExcelWindowSize()
#If Win64 Then
   Dim hwnd As LongLong
#Else
   Dim hwnd As Long
#End If

lub ze stałej kompilacji warunkowej VBA7 i typu LongPtr
Sub DisplayExcelWindowSize()
#If VBA7 Then
   Dim hwnd As LongPtr
#Else
   Dim hwnd As Long
#End If
Dim uRect As RECT

' Get the handle identifier of the main Excel window.
hwnd = FindWindow("XLMAIN", Application.Caption)

' Get the window's dimensions into the RECT UDT.
GetWindowRect hwnd, uRect

' Display the result.
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