[Cần hướng dẫn] Về lập trình Viết Function với Settimer và Killtimer (2 người xem)

Liên hệ QC

Người dùng đang xem chủ đề này

Tôi tuân thủ nội quy khi đăng bài

Mr.hieudoanxd

Thành viên thường trực
Tham gia
25/10/19
Bài viết
346
Được thích
159
Em chào cả nhà!
Tết cận kề, vài ngày cuối năm không biết làm gì tìm hiểu thêm một chút VBA
Hiện tại em đang tìm hiểu về viết Function với Settimer và Killtimer để làm một số thao tác với bảng tính mà function thông thường khó có khả năng làm việc được. Ví dụCode ở dưới em đã làm giải quyết được một số bài toán nhỏ. Tuy nhiên thú thực em không đủ sức làm chủ được thủ thuật viêt Function với Settimer và Killtimer. Nhiều khi Excel văng không rõ nguyên nhân
Trong diễn đàn của mình em thấy có bác @HeSanbi Hay viết Function với các hàm này. Em nhờ bác và các anh chị trong diễn đàn hướng dẫn đề phòng các lỗi phát sinh khi khai thác Settimer và Killtimer và vượt qua các lỗi trên. Đồng thời có các lưu ý gì khi sử dụng nó.
Ít nhất em thấy nếu em sử dụng vòng lặp trong sub callback hoặc gộp 2 hàm cùng sử dụng phương pháp trên là rất hay bị không thoát được Settimer và có nguy cơ treo ứng dụng.


Mã:
#If VBA7 Then
  Private Declare PtrSafe Function SetTimer Lib "user32" (ByVal hWnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As LongPtr, ByVal lpTimerFunc As LongPtr) As Long
  Private Declare PtrSafe Function KillTimer Lib "user32" (ByVal hWnd As LongPtr, ByVal nIDEvent As LongPtr) As Long
  Dim TimerID As LongPtr
