==== ProbeWorker ==== 34| for (HWND c = GetWindow(w, GW_CHILD); c; c = GetWindow(c, GW_HWNDNEXT)) 35| ImmAssociateContext(c, nullptr); 36| } 37| } 38| 39| static void pump_for(int secs) { 40| #ifdef _WIN32 41| _set_invalid_parameter_handler(probe_invalid_param); 42| #endif 43| auto t0 = std::chrono::steady_clock::now(); 44| while (std::chrono::duration_cast( 45| std::chrono::steady_clock::now() - t0).count() < secs) { 46| MSG msg; 47| while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { 48| printf("[pump] msg=0x%X hwnd=%p\n", (unsigned)msg.message, (void*)msg.hwnd); 49| fflush(stdout); 50| TranslateMessage(&msg); 51| DispatchMessageW(&msg); 52| } 53| Sleep(5); 54| } 55| } 56| 57| static void sleep_for(int secs) { Sleep((DWORD)(secs * 1000)); } 58| 59| // Per-channel persistent worker replicating the bridge (round-3 probe). 60| class ProbeWorker { 61| public: 62| void start() { 63| th = std::thread([this] { 64| #ifdef _WIN32 65| _set_invalid_parameter_handler(probe_invalid_param); 66| CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 67| ImmAssociateContextEx(nullptr, nullptr, IACE_DEFAULT); 68| #endif 69| std::unique_lock lk(mu); 70| for (;;) { 71| if (stop && jobs.empty()) break; 72| if (!jobs.empty()) { 73| auto j = std::move(jobs.front()); 74| jobs.pop_front(); 75| lk.unlock(); 76| try { j(); } 77| catch (const std::exception& e) { printf("[worker] JOB EXCEPTION: %s\n", e.what()); fflush(stdout); } 78| catch (...) { printf("[worker] JOB UNKNOWN EXCEPTION\n"); fflush(stdout); } 79| lk.lock(); 80| continue; 81| } 82| cv.wait_for(lk, std::chrono::milliseconds(5)); 83| lk.unlock(); 84| try { 85| MSG msg; 86| while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { 87| printf("[wpump] msg=0x%X hwnd=%p\n", (unsigned)msg.message, (void*)msg.hwnd); 88| fflush(stdout); 89| TranslateMessage(&msg); 90| DispatchMessageW(&msg); 91| } 92| } 93| catch (const std::exception& e) { printf("[worker] PUMP EXCEPTION: %s\n", e.what()); fflush(stdout); } 94| catch (...) { printf("[worker] PUMP UNKNOWN EXCEPTION\n"); fflush(stdout); } 95| lk.lock(); 96| } 97| #ifdef _WIN32 98| CoUninitialize(); 99| #endif 100| }); 101| } 102| void post(std::function j) { 103| { std::lock_guard lk(mu); jobs.push_back(std::move(j)); } 104| cv.notify_all(); 105| } 106| std::thread th; 107| std::mutex mu; 108| std::condition_variable cv; 109| std::deque> jobs; 110| bool stop = false; 111| }; 112| 113| int main(int argc, char* argv[]) { 114| if (argc < 2) { 115| printf("usage: gui_probe [variant] [secs] [hwnd]\n" ==== shared_worker ==== 220| } else if (variant == "shared_worker" || variant == "ui_thread") { 221| // Proposed round-3 fix: ALL VST3 instances live in ONE apartment — 222| // one persistent worker thread hosts load + openGUI for every channel. 223| ProbeWorker w; 224| w.start(); 225| std::atomic done{false}; 226| int aRes = 0, bRes = 0; 227| w.post([&]() { 228| bool r = inst.loadPlugin(path, 44100.0); 229| printf("[shared_worker] load inst1=%d\n", r ? 1 : 0); 230| fflush(stdout); 231| if (r) { 232| HWND h = CreateWindowEx(0, "GuiProbeClass", "ProbeA", WS_OVERLAPPEDWINDOW, 233| 0, 0, 800, 600, nullptr, nullptr, GetModuleHandle(nullptr), nullptr); 234| disable_ime_contexts(h); 235| r = inst.openGUI(h); 236| disable_ime_contexts(h); 237| printf("[shared_worker] attach inst1=%d hwnd=%p\n", r ? 1 : 0, (void*)h); 238| fflush(stdout); 239| aRes = r ? 1 : 0; 240| } 241| r = inst2.loadPlugin(path, 44100.0); 242| printf("[shared_worker] load inst2=%d\n", r ? 1 : 0); 243| fflush(stdout); 244| if (r) { 245| HWND h = CreateWindowEx(0, "GuiProbeClass", "ProbeB", WS_OVERLAPPEDWINDOW, 246| 0, 0, 800, 600, nullptr, nullptr, GetModuleHandle(nullptr), nullptr); 247| disable_ime_contexts(h); 248| r = inst2.openGUI(h); 249| disable_ime_contexts(h); 250| printf("[shared_worker] attach inst2=%d hwnd=%p\n", r ? 1 : 0, (void*)h); 251| fflush(stdout); 252| bRes = r ? 1 : 0; 253| } 254| done = true; 255| }); 256| auto t0 = std::chrono::steady_clock::now(); 257| try { 258| while (!done.load() && std::chrono::duration_cast( 259| std::chrono::steady_clock::now() - t0).count() < secs) { 260| MSG msg; 261| while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { 262| TranslateMessage(&msg); 263| DispatchMessageW(&msg); 264| } 265| Sleep(2); 266| } 267| } catch (const std::exception& e) { printf("[main] PUMP1 EXCEPTION: %s\n", e.what()); fflush(stdout); } 268| catch (...) { printf("[main] PUMP1 UNKNOWN EXCEPTION\n"); fflush(stdout); } 269| // Stay alive 5s with BOTH editors attached and the worker pumping, 270| // while MAIN KEEPS PUMPING like the bridge audio loop: distinguishes a 271| // live crash (bad) from a stop-pumping probe artifact. 272| auto t1 = std::chrono::steady_clock::now(); 273| try { 274| while (std::chrono::duration_cast( 275| std::chrono::steady_clock::now() - t1).count() < 5) { 276| MSG msg; 277| while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { 278| TranslateMessage(&msg); 279| DispatchMessageW(&msg); 280| } 281| Sleep(2); 282| } 283| } catch (const std::exception& e) { printf("[main] PUMP2 EXCEPTION: %s\n", e.what()); fflush(stdout); } 284| catch (...) { printf("[main] PUMP2 UNKNOWN EXCEPTION\n"); fflush(stdout); } 285| printf("RESULT: done=%d aRes=%d bRes=%d\n", done.load() ? 1 : 0, aRes, bRes); 286| return (done.load() && aRes && bRes) ? 0 : 3; 287| } else if (variant == "bridge_two") { 288| // Exact bridge architecture: per-channel persistent worker threads with 289| // own COM STA apartments and idle message pumps; inst1 loaded+attached 290| // on worker A, then inst2 loaded+attached on worker B; main pumps.