VBA Nâng cao: WinAPI - Khai báo Long và LongPtr tương thích đa nền tảng

Liên hệ QC

HeSanbi

Nam Nhân✨Hiếu Lễ Nghĩa Trí Tín✨
Tham gia
24/2/13
Bài viết
2,382
Được thích
3,536
Giới tính
Nam
Nếu các bạn đang hăng say học VBA hoặc đang lập trình VBA và vọc mã WinAPI thì hãy học ngay mẹo này để viết cho ứng dụng của bạn gọn nhẹ hơn. Khi ứng dụng của bạn viết đa nền tảng và đa phiên bản thì gặp phải nhiều vấn đề về tương thích. Nhất là khai báo Long, LongPtr, LongLong, hoặc các hàm VarPtr, StrPtr, ...
Hoặc các bạn cần bộ nhớ kiểu biến lớn hơn là LongLong trong dự án của bạn. Để tận dụng vào các thuật toán mà bạn tạo ra.

Với mẹo vô cùng đơn giản dưới đây là các bạn có thể bỏ qua các dòng mã rườm rà làm mã của bạn rối và khó đọc.

Đơn giản thôi các bạn hãy thêm dòng mã sau vào bất kì dự án nào của bạn có sử dụng các API 32 và 64 bit.


JavaScript:
#If VBA7 = 0 Then
   Public Enum LongLong:[_]:End Enum
   #If Win64 = 0 Then
   Public Enum LongPtr:[_]:End Enum
   #End If
#End If

***Lưu ý: Khi các bạn chép mã vào VBA, nếu các dòng mã Hiện màu đỏ là bình thường nhé. VBA sẽ không thông dịch mã đó, vì hệ điều hành của các bạn tương thích mã đủ điều kiện.
(Mã này không nên đặt vào module có khai báo cục bộ là Option Private Module.
Vì sẽ không thể khai báo tham chiếu biến trong Class và Module Object (Sheet, Form).

Hãy chạy thử thủ tục TestPretreatment dưới đây để xem thông tin máy tính và Office

JavaScript:
Sub TestPretreatment ()
#If VBA7 Then
#If Win64 Then
     Debug.Print "VBA7 64bit"
#Else
     Debug.Print "VBA7 32Bit"
#End If
#ElseIf VBA6 Then
#If Win64 Then
     Debug.Print "VBA6 64bit"
#Else
     Debug.Print "VBA6 32Bit"
#End If
#Else
     Debug.Print "________"
#End If
End Sub



Như đã nói trên, chỉ cần các bạn khai báo như vậy là có thể viết những dòng mã tương thích đa nền tảng mà không cần phải nhờ đến hai PC hoặc hai ứng dụng khác phiên bản để kiểm thử mã, sau khi đã viết mã tương thích.

Ví dụ khai báo như sau, sẽ làm cho mã bị rối và dài:


JavaScript:
#If VBA7 And Win64 Then
Private Sub TimeProc_ApiWindowFlexMove(ByVal hwnd As LongPtr, ByVal wMsg^, ByVal idEvent As LongPtr, ByVal dwTime^)
#ElseIf VBA7 Then
Private Sub TimeProc_ApiWindowFlexMove(ByVal hwnd As LongPtr, ByVal wMsg&, ByVal idEvent As LongPtr, ByVal dwTime&)
#Else
Private Sub TimeProc_ApiWindowFlexMove(ByVal hwnd&, ByVal wMsg&, ByVal idEvent&, ByVal dwTime&)
#End If
End Sub

Bạn chỉ cần khai báo như sau, khi đã chép mã đã nói trên:
JavaScript:
Private Sub TimeProc_ApiWindowFlexMove(ByVal hwnd As LongPtr, ByVal wMsg As LongLong, ByVal idEvent As LongPtr, ByVal dwTime As LongLong)

End Sub


Các khai báo trước đây, như dưới đây hãy bỏ nó đi:

JavaScript:
#If VBA7  Then
Dim hwnd As LongPtr
#Else
Dim hwnd As Long
#End If

Và bây giờ các bạn chỉ cần khai báo, khi có mã đã nói trên, là đủ để tương thích, mà không cần khai báo tiền xử lý #:
JavaScript:
Dim hwnd As LongPtr

Với mẹo khai báo biến Long như vậy, các bạn có thể chép lại mã có các API với các tham số biến LongPtr, LongLong rất đơn giản.

Nếu các bạn chép mã API đang ở nền tảng win64, thì chỉ cần bỏ PtrSafe trong dòng mã để tương thích với Nền tảng win32 (Còn tùy thuộc vào hàm API, có hàm API không có ở Win32).
Bạn không cần sửa LongPtr, LongLong thành Long để tương thích với win32 nữa. Khi đã có mã nói trên.


Lưu ý: Sửa từ API không có PtrSafe sang API có PtrSafe thì có những trường hợp Long phải sửa thành LongPtr hoặc LongLong

Ví dụ khai báo tương thích và các hàm API tương thích đa nền tảng:

JavaScript:
' Khai báo tương thích đa nền tảng
#If VBA7 = 0 Then
   Public Enum LongLong:[_]:End Enum
   #If Win64 = 0 Then
   Public Enum LongPtr:[_]:End Enum
   #End If
#End If
' Các hàm API tương thích đa nền tảng
#If VBA7 Then
Private Declare PtrSafe Function IUnknown_GetWindow Lib "shlwapi" Alias "#172" (ByVal pIUnk As Any, ByVal hwnd As LongPtr) As Long
#Else
Private Declare Function IUnknown_GetWindow Lib "shlwapi" Alias "#172" (ByVal pIUnk As Any, ByVal hwnd As LongPtr) As Long
#End If

#If VBA7 Then
  #If Win64 Then
  Private Declare PtrSafe Function GetWindowLong Lib "user32" Alias "GetWindowLongPtrA" (ByVal hWnd As LongPtr, ByVal nIndex As Long) As LongPtr
  Private Declare PtrSafe Function SetWindowLong Lib "user32" Alias "SetWindowLongPtrA" (ByVal hWnd As LongPtr, ByVal nIndex As Long, ByVal dwNewLong As LongPtr) As LongPtr
  Declare PtrSafe Function GetClassLong Lib "user32" Alias "GetClassLongPtrA" (ByVal hWnd As LongPtr, ByVal nIndex As Long) As LongPtr
  Private Declare PtrSafe Function SetClassLong Lib "user32" Alias "SetClassLongPtrA" (ByVal hWnd As LongPtr, ByVal nIndex As Long, ByVal dwNewLong As LongPtr) As LongPtr
  #Else
  Private Declare PtrSafe Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hWnd As LongPtr, ByVal nIndex As Long) As Long
  Private Declare PtrSafe Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As LongPtr, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
  Declare PtrSafe Function GetClassLong Lib "user32" Alias "GetClassLongA" (ByVal hWnd As LongPtr, ByVal nIndex As Long) As Long
  Private Declare PtrSafe Function SetClassLong Lib "user32" Alias "SetClassLongA" (ByVal hWnd As LongPtr, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
  #End If
#Else
Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long) As Long
Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Declare Function GetClassLong Lib "user32" Alias "GetClassLongA" (ByVal hWnd As Long, ByVal nIndex As Long) As Long
Private Declare Function SetClassLong Lib "user32" Alias "SetClassLongA" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
#End If

