- '--------------------------------------------------------------------------------------- 
- ' Module      : cUSB_Detection 
- ' DateTime    : 19/06/2008 18:17 
- ' Author      : Cobein 
- ' Mail        : cobein27@hotmail.com 
- ' WebPage     : http://cobein27.googlepages.com/vb6 
- ' Purpose     : Simple USB device detection 
- ' Usage       : At your own risk 
- ' Requirements: None 
- ' Distribution: You can freely use this code in your own 
- '               applications, but you may not reproduce 
- '               or publish this code on any web site, 
- '               online service, or distribute as source 
- '               on any media without express permission. 
- ' 
- ' Reference   : http://msdn.microsoft.com/en-us/library/aa363205(VS.85).aspx 
- ' 
- ' TODO        : Expand capabilities to support all WM_DEVICECHANGE message params and types 
- ' 
- ' Important   : The class is pre-filtering drives by bustype = USB 
- ' 
- ' History     : 19/06/2008 First Cut.................................................... 
- '--------------------------------------------------------------------------------------- 
- Option Explicit 
-   
- 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 
-   
- 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 
-   
- Private Const IOCTL_STORAGE_BASE    As Long = &H2D 
- Private Const METHOD_BUFFERED       As Long = 0 
- Private Const FILE_ANY_ACCESS       As Long = 0 
- Private Const GENERIC_READ          As Long = &H80000000 
- Private Const FILE_SHARE_READ       As Long = &H1 
- Private Const OPEN_EXISTING         As Long = 3 
- Private Const FILE_SHARE_WRITE      As Long = &H2 
- Private Const INVALID_HANDLE_VALUE  As Long = (-1) 
-   
- Private Const WM_DEVICECHANGE       As Long = &H219 
- Private Const DBT_DEVICEARRIVAL     As Long = &H8000& 
- Private Const DBT_DEVICEREMOVECOMPLETE As Long = &H8004& 
- Private Const DBT_DEVTYP_VOLUME     As Long = &H2 
- Private Const DBTF_MEDIA            As Long = &H1 
-   
- Private Enum eThunkType 
-     SubclassThunk = 0 
-     HookThunk = 1 
-     CallbackThunk = 2 
- End Enum 
-   
- Private Enum eMsgWhen 
-     MSG_BEFORE = 1 
-     MSG_AFTER = 2 
-     MSG_BEFORE_AFTER = MSG_BEFORE Or MSG_AFTER 
- End Enum 
-   
- Enum eDriveType 
-     DRIVE_UNKNOWN 
-     DRIVE_NO_ROOT_DIR 
-     DRIVE_REMOVABLE 
-     DRIVE_FIXED 
-     DRIVE_REMOTE 
-     DRIVE_CDROM 
-     DRIVE_RAMDISK 
- End Enum 
-   
- Private Enum STORAGE_PROPERTY_ID 
-     StorageDeviceProperty = 0 
-     StorageAdapterProperty 
- End Enum 
-   
- Private Enum STORAGE_QUERY_TYPE 
-     PropertyStandardQuery = 0 
-     PropertyExistsQuery 
-     PropertyMaskQuery 
-     PropertyQueryMaxDefined 
- End Enum 
-   
- Enum STORAGE_BUS_TYPE 
-     BusTypeUnknown = 0 
-     BusTypeScsi 
-     BusTypeAtapi 
-     BusTypeAta 
-     BusType1394 
-     BusTypeSsa 
-     BusTypeFibre 
-     BusTypeUsb 
-     BusTypeRAID 
-     BusTypeMaxReserved = &H7F 
- End Enum 
-   
- Private Type DEV_BROADCAST_HDR 
-     dbch_size                       As Long 
-     dbch_devicetype                 As Long 
-     dbch_reserved                   As Long 
- End Type 
-   
- Private Type DEV_BROADCAST_VOLUME 
-    dbcv_size                        As Long 
-    dbcv_devicetype                  As Long 
-    dbcv_reserved                    As Long 
-    dbcv_unitmask                    As Long 
-    dbcv_flags                       As Integer 
- End Type 
-   
- Private Type STORAGE_PROPERTY_QUERY 
-     PropertyId                      As STORAGE_PROPERTY_ID 
-     QueryType                       As STORAGE_QUERY_TYPE 
-     AdditionalParameters(0)         As Byte 
- End Type 
-   
- Private Type OVERLAPPED 
-     Internal                        As Long 
-     InternalHigh                    As Long 
-     offset                          As Long 
-     OffsetHigh                      As Long 
-     hEvent                          As Long 
- End Type 
-   
- Private Type STORAGE_DEVICE_DESCRIPTOR 
-     Version                         As Long 
-     Size                            As Long 
-     DeviceType                      As Byte 
-     DeviceTypeModifier              As Byte 
-     RemovableMedia                  As Byte 
-     CommandQueueing                 As Byte 
-     VendorIdOffset                  As Long 
-     ProductIdOffset                 As Long 
-     ProductRevisionOffset           As Long 
-     SerialNumberOffset              As Long 
-     BusType                         As STORAGE_BUS_TYPE 
-     RawPropertiesLength             As Long 
-     RawDeviceProperties(0)          As Byte 
- End Type 
-   
- 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 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 
- Private Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" (ByVal lpFileName As String, ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, lpSecurityAttributes As Any, ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) As Long 
- Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long 
- Private Declare Function DeviceIoControl Lib "kernel32" (ByVal hDevice As Long, ByVal dwIoControlCode As Long, lpInBuffer As Any, ByVal nInBufferSize As Long, lpOutBuffer As Any, ByVal nOutBufferSize As Long, lpBytesReturned As Long, lpOverlapped As OVERLAPPED) 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 DestroyWindow Lib "user32" (ByVal hwnd As Long) As Long 
- Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDst As Any, pSrc As Any, ByVal ByteLen As Long) 
- Private Declare Function GetDriveType Lib "kernel32" Alias "GetDriveTypeA" (ByVal nDrive As String) As Long 
-   
- Private c_lhWnd                     As Long 
- 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 
-   
- Public Event DriveArrival(ByVal sDrive As String, ByVal lDriveType As eDriveType) 
- Public Event DriveRemoval(ByVal sDrive As String) 
-   
- Private Sub Class_Initialize() 
-   
-     c_lhWnd = CreateWindowEx(0, "STATIC", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) 
-   
-     If Not c_lhWnd = 0 Then 
-         If ssc_Subclass(c_lhWnd, , 1) Then 
-             Call ssc_AddMsg(c_lhWnd, WM_DEVICECHANGE, MSG_AFTER) 
-         Else 
-             Call DestroyWindow(c_lhWnd): c_lhWnd = 0 
-         End If 
-     End If 
- End Sub 
-   
- Private Sub Class_Terminate() 
-     Call ssc_Terminate 
-     Call DestroyWindow(c_lhWnd) 
-     c_lhWnd = 0 
- End Sub 
-   
- '-The following routines are exclusively for the ssc_subclass routines---------------------------- 
- Private Function ssc_Subclass(ByVal lng_hWnd As Long, _ 
-        Optional ByVal lParamUser As Long = 0, _ 
-        Optional ByVal nOrdinal As Long = 1, _ 
-        Optional ByVal oCallback As Object = Nothing, _ 
-        Optional ByVal bIdeSafety As Boolean = True, _ 
-        Optional ByVal bUnicode As Boolean = False) As Boolean 'Subclass the specified window handle 
-   
-     ' \\LaVolpe - reworked routine a bit, revised the ASM to allow auto-unsubclass on WM_DESTROY 
-     Dim z_Sc(0 To IDX_UNICODE) As Long                 'Thunk machine-code initialised here 
-     Const CODE_LEN      As Long = 4 * IDX_UNICODE      'Thunk length in bytes 
-   
-     Const MEM_LEN       As Long = CODE_LEN + (8 * (MSG_ENTRIES))  'Bytes to allocate per thunk, data + code + msg tables 
-     Const PAGE_RWX      As Long = &H40&                'Allocate executable memory 
-     Const MEM_COMMIT    As Long = &H1000&              'Commit allocated memory 
-     Const MEM_RELEASE   As Long = &H8000&              'Release allocated memory flag 
-     Const IDX_EBMODE    As Long = 3                    'Thunk data index of the EbMode function address 
-     Const IDX_CWP       As Long = 4                    'Thunk data index of the CallWindowProc function address 
-     Const IDX_SWL       As Long = 5                    'Thunk data index of the SetWindowsLong function address 
-     Const IDX_FREE      As Long = 6                    'Thunk data index of the VirtualFree function address 
-     Const IDX_BADPTR    As Long = 7                    'Thunk data index of the IsBadCodePtr function address 
-     Const IDX_OWNER     As Long = 8                    'Thunk data index of the Owner object's vTable address 
-     Const IDX_CALLBACK  As Long = 10                   'Thunk data index of the callback method address 
-     Const IDX_EBX       As Long = 16                   'Thunk code patch index of the thunk data 
-     Const GWL_WNDPROC   As Long = -4                   'SetWindowsLong WndProc index 
-     Const WNDPROC_OFF   As Long = &H38                 'Thunk offset to the WndProc execution address 
-     Const SUB_NAME      As String = "ssc_Subclass"     'This routine's name 
-   
-     Dim nAddr         As Long 
-     Dim nID           As Long 
-     Dim nMyID         As Long 
-   
-     If IsWindow(lng_hWnd) = 0 Then                      'Ensure the window handle is valid 
-         zError SUB_NAME, "Invalid window handle" 
-         Exit Function 
-     End If 
-   
-     nMyID = GetCurrentProcessId                         'Get this process's ID 
-     GetWindowThreadProcessId lng_hWnd, nID              'Get the process ID associated with the window handle 
-     If nID <> nMyID Then                                'Ensure that the window handle doesn't belong to another process 
-         zError SUB_NAME, "Window handle belongs to another process" 
-         Exit Function 
-     End If 
-   
-     If oCallback Is Nothing Then Set oCallback = Me     'If the user hasn't specified the callback owner 
-   
-     nAddr = zAddressOf(oCallback, nOrdinal)             'Get the address of the specified ordinal method 
-     If nAddr = 0 Then                                   'Ensure that we've found the ordinal method 
-         zError SUB_NAME, "Callback method not found" 
-         Exit Function 
-     End If 
-   
-     z_ScMem = VirtualAlloc(0, MEM_LEN, MEM_COMMIT, PAGE_RWX) 'Allocate executable memory 
-   
-     If z_ScMem <> 0 Then                                  'Ensure the allocation succeeded 
-   
-         If z_scFunk Is Nothing Then Set z_scFunk = New Collection 'If this is the first time through, do the one-time initialization 
-         On Error GoTo CatchDoubleSub                              'Catch double subclassing 
-         z_scFunk.Add z_ScMem, "h" & lng_hWnd                    'Add the hWnd/thunk-address to the collection 
-         On Error GoTo 0 
-   
-         ' \\Tai Chi Minh Ralph Eastwood - fixed bug where the MSG_AFTER was not being honored 
-         ' \\LaVolpe - modified thunks to allow auto-unsubclassing when WM_DESTROY received 
-         z_Sc(14) = &HD231C031: z_Sc(15) = &HBBE58960: z_Sc(16) = &H12345678: z_Sc(17) = &HF63103FF: z_Sc(18) = &H750C4339: z_Sc(19) = &H7B8B4A38: z_Sc(20) = &H95E82C: z_Sc(21) = &H7D810000: z_Sc(22) = &H228&: z_Sc(23) = &HC70C7500: z_Sc(24) = &H20443: z_Sc(25) = &H5E90000: z_Sc(26) = &H39000000: z_Sc(27) = &HF751475: z_Sc(28) = &H25E8&: z_Sc(29) = &H8BD23100: z_Sc(30) = &H6CE8307B: z_Sc(31) = &HFF000000: z_Sc(32) = &H10C2610B: z_Sc(33) = &HC53FF00: z_Sc(34) = &H13D&: z_Sc(35) = &H85BE7400: z_Sc(36) = &HE82A74C0: z_Sc(37) = &H2&: z_Sc(38) = &H75FFE5EB: z_Sc(39) = &H2C75FF30: z_Sc(40) = &HFF2875FF: z_Sc(41) = &H73FF2475: z_Sc(42) = &H1053FF24: z_Sc(43) = &H811C4589: z_Sc(44) = &H13B&: z_Sc(45) = &H39727500: 
-         z_Sc(46) = &H6D740473: z_Sc(47) = &H2473FF58: z_Sc(48) = &HFFFFFC68: z_Sc(49) = &H873FFFF: z_Sc(50) = &H891453FF: z_Sc(51) = &H7589285D: z_Sc(52) = &H3045C72C: z_Sc(53) = &H8000&: z_Sc(54) = &H8920458B: z_Sc(55) = &H4589145D: z_Sc(56) = &HC4816124: z_Sc(57) = &H4&: z_Sc(58) = &H8B1862FF: z_Sc(59) = &H853AE30F: z_Sc(60) = &H810D78C9: z_Sc(61) = &H4C7&: z_Sc(62) = &H28458B00: z_Sc(63) = &H2975AFF2: z_Sc(64) = &H2873FF52: z_Sc(65) = &H5A1C53FF: z_Sc(66) = &H438D1F75: z_Sc(67) = &H144D8D34: z_Sc(68) = &H1C458D50: z_Sc(69) = &HFF3075FF: z_Sc(70) = &H75FF2C75: z_Sc(71) = &H873FF28: z_Sc(72) = &HFF525150: z_Sc(73) = &H53FF2073: z_Sc(74) = &HC328C328 
-   
-         z_Sc(IDX_EBX) = z_ScMem                                                 'Patch the thunk data address 
-         z_Sc(IDX_INDEX) = lng_hWnd                                               'Store the window handle in the thunk data 
-         z_Sc(IDX_BTABLE) = z_ScMem + CODE_LEN                                   'Store the address of the before table in the thunk data 
-         z_Sc(IDX_ATABLE) = z_ScMem + CODE_LEN + ((MSG_ENTRIES + 1) * 4)         'Store the address of the after table in the thunk data 
-         z_Sc(IDX_OWNER) = ObjPtr(oCallback)                                     'Store the callback owner's object address in the thunk data 
-         z_Sc(IDX_CALLBACK) = nAddr                                              'Store the callback address in the thunk data 
-         z_Sc(IDX_PARM_USER) = lParamUser                                        'Store the lParamUser callback parameter in the thunk data 
-   
-         ' \\LaVolpe - validate unicode request & cache unicode usage 
-         If bUnicode Then bUnicode = (IsWindowUnicode(lng_hWnd) <> 0&) 
-         z_Sc(IDX_UNICODE) = bUnicode                                            'Store whether the window is using unicode calls or not 
-   
-         ' \\LaVolpe - added extra parameter "bUnicode" to the zFnAddr calls 
-         z_Sc(IDX_FREE) = zFnAddr("kernel32", "VirtualFree", bUnicode)           'Store the VirtualFree function address in the thunk data 
-         z_Sc(IDX_BADPTR) = zFnAddr("kernel32", "IsBadCodePtr", bUnicode)        'Store the IsBadCodePtr function address in the thunk data 
-   
-         Debug.Assert zInIDE 
-         If bIdeSafety = True And z_IDEflag = 1 Then                             'If the user wants IDE protection 
-             z_Sc(IDX_EBMODE) = zFnAddr("vba6", "EbMode", bUnicode)                'Store the EbMode function address in the thunk data 
-         End If 
-   
-         ' \\LaVolpe - use ANSI for non-unicode usage, else use WideChar calls 
-         If bUnicode Then 
-             z_Sc(IDX_CWP) = zFnAddr("user32", "CallWindowProcW", bUnicode)          'Store CallWindowProc function address in the thunk data 
-             z_Sc(IDX_SWL) = zFnAddr("user32", "SetWindowLongW", bUnicode)           'Store the SetWindowLong function address in the thunk data 
-             z_Sc(IDX_UNICODE) = 1 
-             RtlMoveMemory z_ScMem, VarPtr(z_Sc(0)), CODE_LEN                        'Copy the thunk code/data to the allocated memory 
-             nAddr = SetWindowLongW(lng_hWnd, GWL_WNDPROC, z_ScMem + WNDPROC_OFF)    'Set the new WndProc, return the address of the original WndProc 
-         Else 
-             z_Sc(IDX_CWP) = zFnAddr("user32", "CallWindowProcA", bUnicode)          'Store CallWindowProc function address in the thunk data 
-             z_Sc(IDX_SWL) = zFnAddr("user32", "SetWindowLongA", bUnicode)           'Store the SetWindowLong function address in the thunk data 
-             RtlMoveMemory z_ScMem, VarPtr(z_Sc(0)), CODE_LEN                        'Copy the thunk code/data to the allocated memory 
-             nAddr = SetWindowLongA(lng_hWnd, GWL_WNDPROC, z_ScMem + WNDPROC_OFF)    'Set the new WndProc, return the address of the original WndProc 
-         End If 
-         If nAddr = 0 Then                                                           'Ensure the new WndProc was set correctly 
-             zError SUB_NAME, "SetWindowLong failed, error #" & Err.LastDllError 
-             GoTo ReleaseMemory 
-         End If 
-         'Store the original WndProc address in the thunk data 
-         RtlMoveMemory z_ScMem + IDX_WNDPROC * 4, VarPtr(nAddr), 4&              ' z_Sc(IDX_WNDPROC) = nAddr 
-         ssc_Subclass = True                                                     'Indicate success 
-     Else 
-         zError SUB_NAME, "VirtualAlloc failed, error: " & Err.LastDllError 
-     End If 
-   
-     Exit Function                                                             'Exit ssc_Subclass 
-   
- CatchDoubleSub: 
-     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 
-   
- '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" 
-     End If 
-   
- Bail: 
-     z_ScMem = nBase                                                           'Restore the value of z_ScMem 
- End Sub 
-