#Else
  Private Declare Function SetTimer Lib "user32" (ByVal HWnd As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
  Private Declare Function KillTimer Lib "user32" (ByVal HWnd As Long, ByVal nIDEvent As Long) As Long
  Dim TimerID As Long
#End If

Dim rRH As Range, nNum As Double

Function zRowHeight(rng As Range, num As Double) As String
  Set rRH = rng
  nNum = num
  TimerID = SetTimer(0, 0, 1, AddressOf zRowHeight_callback)
  zRowHeight = "Hoàn thành"
  Exit Function
End Function
 
Private Sub zRowHeight_callback()
  On Error GoTo ERR
  Rows(rRH.cells(1, 1).Row & ":" & rRH.Rows.Count - 1).RowHeight = nNum
  KillTimer 0, TimerID
ERR:
  KillTimer 0, TimerID
End Sub
 
SetTimer hiểu đơn giản là nó khởi tạo một bộ đếm thời gian để thực hiện gọi đến một địa chỉ bộ nhớ là hàm gọi lại để thực thi, và cần thực thi ngay lập tức, không thể đợi.

SetTimer nhận 4 đối số:
1. Cửa sổ tay cầm, liên kết với bộ đếm, nếu tay cầm đóng, bộ đếm tự động giải phóng.​
2. Id sự kiện, được hiểu là một định danh bộ đếm thời gian, dùng để điều hướng khi người dùng thiết lập, nếu không thì nó được tạo tự động, chỉ bằng 0 khi có cửa sổ tay cầm.​
3. Thời gian bộ đếm gọi lại, chỉ kết thúc gọi khi gọi hàm KillTimer hoặc cửa sổ tay cầm đóng.​
4. Địa chỉ trỏ đến hàm gọi lại. Địa chỉ này là một hàm nằm tại Module, với 4 tham số bắt buộc có, để nhận lại các thông số trong quá trình được gọi.​
Địa chỉ này có thể nằm trong Class Module khi lập trình tận dụng thunk.​

KillTimer là hàm giải phóng địa chỉ bộ nhớ hàm được gán vào tay cầm hoặc id sự kiện.

Nói về API SetTimer nói riêng và các API liên quan đến gọi lại từ địa chỉ bộ nhớ nói chung. Thì chúng cần được chạy ngay lập tức khi được gọi lại.
Vấn đề là VBA chỉ chạy xử lý trên một luồng duy nhất, trong khi có nhiều thứ cần cấp quyền cao nhất tương tác đến ứng dụng, các sự kiện tính toán, truy cập xuất dữ liệu, sự kiện cửa sổ, sự kiện chuột, nhập phím, ... quá nhiều thứ. Hàm SetTimer dễ rất sẽ bị xung đột.

Hàm SetTimer khi gọi sẽ gây ra sập ứng dụng nếu gặp các trường hợp sau:
1. Hàm gọi lại đang chạy gặp lỗi.​
2. Hàm gọi lại đang chạy nhưng có gọi đến lệnh DoEvents, và có một phương thức khác được nhường luồng chạy có lỗi.​
3. Sắp hết thời gian bộ đếm nhưng không thể gọi đến hàm gọi lại, vì luồng đang bận.​
4. Đã khởi tạo bộ đếm vào bộ nhớ, nhưng ứng dụng bị StateLoss (Mất trạng thái) hoặc bị dừng để Debug.​
5. Xung đột với các API hàm sự kiện, các hàm API thời gian chạy, vì các hàm đó cũng cần luồng để chạy ngay lập tức.​
6. Người dùng đang tương tác với ứng dụng.​
7. Thời gian bộ đếm nhỏ hơn tổng thời gian chạy của hàm gọi lại.​
8. Hàm gọi lại truy cập địa chỉ bộ nhớ toàn cục của dự án, nhưng xung đột.​


Để xử lý được vấn đề SetTimer này có vài cách:
1. Tận dụng thunk, lập trình mã Assemble để kiểm tra luồng của Excel có đang bận không, Excel có bị StateLoss không, để nhảy đến vị trí giải phóng bộ nhớ khi đã gọi SetTimer, một người lập trình chuyên nghiệp chưa chắc có thể làm được điều này nếu không học sâu vào mã máy và đọc mã máy để dịch ngược.​
2. Lập trình đa luồng với các ngôn ngữ có hỗ trợ đóng gói DLL, khởi tạo hàm gọi lại ở một luồng khác, để chúng không bị xung đột. VBA không hỗ trợ điều này, nhưng gọi với các API đến DLL đã lập trình xử lý đa luồng thì có khả năng.​
3. Lập trình mã cẩn trọng, giải phóng bộ nhớ cẩn trọng. Giải phóng các khởi tạo API hàm sự kiện, API thời gian chạy trước khi gọi SetTimer. Khi gọi SetTimer luôn cần có cửa sổ tay cầm và id event để về sau giải phóng bộ nhớ.​
Dưới đây là mã tạm thời tôi viết cho tôi sử dụng, nên không có chú thích, nào rõ ràng, để vượt qua một số khả năng làm sập ứng dụng. Nhưng khả năng sập là còn nhiều.
Nếu bạn có khả năng đọc mã, thì tận dụng, cũng đơn giản.

setSafeTimer là khởi chạy như SetTimer
killSafeTimer như killTimer, onBreakTimer là gọi killTimer trước khi chạy mã chính. repeatSetTimer là để gọi lại mà không cần thực hiện lại với setSafeTimer.
Khi thoát ứng dụng cần gọi hàm DestroyAllVirtualWindow.

Bạn nên tận dụng OnTime của Application sẽ rất an toàn. Nhưng khi gọi con trỏ chuột sẽ có cảnh báo.

JavaScript:
Sub calltest()
  Dim h As LongPtr: h = Choose(1, AddressOf ProcTimer)
  Dim h2 As LongPtr: h2 = Choose(1, AddressOf virtualProc)
  ' 500 là thời gian hẹn giờ, -1 là lặp lại vô tận, 0 là chạy 1 lần, lớn hơn 0 là vị trí tổng thời bộ đếm sẽ dừng.
  Call setSafeTimer(cIdEvent, h, h2, 500, -1, False)
End Sub

Sub stoptest()
  Dim h As LongPtr: h = Choose(1, AddressOf ProcTimer)
  Call killSafeTimer(h, cIdEvent)
End Sub

Private Sub ProcTimer(ByVal hwnd As LongPtr, ByVal wMsg As Long, ByVal idEvent As LongPtr, ByVal dwTime As LongPtr)
  On Error Resume Next
  If onBreakTimer(hwnd, idEvent, 2) <> 0 Then Exit Sub
  '
    ' Mã cần chạy ở đây
  '
  ' Gọi lại với repeatSetTimer, nếu không sẽ không lặp lại
  Call repeatSetTimer(hwnd, idEvent, dwTime)
End Sub
Private Sub virtualProc()
  virtualProcTimer
End Sub
JavaScript:
Private Const ProjectName = "VBSafeTimerStateLoss"
Private Const ProjectVersion = "1.1"

Option Explicit
#If VBA7 = 0 Then
  Private Enum LongLong: [_]: End Enum
  Private Enum LongPtr: [_]: End Enum
#End If
#If Win64 Then
  Private Const PTR_LEN = 8&
#Else
  Private Const PTR_LEN = 4&
#End If
Private Const NULL_PTR As LongPtr = 0
Private Const UPPER_BOUND = PTR_LEN * 1.5
#If -VBA7 And -Win64 Then
  Private Declare PtrSafe Function SetTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As LongPtr, ByVal lpTimerFunc As LongPtr) As LongPtr
  Private Declare PtrSafe Function KillTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr) As LongPtr
