@@ -76,28 +76,37 @@ private void ThreadMain()
7676 }
7777
7878 /// <summary>
79- /// Forces first-time UI Automation / accessibility initialization on this STA thread by
80- /// creating a throwaway control, realizing its accessible object, and then destroying it. Doing
81- /// this once at startup avoids the lazy UIA activation that otherwise deadlocks on the 32-bit
82- /// host during form teardown (see <see cref="ThreadMain"/>).
79+ /// Forces first-time UI Automation initialization on this STA thread by creating a real
80+ /// window handle and then destroying it. Destroying the handle raises WM_DESTROY, which
81+ /// runs Control.ReleaseUiaProvider -> UiaReturnRawElementProvider -> UIAutomationCore
82+ /// CheckInit/DoInit -> CoCreateInstance(CUIAutomation7). Running that once here, while the
83+ /// STA thread is idle and the native OCX/loader work is not in flight, means later form
84+ /// teardown only releases an already-initialized UIA core instead of triggering a
85+ /// cross-apartment COM activation that deadlocks on the 32-bit (x86) host.
8386 /// </summary>
8487 private static void WarmUpUiAutomation ( )
8588 {
86- try {
87- using var warmup = new Control ( ) ;
88- // Force handle creation so the control has a real HWND.
89+ try {
90+ // Use a Form (not a bare Control): its WM_DESTROY path matches what Form1 hits
91+ // during real test teardown, so the same UIA init code runs now.
92+ using var warmup = new Form
93+ {
94+ ShowInTaskbar = false ,
95+ FormBorderStyle = FormBorderStyle . None ,
96+ StartPosition = FormStartPosition . Manual ,
97+ Location = new System . Drawing . Point ( - 32000 , - 32000 ) ,
98+ Size = new System . Drawing . Size ( 1 , 1 ) ,
99+ } ;
100+
101+ // Force native handle creation.
89102 _ = warmup . Handle ;
90103
91- // Touching AccessibilityObject realizes the control's accessible/UIA provider, which
92- // drives the same UIAutomationCore initialization that Control.ReleaseUiaProvider hits
93- // during WM_DESTROY. Doing it here, on an idle STA thread, primes that init safely so
94- // later form teardown only releases an already-initialized provider.
95- _ = warmup . AccessibilityObject ;
96-
97- // Disposing now runs the release path once, while the thread is idle rather than
98- // mid-teardown.
104+ // Destroy the handle => WM_DESTROY => ReleaseUiaProvider => UIAutomationCore DoInit.
105+ // This is the exact path that deadlocks at teardown; doing it here primes the UIA
106+ // core once, on an idle thread.
107+ warmup . Dispose ( ) ;
99108 } catch {
100- // Warm-up is best-effort; it must not fail apartment startup.
109+ // Warm-up is best-effort; it must never fail apartment startup.
101110 }
102111 }
103112
0 commit comments