Có thể các bạn đang hỏi tại sao lại có kiểu LongPtr. Đơn giản thôi, nó là kiểu khai báo tương thích. Nếu LongPtr ở môi trường 32 bit thì nó là Long, nếu nó ở 64bit thì nó là LongLong. Nó không thực sự là Long và LongLong.

Ngay từ lúc này tôi khuyên các bạn nên tận dụng Kiểu LongLong vì nó khai thác bộ nhớ lớn hơn Long khá nhiều.
Vì khi bạn đang sử dụng Win64 mà lại dùng kiểu Long cho một thuật toán mạnh mẽ thì thật là tiếc nuối cho đà phát triển của bạn.


Mẹo tôi hướng dẫn trên đây sẽ giúp các bạn đơn giản việc viết mã đa nền tảng, các bạn có thể tận dụng nó trên con đường lập trình của mình.
Chúc các bạn thành công!

-------------------------------------------
Các bạn có thể tham khảo thêm bài viết của tôi tại tag
#sanbi udf
 
Lần chỉnh sửa cuối:
@HeSanbi

1. Windows 11 (64bit) + Office 2013 (32bit): báo lỗi TH3, 4 và code bài #57

Screen Shot 2023-10-31 at 13.01.52.png. Screen Shot 2023-10-31 at 13.05.43.png

2. Windows 11 (64bit) + Microsoft 365: không báo lỗi trường hợp nào.

Screen Shot 2023-10-31 at 13.11.34.png
 
Upvote 0
@ongke0711 rất cảm ơn anh

Mã tương thích phù em đã cập nhật vào bài viết.

Các mã khai báo trước đây gây ra lỗi khai báo nếu LongPtr nằm trong mã các hàm Win32 API.
 
