[Bug 59519] New: ole32: OLE clipboard caches stale window handle after STA thread terminates, breaking subsequent OleSetClipboard calls
http://bugs.winehq.org/show_bug.cgi?id=59519 Bug ID: 59519 Summary: ole32: OLE clipboard caches stale window handle after STA thread terminates, breaking subsequent OleSetClipboard calls Product: Wine Version: unspecified Hardware: x86-64 OS: Linux Status: UNCONFIRMED Severity: normal Priority: P2 Component: ole32 Assignee: wine-bugs@list.winehq.org Reporter: ayourk@gmail.com Distribution: --- Created attachment 80543 --> http://bugs.winehq.org/attachment.cgi?id=80543 reproducer source This bug is really starting to bug me ;) so I figured it was time to report it and squash it! I know that in REAL Windows, this works fine, but as you can test from the attached sample app, the clipboard copy only really works once before the application has to be restarted for it to work again. OleSetClipboard fails with CLIPBRD_E_CANT_OPEN (0x800401D0) on the second and all subsequent calls when each call is made from a new STA thread and the main thread has OLE initialized. This pattern is used by .NET WinForms applications. Clipboard.SetText() internally spawns a new STA thread, calls OleSetClipboard, then exits the thread. The main thread keeps OLE initialized (.NET runtime), so OLE_moduleLockCount never drops to 0 and clipbrd_uninitialize() never runs on the copy threads. The cached clipboard window HWND from the first (now-dead) thread is never cleared. This is deterministic, not a race condition. 100% reproducible on Wine 9.0 through 11.0. I even tested it on the latest development branch. The buggy caching pattern has been present since the OLE clipboard was first implemented (commit 557ff419bea, July 18, 1999), so this affects every Wine version ever released. It went unnoticed because the trigger pattern (main thread keeps OLE alive + new STA threads for each clipboard operation) is specific to .NET WinForms applications. Affected application: Space Engineers (Steam App ID 244850) — Ctrl+C works once, then freezes for ~1 second on every subsequent Ctrl+C. The clipboard never updates past the first copy. This is also reported at https://github.com/ValveSoftware/Proton/issues/1792 STEPS TO REPRODUCE ================== Prerequisites: Main thread has OLE initialized (OleInitialize), as in any .NET application. 1. Spawn STA Thread A: OleInitialize(NULL) -> OleSetClipboard(NULL) -> OleSetClipboard(data1) -> OleFlushClipboard() -> OleUninitialize() -> thread exits Wait for Thread A to complete. 2. Paste: OpenClipboard(NULL) -> GetClipboardData(CF_TEXT) -> "first" Result: correct. 3. Spawn STA Thread B: OleInitialize(NULL) -> OleSetClipboard(NULL) Result: returns 0x800401D0 (CLIPBRD_E_CANT_OPEN) All 10 retries fail with the same error. 4. Paste: GetClipboardData(CF_TEXT) -> still "first" Expected: "second" 5. All subsequent copy attempts from new threads fail the same way. EXPECTED BEHAVIOR (Windows) =========================== Each copy succeeds immediately. After step 3, the clipboard contains "second". Tested on Windows 7, 10, and 11 -- all succeed. ACTUAL BEHAVIOR (Wine 9.0 through 11.0) ======================================== First copy succeeds. All subsequent copies fail with CLIPBRD_E_CANT_OPEN. The clipboard permanently retains the first copied text. Each failed copy takes ~1 second (10 retries x 100ms). ROOT CAUSE ========== In dlls/ole32/clipboard.c, get_clipbrd_window() caches a single HWND per process: static inline HRESULT get_clipbrd_window(ole_clipbrd *clipbrd, HWND *wnd) { if ( !clipbrd->window ) clipbrd->window = create_clipbrd_window(); *wnd = clipbrd->window; return *wnd ? S_OK : E_FAIL; } When the STA thread that created this window terminates, the window is destroyed but the stale HWND remains cached. clipbrd_uninitialize() (which clears it) only runs when OLE_moduleLockCount drops to 0, which never happens in a .NET app because the main thread keeps OLE alive. PROPOSED FIX ============ Add an IsWindow() check to get_clipbrd_window(): if ( clipbrd->window && !IsWindow(clipbrd->window) ) clipbrd->window = NULL; This detects the stale handle and forces creation of a fresh window on the current thread. Patch is attached. REPRODUCER ========== A standalone GUI test app is attached (clipboard_test.c + .exe). It has two modes: Mode A (Bug): Replicates the exact .NET OLE clipboard path. - Main thread calls OleInitialize (simulates .NET environment) - Each copy spawns a new STA thread: OleInitialize -> OleSetClipboard(NULL) -> OleSetClipboard(data) -> OleFlushClipboard -> OleUninitialize -> exit - 10-retry loop with 100ms delays (matches .NET Clipboard.SetText exactly) - Three copy/paste cycles with "first", "second", "third" - Copy 1 succeeds; Copy 2 and 3 fail with 0x800401D0 Mode B (Fix): Same OLE operations but manages the clipboard window lifetime correctly, demonstrating the fix works. - All three copies succeed. Build: x86_64-w64-mingw32-gcc -o clipboard_test.exe clipboard_test.c \ -lgdi32 -lole32 -luuid -mwindows Run: wine clipboard_test.exe Log output (clipboard_test.log) is attached showing the failure. -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=59519 --- Comment #1 from AYourk <ayourk@gmail.com> --- Created attachment 80544 --> http://bugs.winehq.org/attachment.cgi?id=80544 log output showing the bug -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=59519 --- Comment #2 from AYourk <ayourk@gmail.com> --- Created attachment 80545 --> http://bugs.winehq.org/attachment.cgi?id=80545 the proposed fix -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
http://bugs.winehq.org/show_bug.cgi?id=59519 --- Comment #3 from AYourk <ayourk@gmail.com> --- I was wrong, this wasn't reported at https://github.com/ValveSoftware/Proton/issues/1792 -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.
participants (1)
-
WineHQ Bugzilla