#ElseIf VBA7 Then
  Private Declare PtrSafe Function SetTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As LongPtr, ByVal lpTimerFunc As LongPtr) As Long
  Private Declare PtrSafe Function KillTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr) As Long
#Else
  Private Declare Function setTimer Lib "user32" Alias "SetTimer" (ByVal HWnd As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
  Private Declare Function killTimer Lib "user32" Alias "KillTimer" (ByVal HWnd As Long, ByVal nIDEvent As Long) As Long
#End If
#If VBA7 Then
Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
Private Declare PtrSafe Function SetProp Lib "user32" Alias "SetPropA" (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal hData As LongPtr) As Long
Private Declare PtrSafe Function GetProp Lib "user32" Alias "GetPropA" (ByVal hwnd As LongPtr, ByVal lpString As String) As LongPtr
Private Declare PtrSafe Function RemoveProp Lib "user32" Alias "RemovePropA" (ByVal hwnd As LongPtr, ByVal lpString As String) As LongPtr
Private Declare PtrSafe Function getTickCount Lib "kernel32.dll" Alias "GetTickCount" () As Long
Private Declare PtrSafe Function CreateWindowEx Lib "user32" Alias "CreateWindowExA" (ByVal dwExStyle As Long, ByVal lpClassName As String, ByVal lpWindowName As String, ByVal dwStyle As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hWndParent As LongPtr, ByVal hMenu As LongPtr, ByVal hInstance As LongPtr, lpParam As Any) As LongPtr
Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
Private Declare PtrSafe Function GetCurrentThreadId Lib "kernel32" () As Long
Private Declare PtrSafe Function IsWindow Lib "user32" (ByVal hwnd As LongPtr) As Long
Private Declare PtrSafe Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As LongPtr
Private Declare PtrSafe Function EnumThreadWindows Lib "user32" (ByVal dwThreadId As Long, ByVal lpfn As LongPtr, lParam As LongPtr) As Long
Private Declare PtrSafe Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As LongPtr, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Private Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageW" (ByVal hwnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, lParam As Any) As Long
Private Declare PtrSafe Function GetDlgItem Lib "user32" (ByVal hDlg As LongPtr, ByVal nIDDlgItem As Long) As LongPtr
Private Declare PtrSafe Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As Long
#Else
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Private Declare Function SetProp Lib "user32" Alias "SetPropA" (ByVal hwnd As Long, ByVal lpString As String, ByVal hData As Long) As Long
Private Declare Function GetProp Lib "user32" Alias "GetPropA" (ByVal hwnd As Long, ByVal lpString As String) As Long
Private Declare Function RemoveProp Lib "user32" Alias "RemovePropA" (ByVal hwnd As Long, ByVal lpString As String) As Long
Private Declare Function getTickCount Lib "kernel32.dll" Alias "GetTickCount" () As Long
Private Declare Function CreateWindowEx Lib "user32" Alias "CreateWindowExA" (ByVal dwExStyle As Long, ByVal lpClassName As String, ByVal lpWindowName As String, ByVal dwStyle As Long, ByVal X As Long, ByVal Y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hwndParent As Long, ByVal hMenu As Long, ByVal hInstance As Long, lpParam As Any) As Long
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function GetCurrentThreadId Lib "kernel32" () As Long
Private Declare Function IsWindow Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As LongPtr
Private Declare Function EnumThreadWindows Lib "user32" (ByVal dwThreadId As Long, ByVal lpfn As Long, lParam As Long) As Long
Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Private Declare Function SendMessage Lib "user32" Alias "SendMessageW" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Private Declare Function GetDlgItem Lib "user32" (ByVal hDlg As Long, ByVal nIDDlgItem As Long) As Long
Private Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
#End If

Private Enum idEventUseDefined
  cIdEvent = 111122000
End Enum


'======================================================================================================================
' Safe-Timer And StateLoss Callback
'======================================================================================================================
'-----------------------------------------------------------
'                  Method: setSafeTimer
'-----------------------------------------------------------
' SetProp Prefix+idEvent:
'   Prefix E: Elapse Timer
'   Prefix T: Time over
'   Prefix X: Main ProcTimer Address
'   Prefix P: Fake ProcTimer Address
'   Prefix C: current time
'   Prefix B: Enabled repeatSetTimer
'   Prefix R: callback If VB Reset (StateLoss Callback)
'   Prefix S: StateLoss
'   Prefix I: idCreateWindow
'-----------------------------------------------------------
Sub setSafeTimer(ByVal idEvent As LongPtr, ByVal ProcAddr As LongPtr, Optional ByVal virtualProc As LongPtr, _
        Optional ByVal uElapse As LongPtr, _
        Optional ByVal eElapse As LongPtr, _
        Optional callbackIfVBReset As Boolean, Optional ByVal newHandle As Boolean, Optional ByVal hwnd As LongPtr)
  If idEvent <= 1000 Then Err.Raise 8550001, , "Enter idEvent great than 1000"
  Dim h As LongPtr, p As LongPtr, u As LongPtr, s$, a, b, i%
  #If Win64 Then
    p = ProcAddr: ProcAddr = virtualProc
    If ProcAddr = 0 Then ProcAddr = Choose(1, AddressOf virtualProcTimer)
    SwapMemoryAddresses ProcAddr, p
  #End If
  s = CStr(idEvent): u = uElapse
  h = hwnd: If h = 0 Then h = iiiVirtualWindow(1, IIf(newHandle, ProcAddr, 0))
  Select Case GetProp(h, "S" & s)
  Case 0: SetProp h, "C" & s, getTickCount
  Case 1: u = u - getTickCount + GetProp(h, "C" & s): If u < 0 Then u = 0
  End Select
  a = Array("P", "X", "E", "T", "B", "I", "S", "R")
  b = Array(ProcAddr, p, uElapse, eElapse, 1, -newHandle, 0, -callbackIfVBReset)
  For i = 0 To UBound(a)
    SetProp h, a(i) & s, b(i)
  Next

  Dim l As LongPtr, n As LongPtr, o As LongPtr:  n = h
  Do: l = 0: l = GetProp(h, CStr(n)):
    If l <> 0 Then n = l: If o = 0 Then o = l Else If o = l Then Exit Do
  Loop Until l = 0
  If n <> idEvent Then SetProp h, CStr(n), idEvent ' set multi idEvent
 
  h = SetTimer(h, idEvent, u, ProcAddr)
End Sub
Function repeatSetTimer(ByVal hwnd As LongPtr, ByVal idEvent As LongPtr, Optional ByVal dwTime As LongPtr) As Boolean
  Dim t As LongPtr, l As LongPtr, s$
  s = CStr(idEvent): l = GetProp(hwnd, "T" & s): t = l + GetProp(hwnd, "C" & s)
  If (dwTime <= t And dwTime > 0 And GetProp(hwnd, "B" & s) = 1) Or l < 0 Then
    Call SetTimer(hwnd, idEvent, GetProp(hwnd, "E" & s), GetProp(hwnd, "P" & s)): repeatSetTimer = True
  Else
    RemoveAllProp hwnd, idEvent
  End If
End Function
Function killSafeTimer(ByVal ProcAddr As LongPtr, ByVal idEvent As LongPtr, Optional ByVal newHandle As Boolean) As Boolean
  On Error Resume Next
  Dim h As LongPtr
  h = iiiVirtualWindow(1, IIf(newHandle, ProcAddr, 0))
  KillTimer h, idEvent: RemoveAllProp h, idEvent
End Function
Private Sub RemoveAllProp(ByVal hwnd As LongPtr, ByVal idEvent As LongPtr)
  On Error Resume Next
  Dim h As LongPtr, l As LongPtr, n As LongPtr, s$, i
  h = hwnd: s = CStr(idEvent)
  For Each i In Array("P", "X", "E", "T", "B", "R", "C", "S", "I")
    RemoveProp h, i & s
  Next
  n = GetProp(h, CStr(h))
  If n = idEvent Then SetProp h, CStr(h), GetProp(h, s): Exit Sub
  Do: l = GetProp(h, CStr(n)): If l = idEvent Then SetProp h, CStr(n), GetProp(h, s): Exit Do
    If l > 0 Then n = l
  Loop Until l = 0
  RemoveProp h, s
End Sub
Function onBreakTimer(Optional ByVal hwnd As LongPtr, Optional ByVal idEvent As LongPtr, _
            Optional ByVal onBreak&, Optional ByVal onDoEvents As Boolean) As Long
  On Error Resume Next
  KillTimer hwnd, idEvent
  If onBreak <> 0 Then
    Dim h As LongPtr
    h = FindWindow("#32770", "Microsoft Visual Basic")
    If h = 0 Then
      h = FindWindow("#32770", "Microsoft Visual Basic for Applications")
      If h <> 0 Then onBreakTimer = 1: GoTo c
    Else
      If (onBreak And 1) <> 0 Then onBreakTimer = 2:
      GoTo c
    End If
  End If
  Err.Clear
Exit Function
c:
  If (onBreak And 2) <> 0 Then
    Const IDEnd = &H12C0, IDDebug = &H12C1, IDContinue = &H12C2, BM_CLICK = &HF5
    Call SendMessage(GetDlgItem(h, IDEnd), BM_CLICK, 0, ByVal 0)
  End If
End Function
Private Sub virtualProcTimer()
  On Error Resume Next
  Dim h As LongPtr, l As LongPtr, ll As LongPtr, o As LongPtr, s$, k%:
  Dim h0 As LongPtr, p0 As LongPtr, o0 As LongPtr
  h0 = iiiVirtualWindow(): h = h0: ll = h0
  Do While h > 0
    l = h
    Do:
      l = GetProp(h, CStr(l)): If l = 0 Or o = l Then Exit Do
      KillTimer h, l: s = CStr(l)
      If GetProp(h, "R" & s) = 1 Then
        k = k + 1: SetProp h, "S" & s, 1
        Call setSafeTimer(l, GetProp(h, "X" & s), GetProp(h, "P" & s), GetProp(h, "E" & s), GetProp(h, "T" & s), True, GetProp(h, "I" & s))
      Else
        RemoveAllProp h, l
      End If
      If o = 0 Then o = l
    Loop Until l = 0
    p0 = GetProp(h0, "VW" & CStr(h))
    If k > 0 Then ll = h Else If h <> h0 Then SetProp h0, "VW" & ll, p0: CloseWindow h
    h = p0: If h = 0 Or o0 = h Then Exit Do
    If o0 = 0 Then o0 = h
  Loop
End Sub

Sub DestroyAllVirtualWindow()
  On Error Resume Next
  Dim h As LongPtr, h0 As LongPtr, o0 As LongPtr
  h0 = iiiVirtualWindow(): h = h0
  Do While h > 0
    If h <> h0 Then If IsWindow(h) <> 0 Then CloseWindow h
    h = GetProp(h0, "VW" & CStr(h)): If h = 0 Or o0 = h Then Exit Do
    If o0 = 0 Then o0 = h
  Loop
  If h0 > 0 Then If IsWindow(h0) <> 0 Then CloseWindow h0
End Sub

Function iiiVirtualWindow(Optional createnew%, Optional ByVal idCreateWindow As LongPtr) As LongPtr
  ' Use CreateWindow to make handle
  Dim s$, h As LongPtr, h0 As LongPtr
  If idCreateWindow <= 0 Then s = Choose(1, AddressOf virtualProcTimer) Else h0 = iiiVirtualWindow(1, 0): s = idCreateWindow
  s = "_____XLVirtualWindow[" & s & "]": h = FindWindow("STATIC", s)
  Select Case createnew
  Case 1: If h = 0 Then GoTo n
  Case -1, 2:
    If h > 0 Then CloseWindow h: h = 0
    If createnew = 2 Then
n:      h = CreateWindowEx(0, "STATIC", s, 0, 0, 0, 0, 0, 0, 0, GetModuleHandle(vbNullString), ByVal 0&)
      If idCreateWindow <> 0 Then
        Dim l As LongPtr, n As LongPtr, o As LongPtr:  n = h0
        Do: l = 0: l = GetProp(h0, "VW" & CStr(n)):
          If l <> 0 Then n = l: If o = 0 Then o = l Else If o = l Then Exit Do
        Loop Until l = 0
        If n <> h Then SetProp h0, "VW" & CStr(n), h ' set multiHandle
      End If
    End If
  End Select
  iiiVirtualWindow = h
End Function
Function iiiVBEHandle() As LongPtr
  Static l As LongPtr
  If l = 0 Then EnumThreadWindows GetCurrentThreadId, AddressOf EnumThreadWndProc, l
  If l = 0 Then l = Application.hwnd Else If IsWindow(l) = 0 Then l = Application.hwnd
  iiiVBEHandle = l
End Function
Private Function EnumThreadWndProc(ByVal hwnd As Long, lParam As LongPtr) As Long
  Dim l1 As Long, h As LongPtr, s1 As String * 100, s$
  l1 = GetClassName(hwnd, s1, 100): s = Left$(s1, l1)
  Select Case s
  Case "wndclass_desked_gsk": lParam = hwnd
  End Select
  EnumThreadWndProc = True
End Function
Private Function SwapMemoryAddresses(ByVal Addrss1 As LongPtr, ByVal Addrss2 As LongPtr)
  Call CopyMemory(ByVal Addrss1 + PTR_LEN * 6& + 4&, ByVal Addrss2 + PTR_LEN * 6& + 4&, PTR_LEN)
End Function

Sub CloseWindow(hwnd As LongPtr)
   Const WM_CLOSE = &H10
   Const WM_QUIT = &H12
   Const WM_DESTROY = &H2
   Const WM_NCDESTROY = &H82
  PostMessage hwnd, WM_CLOSE, 0, 0
    'PostMessage Hwnd, WM_DESTROY, 0, 0
   ' PostMessage Hwnd, WM_NCDESTROY, 0, 0
   ' PostMessage Hwnd, WM_QUIT, 0, 0
End Sub
 
Lần chỉnh sửa cuối:
Upvote 0
Bác có thể cho một ví dụ Function bất kỳ sử dụng "mã tạm thời" mà bác viết để em tham khảo được không? Tiêu hóa được hết đống kiến thức này chắc cũng ngốn cơ số thời gian ạ
Để xử lý được vấn đề SetTimer này có vài cách:
1. Tận dụng thunk, lập trình mã Assemble để kiểm tra luồng của Excel có đang bận không, Excel có bị StateLoss không, để nhảy đến vị trí giải phóng bộ nhớ khi đã gọi SetTimer, một người lập trình chuyên nghiệp chưa chắc có thể làm được điều này nếu không học sâu vào mã máy và đọc mã máy để dịch ngược.​
2. Lập trình đa luồng với các ngôn ngữ có hỗ trợ đóng gói DLL, khởi tạo hàm gọi lại ở một luồng khác, để chúng không bị xung đột. VBA không hỗ trợ điều này, nhưng gọi với các API đến DLL đã lập trình xử lý đa luồng thì có khả năng.​
3. Lập trình mã cẩn trọng, giải phóng bộ nhớ cẩn trọng. Giải phóng các khởi tạo API hàm sự kiện, API thời gian chạy trước khi gọi SetTimer. Khi gọi SetTimer luôn cần có cửa sổ tay cầm và id event để về sau giải phóng bộ nhớ.​
Dưới đây là mã tạm thời tôi viết cho tôi sử dụng, nên không có chú thích, nào rõ ràng, để vượt qua một số khả năng làm sập ứng dụng. Nhưng khả năng sập là còn nhiều.
Nếu bạn có khả năng đọc mã, thì tận dụng, cũng đơn giản.
 
Upvote 0
Bạn không nắm được, và không hiểu sâu các hàm API có tính chất quan trọng cao, thì chưa thể nói đến việc sử dụng được các hàm này.
Các hàm này chí ít bạn cũng phải biết về kiến thức cấp phát và truy cập bộ nhớ. Tôi cũng chỉ sử dụng chúng vào một vài trường hợp mà khả năng sập tiến trình không cao.

Không chỉ sử dụng hàm, mà các hàm gọi lại, bạn viết mã gì trong đó, và viết như thế nào để tương thích các hàm API này. Làm sao tôi có thể viết hết một loạt bài ở đây để hướng dẫn hết các kiến thức đó ở đây được.
 
Upvote 0
Bác có thể cho một ví dụ Function bất kỳ sử dụng "mã tạm thời" mà bác viết để em tham khảo được không? Tiêu hóa được hết đống kiến thức này chắc cũng ngốn cơ số thời gian ạ
Bạn chịu khó học một khóa C++ lập trình WinForm là sẽ hiểu hết cách xử lý window, xử lý message của window.
 
Upvote 0
Bạn chịu khó học một khóa C++ lập trình WinForm là sẽ hiểu hết cách xử lý window, xử lý message của window.
Một khóa không đủ đâu. Những cái thớt muốn học thuộc về cách sử dụng các hàm thư viện của Windows được VBA gọi theo function pointer (điển hình là API's)

Thớt muốn chọn con đường này thì phải có tính thật kiên nhẫn và theo lời khuyên của tác giả bài #2. Tôi chỉ cảnh báo rằng con đường này không phải chỉ để dân "rỗi rảnh thử chơi" mà phải có quyết tâm, phải bỏ công sức học tập và tìm hiểu.
Đầu tiên hết, phải biết "thử 12 khía cạnh" nghĩa là gì. Mới thử có 1 cái, thấy không được đã la làng đi hỏi thì cũng học được nhưng lâu lắm mới học đên nơi.

Chú: làm việc kiểu hackers là tốt nhất, thử đi thử lại hêt chiều này sang kiểu khác cho đến khi nào ra mới chịu thôi
 
Upvote 0
Một khóa không đủ đâu. Những cái thớt muốn học thuộc về cách sử dụng các hàm thư viện của Windows được VBA gọi theo function pointer (điển hình là API's)

Thớt muốn chọn con đường này thì phải có tính thật kiên nhẫn và theo lời khuyên của tác giả bài #2. Tôi chỉ cảnh báo rằng con đường này không phải chỉ để dân "rỗi rảnh thử chơi" mà phải có quyết tâm, phải bỏ công sức học tập và tìm hiểu.
Đầu tiên hết, phải biết "thử 12 khía cạnh" nghĩa là gì. Mới thử có 1 cái, thấy không được đã la làng đi hỏi thì cũng học được nhưng lâu lắm mới học đên nơi.

Chú: làm việc kiểu hackers là tốt nhất, thử đi thử lại hêt chiều này sang kiểu khác cho đến khi nào ra mới chịu thôi
Em không phủ nhận em là người không có tính cẩn thận . Tuy nhiên về Vấn đề này, cơ bản là em không biết cách nào để test. Thường em sẽ test sub callback sau đó lưu file, viết thử function và thử nghiệm đến khi hàm chạy, ứng dụng không treo, còn nếu treo ứng dụng là tịt. Nhờ bài 2 em đã rõ thêm 1 phần nào nguyên nhân đó rồi. Giờ tiếp tục tìm hiểu thêm xem như thế nào!
Bài đã được tự động gộp:

. Làm sao tôi có thể viết hết một loạt bài ở đây để hướng dẫn hết các kiến thức đó ở đây được.
Em không có ý định xin toàn bộ các bài bác viết function dạng này, em chỉ cần 1 vài ví dụ nhỏ bác sử dụng phương pháp trên để phần nào đó gỡ được lỗi sập ứng dụng thôi.
 
Upvote 0
Tôi để ví dụ ở trên, bạn chỉ cần đặt mã hoặc phương thức nào bạn tạo vào trong dòng "Mã cần chạy ở đây".
Bất kì dòng mã hay phương thức nào, thì các biến và các đối tượng phải truy cập bằng địa chỉ bộ nhớ để an toàn, thay vì truy cập các biến toàn cục, hoặc các đối tượng.
Bạn cần biết đến Hàm API RtlMoveMemory để truy cập địa chỉ các biến, các đối tượng.
Ví dụ bạn muốn truy cập Application để nhận đối tượng ActiveWindow, thì lấy địa chỉ bộ nhớ của Application với hàm VarPtr và truy cập với RtlMoveMemory. Lưu địa chỉ đó vào một địa chỉ khác để truy cập hoặc lưu vào cửa sổ với các API SetProp.

Không truy cập trực tiếp:

Ví dụ lưu địa chỉ vào cửa sổ:
Call SetProp(hwnd, "Application" ,VarPtr(Application))
Ví dụ truy cập trong hàm gọi lại (Callback Function), để truy cập gián tiếp:
JavaScript:
    Dim App as Object
    Set App = objectVarPointer(GetProp(hwnd, "Application"))
    App.ActiveWindow

JavaScript:
Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
Private Function objectVarPointer(ByVal varPointer As LongPtr) As Object
  Dim o As Object
  CopyMemory ByVal VarPtr(o), ByVal varPointer, LenB(varPointer)
  Set objectVarPointer = o
  CopyMemory ByVal VarPtr(o), 0&, LenB(varPointer)
End Function

Để nắm được các kiến thức cơ bản, bạn cần đọc hiểu các API: RtlMoveMemory, SetProp, GetProp, RemoveProp

Đến đây bạn không biết về hàm RtlMoveMemory thì không thể tiếp tục được. Bạn chỉ có thể viết mã ở mức dùng tạm là cho mã vào những ví dụ tôi đưa ở trên và chạy.
 
Lần chỉnh sửa cuối:
Upvote 0
Em không phủ nhận em là người không có tính cẩn thận . Tuy nhiên về Vấn đề này, cơ bản là em không biết cách nào để test. Thường em sẽ test sub callback sau đó lưu file, viết thử function và thử nghiệm đến khi hàm chạy, ứng dụng không treo, còn nếu treo ứng dụng là tịt. Nhờ bài 2 em đã rõ thêm 1 phần nào nguyên nhân đó rồi. Giờ tiếp tục tìm hiểu thêm xem như thế nào!
Bài đã được tự động gộp:


Em không có ý định xin toàn bộ các bài bác viết function dạng này, em chỉ cần 1 vài ví dụ nhỏ bác sử dụng phương pháp trên để phần nào đó gỡ được lỗi sập ứng dụng thôi.
Để học và sử dụng Win32 API trước hết bạn phải thành thạo C++ trước đã, dù gì tài liệu đều được diễn giải bằng C++ và hệ điều hành Windows được viết bằng C/C++. Tất nhiên việc này không hề đơn giản một chút nào.
Ví dụ: Hàm SetPropA.
Đọc tham số của hàm là hình dung ra được khi viết trên VBA sẽ như thế nào.
Ví dụ: Kiểu HWND của tham số hWnd thực chất là typedef cho kiểu LONG_PTR, từ đó suy ra kiểu LongPtr trên VBA.
1737729387765.png
1737729593421.png
 
Upvote 0
Web KT

Bài viết mới nhất

Back
Top Bottom