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

Liên hệ QC
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
332
Được thích
154
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
Web KT

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

Back
Top Bottom