Private z_IDEflag As Long 'Flag indicating we are in IDE
Private z_ScMem As Long 'Thunk base address
Private z_scFunk As Collection 'hWnd/thunk-address collection
Private z_hkFunk As Collection 'hook/thunk-address collection
Private z_cbFunk As Collection 'callback/thunk-address collection
Private Const IDX_INDEX As Long = 2 'index of the subclassed hWnd OR hook type
Private Const IDX_CALLBACKORDINAL As Long = 22 ' Ubound(callback thunkdata)+1, index of the callback
' Declarations:
Private Declare Sub RtlMoveMemory Lib "kernel32" (ByVal Destination As Long, ByVal Source As Long, ByVal Length As Long)
Private Declare Function IsBadCodePtr Lib "kernel32" (ByVal lpfn As Long) As Long
Private Declare Function VirtualAlloc Lib "kernel32" (ByVal lpAddress As Long, ByVal dwSize As Long, ByVal flAllocationType As Long, ByVal flProtect As Long) As Long
Private Declare Function VirtualFree Lib "kernel32" (ByVal lpAddress As Long, ByVal dwSize As Long, ByVal dwFreeType As Long) As Long
Private Declare Function GetModuleHandleA Lib "kernel32" (ByVal lpModuleName As String) As Long
Private Declare Function GetModuleHandleW Lib "kernel32" (ByVal lpModuleName As Long) As Long
Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
Private Enum eThunkType
SubclassThunk = 0
HookThunk = 1
CallbackThunk = 2
End Enum
'-Selfsub specific declarations----------------------------------------------------------------------------
Private Enum eMsgWhen 'When to callback
MSG_BEFORE = 1 'Callback before the original WndProc
MSG_AFTER = 2 'Callback after the original WndProc
MSG_BEFORE_AFTER = MSG_BEFORE Or MSG_AFTER 'Callback before and after the original WndProc
End Enum
Private Const IDX_WNDPROC As Long = 9 'Thunk data index of the original WndProc
Private Const IDX_BTABLE As Long = 11 'Thunk data index of the Before table
Private Const IDX_ATABLE As Long = 12 'Thunk data index of the After table
Private Const IDX_PARM_USER As Long = 13 'Thunk data index of the User-defined callback parameter data index
Private Const IDX_UNICODE As Long = 75 'Must be Ubound(subclass thunkdata)+1; index for unicode support
Private Const ALL_MESSAGES As Long = -1 'All messages callback
Private Const MSG_ENTRIES As Long = 32 'Number of msg table entries. Set to 1 if using ALL_MESSAGES for all subclassed windows
' \\LaVolpe - Added non-ANSI version API calls
Private Declare Function CallWindowProcA Lib "user32" (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Declare Function CallWindowProcW Lib "user32" (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Declare Function GetCurrentProcessId Lib "kernel32" () As Long
Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As Long, lpdwProcessId As Long) As Long
Private Declare Function IsWindow Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function IsWindowUnicode Lib "user32.dll" (ByVal hwnd As Long) As Long
Private Declare Function SendMessageA Lib "user32.dll" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByRef lParam As Any) As Long
Private Declare Function SendMessageW Lib "user32.dll" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByRef lParam As Any) As Long
Private Declare Function SetWindowLongA Lib "user32" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Declare Function SetWindowLongW Lib "user32" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
'* nOrdinal - Optional, ordinal index of the callback procedure. 1 = last private method, 2 = second last private method, etc.
'* oCallback - Optional, the object that will receive the callback. If undefined, callbacks are sent to this object's instance
'* bIdeSafety - Optional, enable/disable IDE safety measures. NB: you should really only disable IDE safety in a UserControl for design-time subclassing
'* bUnicode - Optional, if True, Unicode API calls will be made to the window vs ANSI calls
zError SUB_NAME, "Window handle is already subclassed"
ReleaseMemory:
VirtualFree z_ScMem, 0, MEM_RELEASE 'ssc_Subclass has failed after memory allocation, so release the memory
End Function
'Terminate all subclassing
Private Sub ssc_Terminate()
' can be made public. Releases all subclassing
' can be removed and zTerminateThunks can be called directly
zTerminateThunks SubclassThunk
End Sub
'UnSubclass the specified window handle
Private Sub ssc_UnSubclass(ByVal lng_hWnd As Long)
' can be made public. Releases a specific subclass
' can be removed and zUnThunk can be called directly
zUnThunk lng_hWnd, SubclassThunk
End Sub
'Add the message value to the window handle's specified callback table
Private Sub ssc_AddMsg(ByVal lng_hWnd As Long, ByVal uMsg As Long, Optional ByVal When As eMsgWhen = MSG_AFTER)
' Note: can be removed if not needed and zAddMsg can be called directly
If IsBadCodePtr(zMap_VFunction(lng_hWnd, SubclassThunk)) = 0 Then 'Ensure that the thunk hasn't already released its memory
If When And MSG_BEFORE Then 'If the message is to be added to the before original WndProc table...
zAddMsg uMsg, IDX_BTABLE 'Add the message to the before table
End If
If When And MSG_AFTER Then 'If message is to be added to the after original WndProc table...
zAddMsg uMsg, IDX_ATABLE 'Add the message to the after table
End If
End If
End Sub
'Delete the message value from the window handle's specified callback table
Private Sub ssc_DelMsg(ByVal lng_hWnd As Long, ByVal uMsg As Long, Optional ByVal When As eMsgWhen = MSG_AFTER)
' Note: can be removed if not needed and zDelMsg can be called directly
If IsBadCodePtr(zMap_VFunction(lng_hWnd, SubclassThunk)) = 0 Then 'Ensure that the thunk hasn't already released its memory
If When And MSG_BEFORE Then 'If the message is to be deleted from the before original WndProc table...
zDelMsg uMsg, IDX_BTABLE 'Delete the message from the before table
End If
If When And MSG_AFTER Then 'If the message is to be deleted from the after original WndProc table...
zDelMsg uMsg, IDX_ATABLE 'Delete the message from the after table
End If
End If
End Sub
'Call the original WndProc
Private Function ssc_CallOrigWndProc(ByVal lng_hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
' Note: can be removed if you do not use this function inside of your window procedure
If IsBadCodePtr(zMap_VFunction(lng_hWnd, SubclassThunk)) = 0 Then 'Ensure that the thunk hasn't already released its memory
If zData(IDX_UNICODE) Then
ssc_CallOrigWndProc = CallWindowProcW(zData(IDX_WNDPROC), lng_hWnd, uMsg, wParam, lParam) 'Call the original WndProc of the passed window handle parameter
Else
ssc_CallOrigWndProc = CallWindowProcA(zData(IDX_WNDPROC), lng_hWnd, uMsg, wParam, lParam) 'Call the original WndProc of the passed window handle parameter
End If
End If
End Function
'Get the subclasser lParamUser callback parameter
Private Function zGet_lParamUser(ByVal hWnd_Hook_ID As Long, vType As eThunkType) As Long
'Note: can be removed if you never need to retrieve/update your user-defined paramter. See ssc_Subclass
If vType <> CallbackThunk Then
If IsBadCodePtr(zMap_VFunction(hWnd_Hook_ID, vType)) = 0 Then 'Ensure that the thunk hasn't already released its memory
zGet_lParamUser = zData(IDX_PARM_USER) 'Get the lParamUser callback parameter
End If
End If
End Function
'Let the subclasser lParamUser callback parameter
Private Sub zSet_lParamUser(ByVal hWnd_Hook_ID As Long, vType As eThunkType, newValue As Long)
'Note: can be removed if you never need to retrieve/update your user-defined paramter. See ssc_Subclass
If vType <> CallbackThunk Then
If IsBadCodePtr(zMap_VFunction(hWnd_Hook_ID, vType)) = 0 Then 'Ensure that the thunk hasn't already released its memory
zData(IDX_PARM_USER) = newValue 'Set the lParamUser callback parameter
End If
End If
End Sub
'Add the message to the specified table of the window handle
Private Sub zAddMsg(ByVal uMsg As Long, ByVal nTable As Long)
Dim nCount As Long 'Table entry count
Dim nBase As Long 'Remember z_ScMem
Dim i As Long 'Loop index
nBase = z_ScMem 'Remember z_ScMem so that we can restore its value on exit
z_ScMem = zData(nTable) 'Map zData() to the specified table
If uMsg = ALL_MESSAGES Then 'If ALL_MESSAGES are being added to the table...
nCount = ALL_MESSAGES 'Set the table entry count to ALL_MESSAGES
Else
nCount = zData(0) 'Get the current table entry count
If nCount >= MSG_ENTRIES Then 'Check for message table overflow
zError "zAddMsg", "Message table overflow. Either increase the value of Const MSG_ENTRIES or use ALL_MESSAGES instead of specific message values"
GoTo Bail
End If
For i = 1 To nCount 'Loop through the table entries
If zData(i) = 0 Then 'If the element is free...
zData(i) = uMsg 'Use this element
GoTo Bail 'Bail
ElseIf zData(i) = uMsg Then 'If the message is already in the table...
GoTo Bail 'Bail
End If
Next i 'Next message table entry
nCount = i 'On drop through: i = nCount + 1, the new table entry count
zData(nCount) = uMsg 'Store the message in the appended table entry
End If
zData(0) = nCount 'Store the new table entry count
Bail:
z_ScMem = nBase 'Restore the value of z_ScMem
End Sub
'Delete the message from the specified table of the window handle
Private Sub zDelMsg(ByVal uMsg As Long, ByVal nTable As Long)
Dim nCount As Long 'Table entry count
Dim nBase As Long 'Remember z_ScMem
Dim i As Long 'Loop index
nBase = z_ScMem 'Remember z_ScMem so that we can restore its value on exit
z_ScMem = zData(nTable) 'Map zData() to the specified table
If uMsg = ALL_MESSAGES Then 'If ALL_MESSAGES are being deleted from the table...
zData(0) = 0 'Zero the table entry count
Else
nCount = zData(0) 'Get the table entry count
For i = 1 To nCount 'Loop through the table entries
If zData(i) = uMsg Then 'If the message is found...
zData(i) = 0 'Null the msg value -- also frees the element for re-use
GoTo Bail 'Bail
End If
Next i 'Next message table entry
zError "zDelMsg", "Message &H" & Hex$(uMsg) & " not found in table"
Private Sub zUnThunk(ByVal thunkID As Long, ByVal vType As eThunkType)
' Releases a specific subclass, hook or callback
' thunkID depends on vType:
' - Subclassing: the hWnd of the window subclassed
' - Hooking: the hook type created
' - Callbacks: the ordinal of the callback
Const IDX_SHUTDOWN As Long = 1
Const MEM_RELEASE As Long = &H8000& 'Release allocated memory flag
If zMap_VFunction(thunkID, vType) Then
Select Case vType
Case SubclassThunk
If IsBadCodePtr(z_ScMem) = 0 Then 'Ensure that the thunk hasn't already released its memory
zData(IDX_SHUTDOWN) = 1 'Set the shutdown indicator
zDelMsg ALL_MESSAGES, IDX_BTABLE 'Delete all before messages
zDelMsg ALL_MESSAGES, IDX_ATABLE 'Delete all after messages
'\\LaVolpe - Force thunks to replace original window procedure handle. Without this, app can crash when a window is subclassed multiple times simultaneously
If zData(IDX_UNICODE) Then 'Force window procedure handle to be replaced
SendMessageW thunkID, 0&, 0&, ByVal 0&
Else
SendMessageA thunkID, 0&, 0&, ByVal 0&
End If
End If
z_scFunk.Remove "h" & thunkID 'Remove the specified thunk from the collection
Case HookThunk
If IsBadCodePtr(z_ScMem) = 0 Then 'Ensure that the thunk hasn't already released its memory
zData(IDX_SHUTDOWN) = 1 'Set the shutdown indicator
zData(IDX_ATABLE) = 0 ' want no more After messages
zData(IDX_BTABLE) = 0 ' want no more Before messages
End If
z_hkFunk.Remove "h" & thunkID 'Remove the specified thunk from the collection
Case CallbackThunk
If IsBadCodePtr(z_ScMem) = 0 Then 'Ensure that the thunk hasn't already released its memory