Upvote 0
Trước hết em cám ơn chủ thớt chia sẻ thông tin ạ, em cũng mò mẩm học hỏi từ diễn đàn nhiều cái. Tuy nhiên em thấy vấn đề chung mà khi người chia sẻ đưa ra lại có một số ý kiến như Kiều Mạnh thường xãy ra nhiều nên vì thế anh em thành viên nhiều khi muốn chia sẻ cũng không dám nói ra. em chỉ nêu suy nghĩ của mình, nên suy nghĩ thoáng ra các bác ạ, ai chia sẻ gì thì mình ghi nhận không nên nặng lời kia nọ. "Hãy yêu thương và chia sẻ, bạn sẽ nhận về niềm vui và hạnh phúc'', "Có tài mà không có đức là người vô dụng".
 
Upvote 0
Anh @ongke0711 và Bác @Maika8008

Thử giúp cho một trường hợp nữa. Lỗi này xuất phát từ trình thông dịch VBA, không biết là bản mới nhất đã được vá lỗi chưa.

Tạo một lớp với tên cClass với mã:
JavaScript:
Private Sub Class_Terminate()
End Sub

Tạo một Module với mã và chạy thử tục bug, chỉ cần có một dòng được in, thì phiên bản trình thông dịch có lỗi
JavaScript:
Sub Bug()
    ' We don't really need a Clone method to reproduce the bug
    If Falsee(New cClass) Then
        Debug.Print "This does print, although it shouldn't!"
    End If

    ' If we add a logical operator and a second method call then the bug disappears:
    If Falsee(New cClass) Or Falsee(New cClass) Then
        Debug.Print "This doesn't print, as it shouldn't."
    End If

    ' It could be any other method. The order of the methods also doesn't matter
    If Falsee(New cClass) Or Sin(0) Then
        Debug.Print "This doesn't print, as it shouldn't."
    End If

    ' The above workaround does not work if we simply use a boolean value after the method call
    If Falsee(New cClass) Or False Then
        Debug.Print "This does print, although it shouldn't!"
    End If

    ' But it does work if we add the boolean before the method call:
    If False Or Falsee(New cClass) Then
        Debug.Print "This doesn't print, as it shouldn't."
    End If
    If True And Falsee(New cClass) Then
        Debug.Print "This doesn't print, as it shouldn't."
    End If
End Sub

Function Falsee(oClass As cClass) As Boolean
    Falsee = False
End Function
 
Lần chỉnh sửa cuối:
Upvote 0
Đã thử, sub Bug không in dòng nào cả.
 
Upvote 0
Đã thử, sub Bug không in dòng nào cả.
Bác giúp Debug hai cách khai báo này, đây là mong muốn của một lập trình viên nước ngoài, tôi đã cố gắng giải thích cho họ, nhưng họ nhất quyết muốn biết kết quả. Làm phiền Bác lần nữa. Bác chụp cho hình ảnh nếu lỗi.

JavaScript:
#If VBA7 = 0 And True Then
   Private Enum LongPtr:[_]:End Enum
#End If


JavaScript:
#If True And VBA7 = 0 Then
   Private Enum LongPtr:[_]:End Enum
#End If
 
Upvote 0
@HeSanbi
Đã thử code của bài #64 và #66 trên 2 phiên bản Office như bài trước và không báo lỗi gì cả, cái sub Bug cũng không in gì luôn nhé.

Screen Shot 2023-11-01 at 00.02.40.png. Screen Shot 2023-11-01 at 00.08.04.png
 
Upvote 0
Bác giúp Debug hai cách khai báo này, đây là mong muốn của một lập trình viên nước ngoài, tôi đã cố gắng giải thích cho họ, nhưng họ nhất quyết muốn biết kết quả. Làm phiền Bác lần nữa. Bác chụp cho hình ảnh nếu lỗi.

JavaScript:
#If VBA7 = 0 And True Then
   Private Enum LongPtr:[_]:End Enum
#End If


JavaScript:
#If True And VBA7 = 0 Then
   Private Enum LongPtr:[_]:End Enum
#End If
Debug cả hai trường hợp đều không báo lỗi.
 
Upvote 0
Debug cả hai trường hợp đều không báo lỗi.
Vấn đề xảy ra trên máy tính của Bác ở biến số biên dịch.

Bác giúp kiểm tra lại đoạn mã dưới đây một lần nữa.
Bác chép tất cả mã vào Module, sau đó nhấn Debug Compile, dòng nào lỗi Bác Comment lại rồi tiếp tục Debug.
Xin lỗi đã làm khó cho Bác nhé


