@@ -22,7 +22,7 @@ static const GUID CLSID_OpenShellStartButtonTap =
2222{ 0x7d15741f , 0x2f3b , 0x4971 , { 0xb8 , 0x91 , 0x6a , 0x5d , 0x42 , 0xd7 , 0x1a , 0x34 } };
2323
2424static const UINT WM_OS_STARTBUTTON_APPLY = WM_APP + 0x35B ;
25- static const UINT WM_OS_STARTBUTTON_DESTROY = WM_APP + 0x35C ;
25+ static const UINT WM_OS_STARTBUTTON_SHUTDOWN = WM_APP + 0x35C ;
2626
2727static volatile LONG g_StartButtonActive = 0 ;
2828static volatile LONG g_ConnectStarted = 0 ;
@@ -175,47 +175,72 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal
175175
176176 m_Site = site;
177177
178- // InitializeXamlDiagnosticsEx loads the TAP DLL by path even though
179- // StartMenuDLL is already loaded. Balance that injection reference so
180- // Open-Shell can unload and initialize the DLL again after an explicit Exit.
181- if (!m_InjectionReferenceBalanced)
182- {
183- HMODULE module = GetThisModule ();
184- if (module )
185- {
186- FreeLibrary (module );
187- m_InjectionReferenceBalanced = true ;
188- }
189- }
190-
178+ // Keep the module reference acquired by InitializeXamlDiagnosticsEx for
179+ // the whole TAP session. It is balanced only after the callback and all
180+ // XAML/COM references have been released. StartMenuDLL has its own module
181+ // reference while Open-Shell is active, so balancing the diagnostics
182+ // reference during teardown cannot unload code that is still executing.
183+ //
191184 // Exit may race a connection attempt. Do not attach a new callback after
192185 // shutdown has already begun.
193186 if (!InterlockedCompareExchange (&g_StartButtonActive, 0 , 0 ))
194187 {
195188 m_Site.Release ();
189+ BalanceInjectionReference ();
196190 InterlockedExchange (&g_ConnectStarted, 0 );
197191 return S_OK ;
198192 }
199193
200194 HRESULT hr = site->QueryInterface (__uuidof (IVisualTreeService), (void **)&m_Visual);
201195 if (FAILED (hr) || !m_Visual)
196+ {
197+ m_Visual.Release ();
198+ m_Site.Release ();
199+ BalanceInjectionReference ();
200+ InterlockedExchange (&g_ConnectStarted, 0 );
202201 return hr;
202+ }
203203
204204 if (!CreateDispatchWindow ())
205- return HRESULT_FROM_WIN32 (GetLastError ());
206-
207- AcquireSRWLockExclusive (&g_TapLock);
208- g_Tap = this ;
209- ReleaseSRWLockExclusive (&g_TapLock);
205+ {
206+ DWORD error = GetLastError ();
207+ m_Visual.Release ();
208+ m_Site.Release ();
209+ BalanceInjectionReference ();
210+ InterlockedExchange (&g_ConnectStarted, 0 );
211+ return HRESULT_FROM_WIN32 (error);
212+ }
210213
211214 // Advise replays the existing tree. OnVisualTreeChange only records
212215 // element handles; all property access is dispatched afterwards.
213216 hr = m_Visual->AdviseVisualTreeChange (static_cast <IVisualTreeServiceCallback*>(this ));
214217 if (SUCCEEDED (hr))
215218 {
216219 m_Advised = true ;
220+
221+ // Publish the TAP only after the subscription is fully established.
222+ // Otherwise an Advise failure can leave g_Tap pointing at an object
223+ // that the XAML runtime is about to release.
224+ AcquireSRWLockExclusive (&g_TapLock);
225+ g_Tap = this ;
226+ ReleaseSRWLockExclusive (&g_TapLock);
227+
217228 RequestApply (false );
218229 }
230+ else
231+ {
232+ if (m_Dispatch)
233+ {
234+ HWND dispatch = m_Dispatch;
235+ m_Dispatch = NULL ;
236+ SetWindowLongPtr (dispatch, GWLP_USERDATA , 0 );
237+ DestroyWindow (dispatch);
238+ }
239+ m_Visual.Release ();
240+ m_Site.Release ();
241+ BalanceInjectionReference ();
242+ InterlockedExchange (&g_ConnectStarted, 0 );
243+ }
219244 LogToFile (STARTUP_LOG , L" Win11StartButton: visual tree advise 0x%08X" , hr);
220245 return hr;
221246 }
@@ -295,41 +320,26 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal
295320 PostMessage (m_Dispatch, WM_OS_STARTBUTTON_APPLY , 0 , 0 );
296321 }
297322
298- void Shutdown ( void )
323+ HRESULT Shutdown ( void )
299324 {
300- // Prevent new users of the TAP while teardown is in progress. The caller
301- // holds a reference, so UnadviseVisualTreeChange cannot destroy this object
302- // out from under the shutdown sequence.
325+ // SetSite creates the dispatch window on the XAML thread. Run the complete
326+ // XAML/COM teardown on that same thread rather than releasing apartment-
327+ // sensitive interfaces from whichever thread initiated Open-Shell Exit.
328+ if (!m_Dispatch)
329+ return E_UNEXPECTED ;
330+
331+ HRESULT hr = (HRESULT )SendMessage (m_Dispatch, WM_OS_STARTBUTTON_SHUTDOWN , 0 , 0 );
332+ if (FAILED (hr))
333+ return hr;
334+
303335 AcquireSRWLockExclusive (&g_TapLock);
304336 if (g_Tap == this )
305337 g_Tap = NULL ;
306338 ReleaseSRWLockExclusive (&g_TapLock);
307339
308- // Restore the native XAML state before detaching the callback.
309- RequestApply (true );
310-
311- if (m_Visual && m_Advised)
312- {
313- HRESULT hr = m_Visual->UnadviseVisualTreeChange (static_cast <IVisualTreeServiceCallback*>(this ));
314- if (FAILED (hr))
315- {
316- // Keep the DLL resident rather than leave XAML with a callback into
317- // unloaded code. Explorer restart remains the safe recovery path.
318- HMODULE module = NULL ;
319- GetModuleHandleEx (GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS ,
320- (LPCTSTR )&GetThisModule, &module );
321- LogToFile (STARTUP_LOG , L" Win11StartButton: visual tree unadvise failed 0x%08X" , hr);
322- return ;
323- }
324- m_Advised = false ;
325- }
326-
327- m_Visual.Release ();
328- m_Site.Release ();
329340 InterlockedExchange (&g_ConnectStarted, 0 );
330-
331- if (m_Dispatch)
332- SendMessage (m_Dispatch, WM_OS_STARTBUTTON_DESTROY , 0 , 0 );
341+ BalanceInjectionReference ();
342+ return S_OK ;
333343 }
334344
335345private:
@@ -349,14 +359,53 @@ class CWin11StartButtonTap: public IObjectWithSite, public IVisualTreeServiceCal
349359 tap->ApplyState (enabled);
350360 return 0 ;
351361 }
352- if (msg == WM_OS_STARTBUTTON_DESTROY && tap)
362+ if (msg == WM_OS_STARTBUTTON_SHUTDOWN && tap)
363+ return tap->ShutdownOnDispatchThread ();
364+ return DefWindowProc (hwnd, msg, wParam, lParam);
365+ }
366+
367+ HRESULT ShutdownOnDispatchThread ( void )
368+ {
369+ // The window is owned by the same thread on which SetSite ran. Keep all
370+ // operations touching the XAML diagnostics interfaces on this thread.
371+ ApplyState (false );
372+
373+ if (m_Visual && m_Advised)
353374 {
354- tap->m_Dispatch = NULL ;
355- SetWindowLongPtr (hwnd, GWLP_USERDATA , 0 );
356- DestroyWindow (hwnd);
357- return 0 ;
375+ HRESULT hr = m_Visual->UnadviseVisualTreeChange (static_cast <IVisualTreeServiceCallback*>(this ));
376+ if (FAILED (hr))
377+ {
378+ LogToFile (STARTUP_LOG , L" Win11StartButton: visual tree unadvise failed 0x%08X" , hr);
379+ return hr;
380+ }
381+ m_Advised = false ;
382+ }
383+
384+ m_Visual.Release ();
385+ m_Site.Release ();
386+
387+ if (m_Dispatch)
388+ {
389+ HWND dispatch = m_Dispatch;
390+ m_Dispatch = NULL ;
391+ SetWindowLongPtr (dispatch, GWLP_USERDATA , 0 );
392+ DestroyWindow (dispatch);
393+ }
394+ return S_OK ;
395+ }
396+
397+ void BalanceInjectionReference ( void )
398+ {
399+ if (m_InjectionReferenceBalanced)
400+ return ;
401+
402+ HMODULE module = GetThisModule ();
403+ if (module )
404+ {
405+ // Mark first: FreeLibrary can run loader callbacks before it returns.
406+ m_InjectionReferenceBalanced = true ;
407+ FreeLibrary (module );
358408 }
359- return DefWindowProc (hwnd, msg, wParam, lParam);
360409 }
361410
362411 bool CreateDispatchWindow ( void )
@@ -906,7 +955,9 @@ void StopWin11StartButtonMonitor( void )
906955 CWin11StartButtonTap *tap = GetTapRef ();
907956 if (tap)
908957 {
909- tap->Shutdown ();
958+ HRESULT hr = tap->Shutdown ();
959+ if (FAILED (hr))
960+ LogToFile (STARTUP_LOG , L" Win11StartButton: TAP shutdown failed 0x%08X" , hr);
910961 tap->Release ();
911962 }
912963}
0 commit comments