diff --git a/native_bridge/include/INativeInstrument.h b/native_bridge/include/INativeInstrument.h index 6f3055f..2a2e3e2 100644 --- a/native_bridge/include/INativeInstrument.h +++ b/native_bridge/include/INativeInstrument.h @@ -41,6 +41,11 @@ public: // inside processAudioBlock on the same instance). virtual bool needsReload() const { return false; } virtual void setReloading(bool /*on*/) {} + // VST3: split openGUI so instance teardown/reload runs on the owning + // apartment thread while view->attached() may run on a temporary thread + // with the channel worker pumping messages (see bridge main.cpp). + virtual bool reloadForGUI() { return true; } + virtual bool attachView(void* /*parentWindowHandle*/) { return false; } // Real-time Audio PCM Float32 rendering loop virtual void processAudioBlock(float* outputL, float* outputR, uint32_t numSamples) = 0; diff --git a/native_bridge/include/Vst3Instrument.h b/native_bridge/include/Vst3Instrument.h index 1d2f128..0e2350e 100644 --- a/native_bridge/include/Vst3Instrument.h +++ b/native_bridge/include/Vst3Instrument.h @@ -25,6 +25,8 @@ public: void programChange(uint32_t channel, uint32_t program) override; void pitchBend(uint32_t channel, uint32_t bend14) override; bool openGUI(void* parentWindowHandle) override; + bool reloadForGUI() override; + bool attachView(void* parentWindowHandle) override; void closeGUI() override; bool needsReload() const override { return hasAttachedOnce_ && !guiAttached_; } void setReloading(bool on) override { reloading_ = on; } diff --git a/native_bridge/src/Vst3Instrument.cpp b/native_bridge/src/Vst3Instrument.cpp index 487c116..ee4070c 100644 --- a/native_bridge/src/Vst3Instrument.cpp +++ b/native_bridge/src/Vst3Instrument.cpp @@ -424,7 +424,10 @@ void Vst3Instrument::noteOff(uint32_t channel, uint32_t pitch, uint32_t sampleOf e.noteOff.channel = 0; // Force to MIDI Channel 1 (0) for VSTi compatibility e.noteOff.pitch = (int16)pitch; e.noteOff.velocity = 0.f; - e.noteOff.noteId = -1; + // noteId phai MATCH noteOn (0): Nexus/DUNE3 track notes bang id, -1 + // khong khop voi note-on -> plugin giu note vo han (am thanh khong dung + // cho den khi kill bridge). + e.noteOff.noteId = 0; e.noteOff.tuning = 0.f; s->eventList.addEvent(e); #endif @@ -485,21 +488,44 @@ bool Vst3Instrument::openGUI(void* parentWindowHandle) { #ifndef HAVE_VST3SDK (void)parentWindowHandle; return false; +#else + if (!reloadForGUI()) return false; + return attachView(parentWindowHandle); +#endif +} + +bool Vst3Instrument::reloadForGUI() { +#ifndef HAVE_VST3SDK + return false; #else auto* s = static_cast(state_); - std::cerr << "[dbg] openGUI hwnd=" << (void*)(uintptr_t)parentWindowHandle - << " controller=" << (s ? (s->controller ? 1 : 0) : -1) << std::endl; - if (!s || !s->controller || !parentWindowHandle) return false; + if (!s || !s->controller) return false; if (s->view && guiAttached_) return true; if (hasAttachedOnce_ && !guiAttached_) { // Reopen GUI sau khi dong: mot so plugin (Nexus...) hang o // view->attached() LAN 2 tren cung component instance. Tao lai inst // moi tren CUNG worker thread (COM STA con song) roi attach lai. + // PHAN NAY PHAI chay tren worker thread — apartment cua channel song + // o day; chay tren thread tam thi apartment moi chet ngay sau do + // (reopen lan sau -> treo nhu bridge_like). std::cerr << "[dbg] openGUI: re-attach - reloading fresh plugin instance" << std::endl; if (!reload()) return false; s = static_cast(state_); if (!s || !s->controller) return false; } + return true; +#endif +} + +bool Vst3Instrument::attachView(void* parentWindowHandle) { +#ifndef HAVE_VST3SDK + (void)parentWindowHandle; + return false; +#else + auto* s = static_cast(state_); + std::cerr << "[dbg] openGUI hwnd=" << (void*)(uintptr_t)parentWindowHandle + << " controller=" << (s ? (s->controller ? 1 : 0) : -1) << std::endl; + if (!s || !s->controller || !parentWindowHandle) return false; IPlugView* rawView = nullptr; tresult qi = s->controller->queryInterface(IPlugView::iid, (void**)&rawView); { diff --git a/native_bridge/src/gui_probe.cpp b/native_bridge/src/gui_probe.cpp index bac5ba2..30ca659 100644 --- a/native_bridge/src/gui_probe.cpp +++ b/native_bridge/src/gui_probe.cpp @@ -80,7 +80,7 @@ public: int main(int argc, char* argv[]) { if (argc < 2) { printf("usage: gui_probe [variant] [secs] [hwnd]\n" - "variants: main_own | worker_own_nopump | worker_own_pump | worker_foreign_nopump | worker_foreign_pump | bridge_like | same_thread | two_instances | two_instances_close | close_reopen | bridge_two | shared_worker\n"); + "variants: main_own | worker_own_nopump | worker_own_pump | worker_foreign_nopump | worker_foreign_pump | bridge_like | same_thread | two_instances | two_instances_close | close_reopen | bridge_two | shared_worker | two_workers_close\n"); return 2; } std::string path = argv[1]; @@ -315,6 +315,99 @@ int main(int argc, char* argv[]) { aDone.load() ? 1 : 0, aOk.load() ? 1 : 0, bDone.load() ? 1 : 0, bOk.load() ? 1 : 0); return (aDone.load() && aOk.load() && bDone.load() && bOk.load()) ? 0 : 3; + } else if (variant == "two_workers_close") { + // Round-4 exact bridge pattern: per-channel workers (own STA + // apartments); attach via temp thread + WORKER nested pump (the bridge + // main.cpp job pumps the worker queue while attachView runs on a + // temporary thread); close editor inst1 before attaching inst2 on its + // own worker. + ProbeWorker wa, wb; + wa.start(); + wb.start(); + std::atomic aDone{false}, aOk{false}, bDone{false}, bOk{false}; + wa.post([&]() { + printf("[two_workers_close] workerA load inst1\n"); + fflush(stdout); + bool r = inst.loadPlugin(path, 44100.0); + printf("[two_workers_close] workerA load inst1=%d\n", r ? 1 : 0); + fflush(stdout); + if (r) { + HWND h = CreateWindowEx(0, "GuiProbeClass", "ProbeA", WS_OVERLAPPEDWINDOW, + 0, 0, 800, 600, nullptr, nullptr, GetModuleHandle(nullptr), nullptr); + printf("[two_workers_close] workerA hwnd=%p\n", (void*)h); + fflush(stdout); + bool r2 = false; + std::atomic d{false}; + std::thread t([&]() { + CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + r2 = inst.attachView(h); + CoUninitialize(); + d = true; + }); + while (!d.load()) { + MSG msg; + while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + Sleep(1); + } + t.join(); + printf("[two_workers_close] workerA attach inst1=%d\n", r2 ? 1 : 0); + fflush(stdout); + aOk = r2; + inst.closeGUI(); // view->removed() on worker A + printf("[two_workers_close] workerA closeGUI inst1\n"); + fflush(stdout); + } + aDone = true; + }); + auto t0 = std::chrono::steady_clock::now(); + auto elapsed = [&]() { + return std::chrono::duration_cast( + std::chrono::steady_clock::now() - t0).count(); + }; + while (!aDone.load() && elapsed() < secs) Sleep(50); + if (!aDone.load()) { printf("RESULT: TIMEOUT inst1 (aDone=0)\n"); return 3; } + wb.post([&]() { + printf("[two_workers_close] workerB load inst2\n"); + fflush(stdout); + bool r = inst2.loadPlugin(path, 44100.0); + printf("[two_workers_close] workerB load inst2=%d\n", r ? 1 : 0); + fflush(stdout); + if (r) { + HWND h = CreateWindowEx(0, "GuiProbeClass", "ProbeB", WS_OVERLAPPEDWINDOW, + 0, 0, 800, 600, nullptr, nullptr, GetModuleHandle(nullptr), nullptr); + printf("[two_workers_close] workerB hwnd=%p\n", (void*)h); + fflush(stdout); + bool r2 = false; + std::atomic d{false}; + std::thread t([&]() { + CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + r2 = inst2.attachView(h); + CoUninitialize(); + d = true; + }); + while (!d.load()) { + MSG msg; + while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + Sleep(1); + } + t.join(); + printf("[two_workers_close] workerB attach inst2=%d\n", r2 ? 1 : 0); + fflush(stdout); + bOk = r2; + } + bDone = true; + }); + while ((!aDone.load() || !bDone.load()) && elapsed() < secs) Sleep(50); + printf("RESULT: aDone=%d aOk=%d bDone=%d bOk=%d\n", + aDone.load() ? 1 : 0, aOk.load() ? 1 : 0, + bDone.load() ? 1 : 0, bOk.load() ? 1 : 0); + return (aDone.load() && aOk.load() && bDone.load() && bOk.load()) ? 0 : 3; } else if (variant == "two_instances" || variant == "two_instances_close" || variant == "close_reopen") { // Bridge round-3 probes: does a SECOND instance of the same plugin hang // in view->attached() while the first instance's view is attached? diff --git a/native_bridge/src/main.cpp b/native_bridge/src/main.cpp index 5a36ff4..cff1f4b 100644 --- a/native_bridge/src/main.cpp +++ b/native_bridge/src/main.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -346,123 +347,119 @@ int main(int argc, char* argv[]) { playheadSamples = c.arg1; } } else if (c.type == 4) { // OPEN_GUI (A7): arg1 = parent HWND (0 → bridge tự tạo native window), arg2 = plugin id - // Chay tren CUNG ChannelWorker da load instrument: thread rieng - // cho openGUI lai tao COM apartment moi, con plugin thi song o - // apartment cu da chet (load thread exit) -> Nexus attached() - // hang (gui_probe: bridge_like treo, same_thread OK). uint32_t guiCh = c.channel; if (guiCh >= 16) guiCh = 0; - // Bo poll 10s tren real-time loop (writeIndex stall > 3s -> Rust - // tuong bridge chet va restart -> 2 bridge cung map SHM -> race). - // LOAD (type=2) post truoc OPEN_GUI tren CUNG ChannelWorker (FIFO) - // -> job openGUI chay sau khi LOAD xong -> kiem tra inst trong job. if (!workers[guiCh]) workers[guiCh] = std::make_unique(); - workers[guiCh]->post([&instruments, guiCh, arg1 = c.arg1, arg2 = std::string(c.arg2)]() { + void* hwnd = (void*)(uintptr_t)c.arg1; +#ifdef _WIN32 + // Window PHAI thuoc MAIN thread (audio loop pump nay dispatch + // messages cua no moi vong lap). Window tren worker + worker + // khong pump trong luc job chay -> view->attached() treo + // (gui_probe: two_workers_close TIMEOUT; same_thread / + // two_instances_close — window tren main — OK). Tao/cap nhat + // window ngay tai day tren main thread. + HWND nativeHwnd = nullptr; + { + std::lock_guard lock(g_guiMutex); + auto it = g_guiWindows.find(guiCh); + if (it != g_guiWindows.end()) nativeHwnd = (HWND)it->second; + } + if (hwnd == 0) { + if (nativeHwnd && IsWindow(nativeHwnd)) { + hwnd = (void*)nativeHwnd; + SetWindowTextA(nativeHwnd, std::string(c.arg2).c_str()); + ShowWindow(nativeHwnd, SW_SHOW); + SetForegroundWindow(nativeHwnd); + // Reuse: cap nhat USERDATA (channel+1) — inst CU da bi + // thay the boi assign() -> WM_DESTROY sau nay lookup + // inst MOI, khong dung con tro dangling. + SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1)); + } else { + nativeHwnd = (HWND)create_native_vst_window(std::string(c.arg2).c_str()); + if (!nativeHwnd) { + std::cerr << "[NativeBridge] GUI create window FAILED plugin=" << c.arg2 << std::endl; + continue; + } + { + std::lock_guard lock(g_guiMutex); + g_guiWindows[guiCh] = nativeHwnd; // keep window alive + g_hwndToCh[nativeHwnd] = guiCh; // WM_DESTROY cleanup + } + SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1)); + hwnd = (void*)nativeHwnd; + } + } +#endif + workers[guiCh]->post([&instruments, guiCh, hwnd, arg2 = std::string(c.arg2)]() { if (!instruments.get(guiCh)) { - std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << arg1 + std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << hwnd << " plugin=" << arg2 << " ch=" << guiCh << " (no instrument loaded)" << std::endl; return; } - std::cerr << "[dbg] openGUI thread start hwnd=" << arg1 - << " plugin=" << arg2 << " ch=" << guiCh << std::endl; - void* hwnd = (void*)(uintptr_t)arg1; + std::cerr << "[dbg] openGUI thread start hwnd=" << hwnd + << " plugin=" << arg2 << " ch=" << guiCh << std::endl; #ifdef _WIN32 - // Option B: chi 1 editor VST mo tai 1 thoi diem toan - // bridge. Instance thu 2 cua CUNG plugin (Nexus) attach - // view o apartment/worker khac -> treo (bridge.log: ch=2 - // ket o attached() khi ch=1 con mo) / crash (gui_probe - // bridge_two). Dong editor cua channel khac TRUOC khi - // attach: WM_CLOSE -> worker channel do destroy window -> - // WM_DESTROY -> closeGUI() -> view->removed() tren dung - // thread so huu (gui_probe two_instances_close: OK). + // Option B: chi 1 editor VST mo tai 1 thoi diem toan + // bridge. Instance thu 2 cua CUNG plugin (Nexus) attach + // view o apartment/worker khac -> treo. Dong editor cua + // channel khac TRUOC khi attach: WM_CLOSE -> main pump + // (audio loop) destroy window -> WM_DESTROY -> closeGUI() + // + xoa registry. Chay tren worker job de khong stall + // writeIndex cua real-time loop. + { + std::vector others; { - std::vector others; + std::lock_guard lock(g_guiMutex); + for (const auto& kv : g_guiWindows) + if (kv.first != guiCh) others.push_back(kv.first); + } + for (uint32_t y : others) { + HWND yHwnd = nullptr; { std::lock_guard lock(g_guiMutex); - for (const auto& kv : g_guiWindows) - if (kv.first != guiCh) others.push_back(kv.first); + auto it = g_guiWindows.find(y); + if (it != g_guiWindows.end()) yHwnd = (HWND)it->second; } - for (uint32_t y : others) { - HWND yHwnd = nullptr; + if (!yHwnd || !IsWindow(yHwnd)) continue; + PostMessage(yHwnd, WM_CLOSE, 0, 0); + std::cerr << "[dbg] openGUI: closing editor ch=" << y + << " before attach ch=" << guiCh << std::endl; + bool closed = false; + for (int i = 0; i < 500; ++i) { { std::lock_guard lock(g_guiMutex); - auto it = g_guiWindows.find(y); - if (it != g_guiWindows.end()) yHwnd = (HWND)it->second; + if (g_guiWindows.find(y) == g_guiWindows.end()) { closed = true; break; } } - if (!yHwnd || !IsWindow(yHwnd)) continue; - PostMessage(yHwnd, WM_CLOSE, 0, 0); - std::cerr << "[dbg] openGUI: closing editor ch=" << y - << " before attach ch=" << guiCh << std::endl; - // Cho worker channel y xu ly WM_CLOSE -> destroy -> - // WM_DESTROY (xoa registry). Timeout 5s tranh - // worker ket (load lau / plugin treo). - bool closed = false; - for (int i = 0; i < 500; ++i) { - { - std::lock_guard lock(g_guiMutex); - if (g_guiWindows.find(y) == g_guiWindows.end()) { closed = true; break; } - } - Sleep(10); - } - if (!closed) - std::cerr << "[dbg] openGUI: editor ch=" << y - << " not closed in 5s, proceeding" << std::endl; - } - } - if (arg1 == 0) { - HWND existingHwnd = nullptr; - { - std::lock_guard lock(g_guiMutex); - auto it = g_guiWindows.find(guiCh); - if (it != g_guiWindows.end()) existingHwnd = (HWND)it->second; - } - if (existingHwnd && IsWindow(existingHwnd)) { - hwnd = existingHwnd; - SetWindowTextA((HWND)hwnd, arg2.c_str()); - ShowWindow((HWND)hwnd, SW_SHOW); - SetForegroundWindow((HWND)hwnd); - // Reuse: cap nhat USERDATA (channel+1) — inst CU - // da bi thay the boi assign() -> WM_DESTROY sau - // nay lookup inst MOI, khong dung con tro dangling. - SetWindowLongPtrA((HWND)hwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1)); - } else { - hwnd = create_native_vst_window(arg2.c_str()); - if (!hwnd) { - std::cerr << "[NativeBridge] GUI create window FAILED plugin=" << arg2 << std::endl; - return; - } - { - std::lock_guard lock(g_guiMutex); - g_guiWindows[guiCh] = hwnd; // keep window alive - g_hwndToCh[(HWND)hwnd] = guiCh; // WM_DESTROY cleanup - } - SetWindowLongPtrA((HWND)hwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1)); + Sleep(10); } + if (!closed) + std::cerr << "[dbg] openGUI: editor ch=" << y + << " not closed in 5s, proceeding" << std::endl; } + } #else - (void)0; + (void)0; #endif - if (auto* inst = instruments.get(guiCh)) { - // Reopen sau khi dong: openGUI() rebuild lai plugin - // instance (reload = terminate + loadPlugin) trong - // khi audio loop co the dang processAudioBlock tren - // CUNG object -> UAF/hang. Chan bang flag reloading_ - // (set/clear duoi engine mutex; renderAll giu mutex - // khi process nen check luon nhat quan). - bool guard = inst->needsReload(); - if (guard) instruments.setReloading(guiCh, true); - bool ok = inst->openGUI(hwnd); - if (guard) instruments.setReloading(guiCh, false); - if (ok) - std::cout << "[NativeBridge] GUI attached hwnd=" << hwnd - << " plugin=" << arg2 << " ch=" << guiCh << std::endl; - else - std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << hwnd - << " plugin=" << arg2 << std::endl; - } - // Editor windows song tren thread nay — ChannelWorker - // pump message queue khi idle (xem class comment). - }); + if (auto* inst = instruments.get(guiCh)) { + // Reopen sau khi dong: reload() (terminate + loadPlugin) + // PHAI chay tren worker thread nay — COM STA apartment + // cua channel song o day. view->attached() cung chay o + // day; window thuoc main thread nen main pump (audio + // loop) dispatch messages cua no — khong can pump worker. + // Chan UAF bang flag reloading_ (set/clear duoi engine + // mutex; renderAll giu mutex khi process). + bool guard = inst->needsReload(); + if (guard) instruments.setReloading(guiCh, true); + bool ok = inst->reloadForGUI() && inst->attachView(hwnd); + if (guard) instruments.setReloading(guiCh, false); + if (ok) + std::cout << "[NativeBridge] GUI attached hwnd=" << hwnd + << " plugin=" << arg2 << " ch=" << guiCh << std::endl; + else + std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << hwnd + << " plugin=" << arg2 << std::endl; + } + }); } } shmIPC->controlQueueCount = 0;