Trường hợp khai báo toàn cục:
JavaScript:
#If VBA7 Then
#Else
   Public Enum LongPtr:[_]:End Enum
#End If

#If VBA7 = 0 Then
   Public Enum LongPtr:[_]:End Enum
#End If

#If Not -VBA7 Then
   Public Enum LongPtr:[_]:End Enum
#End If

#If CBool(VBA7 = 0) Then
   Public Enum LongPtr:[_]:End Enum
#End If

#If VBA7= 0 And Win64 = 0 Then
   Public Enum LongPtr:[_]:End Enum
#End If

#If Not (-VBA7 Or -Win64) Then
   Public Enum LongPtr:[_]:End Enum
#End If

Trường hợp khai báo cục bộ:
JavaScript:
#If VBA7 Then
#Else
   Private Enum LongPtr:[_]:End Enum
#End If

#If VBA7 = 0 Then
   Private Enum LongPtr:[_]:End Enum
#End If

#If Not -VBA7 Then
   Private Enum LongPtr:[_]:End Enum
#End If

#If CBool(VBA7 = 0) Then
   Private Enum LongPtr:[_]:End Enum
#End If

#If VBA7= 0 And Win64 = 0 Then
   Private Enum LongPtr:[_]:End Enum
#End If

#If Not (-VBA7 Or -Win64) Then
   Private Enum LongPtr:[_]:End Enum
#End If
 
Lần chỉnh sửa cuối:
Upvote 0
@ongke0711 cảm ơn anh, vấn đề này chỉ xảy ra trên máy tính Bác @Maika8008
Trường hợp này rất hiếm gặp, lỗi này nằm trong trình thông dịch mã
 
Upvote 0
Upvote 0
Hình ảnh ở #4 xảy ra lỗi, nay lại không có, có gì đó không hợp lí. Ở trên có một trường hợp của #4 lại không có lỗi.

Bác chép từng trường hợp vào một book và module mới thử lại được không?
Bạn xem lại đi, không có TH nào của bài #55 cả.
- Bài #55 là #If VBA7 = 0 Or Win64 = 0 Then
- Bài #69 là #If VBA7 = 0 And Win64 = 0 Then

Đây tôi tổng hợp lại code khai báo của 2 bài:
Mã:
'--------'Day là khai bao o bài #69 => Không báo loi
#If VBA7 Then
#Else
   Private Enum LongPtr:[_]:End Enum
#End If

#If VBA7 = 0 Then
   Private Enum LongPtr:[_]:End Enum
#End If

#If Not -VBA7 Then
   Private Enum LongPtr:[_]:End Enum
#End If

#If CBool(VBA7 = 0) Then
   Private Enum LongPtr:[_]:End Enum
#End If

#If VBA7 = 0 And Win64 = 0 Then
   Private Enum LongPtr:[_]:End Enum
#End If

#If Not (-VBA7 Or -Win64) Then
   Private Enum LongPtr:[_]:End Enum
#End If
'------- het code bài #69

'Day là truong hop 3 bài #55 => Báo loi
'#If VBA7 = 0 Or Win64 = 0 Then
'   Private Enum LongPtr:[_]:End Enum
'#End If

'Day là truong hop 4 bài #55 => Báo loi
'#If Win64 = 0 Then
'   Private Enum LongPtr:[_]:End Enum
'#End If
 
Upvote 0
@Maika8008

Bác sửa Private thành Public và thử lại. Có lẻ vấn đề còn nằm ở cục bộ và toàn cục.

Rất cảm ơn Bác đã bỏ thời gian để kiểm tra.
 
Upvote 0
Thay bằng Public cũng báo lỗi vậy thôi bạn.
Cảm ơn Bác nhé!

Vậy là kết luận, không có lỗi như Bác đã báo cáo ở #4.

Vì khi tôi viết bài viết khai báo đầu tiên mã nên sử dụng để tương thích như dưới đây:

JavaScript:
#If VBA7 Then
#Else
   Public Enum LongPtr:[_]:End Enum
   Public Enum LongLong:[_]:End Enum
#End If

Các cách khai báo sau đều phù hợp để tận dụng:

JavaScript:
#If VBA7 = 0 Then
   Public Enum LongPtr:[_]:End Enum
   Public Enum LongLong:[_]:End Enum
#End If

JavaScript:
#If VBA7 = 0 And Win64 = 0 Then
   Public Enum LongPtr:[_]:End Enum
   Public Enum LongLong:[_]:End Enum
#End If
 
Upvote 0
Web KT
Back
Top Bottom