title: Why can't I `End` code while I'm subclassing without breaking everything?
body: <h3>Context</h3>
  <p>I've written some code in VBA to subclass a userform so that ultimately I can intercept <code>WM_TIMER</code> messages being dispatched to it. I'm doing this instead of specifying a TIMERPROC, as it allows me to use VBA's own error handling and calling methods to run callback functions. I'm using a userform rather than <code>Application.hWnd</code> because:</p>
  <ol>
  <li>I don't have to filter for my vs Excel/the host application's messages.</li>
  <li>There are far too many messages going through <code>Application.hWnd</code> to be able to subclass it in a slow interpreted language like VBA.</li>
  <li>When code execution is interrupted (pressing the stop button, or upon encountering an <code>End</code> statement), the userform vanishes all by itself - disconnecting any timers still sending messages.
  <ul>
  <li>Using the Application window, or worse, creating my own <a href="https://stackoverflow.com/q/4081334/6609896">message window</a> as I had previously been doing means the timers created with <code>SetTimer</code> continue to trigger my message window</li>
  </ul>
  </li>
  </ol>
  <p>It's all working fine, except I've found that occasionally when my code is up and running, and I press the reset/stop button, everything crashes.</p>
  <p><a href="https://i.stack.imgur.com/AVw1Y.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/AVw1Y.png" alt="reset button" /></a></p>
  <p>I'd prefer for my window to be un-subclassed and destroyed safely.</p>
  <hr />
  <p>I created the following to allow me to subclass a userform (<strong>no timers yet, the problem manifests itself just by subclassing</strong>):</p>
  <h3>Standard module: <code>WinAPI</code></h3>
  <p>I'm using the <a href="https://docs.microsoft.com/en-us/windows/win32/controls/subclassing-overview#subclassing-controls-using-comctl32dll-version-6" rel="nofollow noreferrer">new style of subclassing</a> because MSDN told me to, and in case I need to add more subclasses - shouldn't make a difference though.</p>
  <pre><code>Option Explicit
  
  Public Enum WindowsMessage 'As Long - for intellisense
      WM_TIMER = &amp;H113 'only care about this one
      '...
  End Enum
  
  Public Declare Function DefSubclassProc Lib &quot;comctl32.dll&quot; Alias &quot;#413&quot; ( _
                          ByVal hWnd As LongPtr, _
                          ByVal uMsg As WindowsMessage, _
                          ByVal wParam As LongPtr, _
                          ByVal lParam As LongPtr) As LongPtr
  
  Public Declare Function SetWindowSubclass Lib &quot;comctl32.dll&quot; Alias &quot;#410&quot; ( _
                          ByVal hWnd As LongPtr, _
                          ByVal pfnSubclass As LongPtr, _
                          ByVal uIdSubclass As LongPtr, _
                          Optional ByVal dwRefData As LongPtr) As Long
  
  Public Declare Function RemoveWindowSubclass Lib &quot;comctl32.dll&quot; Alias &quot;#412&quot; ( _
                          ByVal hWnd As LongPtr, _
                          ByVal pfnSubclass As LongPtr, _
                          ByVal uIdSubclass As LongPtr) As Long
  </code></pre>
  <p><em>For more WinAPI functions to help with debugging, like <code>SetTimer</code> and <code>Peek</code>/<code>PostMessage</code> use <a href="https://gist.github.com/Greedquest/1e021c51014606e1b35f2f4d552572e6" rel="nofollow noreferrer">this full version</a> of the module</em></p>
  <h3>Userform: <code>ModelessMessageWindow</code></h3>
  <p>I've got <code>showModal</code> set to <code>False</code>, but I never <code>.Show</code> so probably irrelevant</p>
  <pre><code>'@Folder(&quot;FirstLevelAPI&quot;)
  Option Explicit
  
  Private Type messageWindowData
      subClassIDs As New Dictionary '{proc:id}
  End Type
  Private this As messageWindowData
  
  #If VBA7 Then
      Private Declare PtrSafe Function IUnknown_GetWindow Lib &quot;shlwapi&quot; Alias &quot;#172&quot; (ByVal pIUnk As IUnknown, ByRef outHwnd As LongPtr) As Long
  #Else
      Private Declare Function IUnknown_GetWindow Lib &quot;shlwapi&quot; Alias &quot;#172&quot; (ByVal pIUnk As IUnknown, ByRef outHwnd As Long) As Long
  #End If
  
  #If VBA7 Then
      Public Property Get handle() As LongPtr
          IUnknown_GetWindow Me, handle
      End Property
  #Else
      Public Property Get handle() As Long
          IUnknown_GetWindow Me, handle
      End Property
  #End If
  
  Public Function tryCreate(ByRef outWindow As ModelessMessageWindow, Optional ByVal windowProc As LongPtr = 0, Optional ByVal data As LongPtr) As Boolean
      With New ModelessMessageWindow
          .Init
          If windowProc = 0 Then
              tryCreate = True
          Else
              tryCreate = .tryAddSubclass(windowProc, data)
          End If
          Set outWindow = .Self
      End With
  End Function
  
  Public Property Get Self() As ModelessMessageWindow
      Set Self = Me
  End Property
  
  Public Sub Init()
      'Need to run this for window to be able to receive messages
      'Me.Show
      'Me.Hide
  End Sub
  
  Public Function tryAddSubclass(ByVal subclassProc As LongPtr, Optional ByVal data As LongPtr) As Boolean
          
      Dim instanceID As Long
      'Only let one instance of each subclassProc per windowHandle
  
      If this.subClassIDs.Exists(subclassProc) Then
          instanceID = this.subClassIDs(subclassProc)
      Else
          instanceID = this.subClassIDs.Count
          this.subClassIDs(subclassProc) = instanceID
      End If
      
      If WinAPI.SetWindowSubclass(handle, subclassProc, instanceID, data) Then
          tryAddSubclass = True
      End If
  End Function
  
  '@Description(&quot;Remove any registered subclasses - returns True if all removed successfully&quot;)
  Public Function tryRemoveAllSubclasses() As Boolean
      
      Dim timerProc As Variant
      Dim result As Boolean
      result = True 'if no subclasses exist the we removed them nicely
      For Each timerProc In this.subClassIDs.Keys
          result = result And WinAPI.RemoveWindowSubclass(handle, timerProc, this.subClassIDs(timerProc)) &lt;&gt; 0
      Next timerProc
      this.subClassIDs.RemoveAll
      tryRemoveAllSubclasses = result
  End Function
  </code></pre>
  <hr />
  <p>I've discovered that the problem is caused by a <code>DoEvents</code> statement, which allows a reset-button press to interrupt code execution (without <code>DoEvents</code>, the button press is queued after any code has finished executing, and just destroys the Userform as expected, triggering Windows to remove the subclasses cleanly). The same problematic behaviour can be simulated with the <code>End</code> statement:</p>
  <h3>Standard module: <code>SubclassingTest</code></h3>
  <pre><code>'@Folder(&quot;Tests.Experiments&quot;)
  Option Explicit
  
  Public Function subclassProc(ByVal hWnd As LongPtr, ByVal uMsg As WindowsMessage, ByVal wParam As LongPtr, ByVal lParam As LongPtr, ByVal uIdSubclass As LongPtr, ByVal dwRefData As LongPtr) As LongPtr
      Debug.Print &quot;MSG #&quot;; uMsg 'will this even print, or have we interrupted repainting the thread?
      subclassProc = WinAPI.DefSubclassProc(hWnd, uMsg, wParam, lParam)
  End Function
  
  Sub createWindow()
      'get window and subclass it
      Static messageWindow As ModelessMessageWindow 'so it hovers around in memory
      Debug.Print &quot;Creating window&quot;
      If Not ModelessMessageWindow.tryCreate(messageWindow, AddressOf subclassProc) Then
          Debug.Print &quot;Couldn't get/subclass window&quot;
          Exit Sub
      End If
  End Sub
  
  Sub nukeEverything()
      End
  End Sub
  </code></pre>
  <p>After running <code>createWindow</code>, try pressing the reset button; it works fine and nothing crashes, and I get these messages printed:</p>
  <pre><code>MSG # 799 'WM_APPCOMMAND +3 - after createWindow but before pressing the button
  MSG # 528 'WM_PARENTNOTIFY  
  MSG # 144 'WM_MYSTERY +5 - IDK what this is
  MSG # 2   'WM_DESTROY
  MSG # 130 'WM_NCDESTROY
  </code></pre>
  <p>However if I instead run <code>nukeEverything</code> (or have a <code>DoEvents</code> loop providing an entry point for the reset button), I get a crash.</p>
  <h2>What I don't understand...</h2>
  <p>...is why ending stuff mid-execution (either with <code>DoEvents</code> allowing a reset button press through, or via the <code>End</code> statement) is different from the asynchronous approach. I've checked and the <code>AddressOf</code> the callback isn't affected by <code>End</code>*:</p>
  <pre><code>Sub checkPointer() 'always prints the same
      Debug.Print &quot;Address: &quot;; VBA.CLngPtr(AddressOf subclassProc)
      End
  End Sub
  </code></pre>
  <p>i.e. the crash isn't the result of my SUBCLASSPROC function pointer becoming invalid. And of course <code>End</code> doesn't crash Excel when I'm not subclassing windows. So what exactly is causing the crash? Or is there a better approach (I know I can achieve very similar results using TIMERPROCS, but I'm curious to understand why this error is happening and so don't want to resort to those)</p>
  <hr />
  <p>*It has been suggested in the comments that perhaps the function pointer just gets assigned the same address every time, making it appear to remain valid, but it is indeed being destroyed each time I run <code>End</code> and that's causing the crash (when Windows tries to invoke the SUBCLASSPROC). However I don't think this is true; if you create a timer with a TIMERPROC callback set, then pressing the reset button or running <code>NukeEverything</code> does not stop Windows continuing to run the callback. The callback function does remain valid between synchronous/asynchronous state losses, so I imagine my SUBCLASSPROC should too.</p>