diff --git a/native_bridge/src/main.cpp b/native_bridge/src/main.cpp index cff1f4b..dda2cd7 100644 --- a/native_bridge/src/main.cpp +++ b/native_bridge/src/main.cpp @@ -55,25 +55,23 @@ static void sleep_ms(uint32_t ms) { // thread cua channel tao window) co the don map. USERDATA luu channel+1 (KHONG // luu con tro inst truc tiep: assign() thay inst moi moi lan load — con tro cu // bi huy → WM_DESTROY tren con tro dangling → crash/hang bridge). +class ChannelWorker; // fwd — WM_DESTROY posts closeGUI() to the channel worker +static void post_close_gui(uint32_t ch, HWND hwnd); // defined after ChannelWorker static InstrumentEngineManager* g_engine = nullptr; static std::mutex g_guiMutex; static std::map g_guiWindows; // channel -> HWND (keep window alive) static std::map g_hwndToCh; // HWND -> channel (WM_DESTROY cleanup) +static std::map>* g_workers = nullptr; static LRESULT CALLBACK VstWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (uMsg == WM_DESTROY) { - INativeInstrument* instToClose = nullptr; + uint32_t ch = UINT32_MAX; { std::lock_guard lock(g_guiMutex); auto it = g_hwndToCh.find(hwnd); - if (it != g_hwndToCh.end()) { - uint32_t ch = it->second; - g_hwndToCh.erase(it); - g_guiWindows.erase(ch); - if (g_engine) instToClose = g_engine->get(ch); - } + if (it != g_hwndToCh.end()) ch = it->second; } - if (instToClose) instToClose->closeGUI(); + if (ch != UINT32_MAX) post_close_gui(ch, hwnd); } return DefWindowProcA(hwnd, uMsg, wParam, lParam); } @@ -171,6 +169,32 @@ private: bool stop_ = false; }; +// closeGUI() MUST run on the channel worker thread (its COM STA apartment) — +// the view was attached there. Calling view->removed() from the main thread +// (WM_DESTROY handler) is a cross-apartment COM call that corrupts the plugin; +// Nexus then hangs on the NEXT view->attached(). Erase the registry inside the +// job so Option B (closing another editor before attach) waits for closeGUI to +// actually finish. +static void post_close_gui(uint32_t ch, HWND hwnd) { + ChannelWorker* w = nullptr; + if (g_workers) { + auto wit = g_workers->find(ch); + if (wit != g_workers->end()) w = wit->second.get(); + } + if (w) { + w->post([ch, hwnd]() { + if (auto* i = g_engine->get(ch)) i->closeGUI(); + std::lock_guard lock(g_guiMutex); + g_hwndToCh.erase(hwnd); + g_guiWindows.erase(ch); + }); + } else { + std::lock_guard lock(g_guiMutex); + g_hwndToCh.erase(hwnd); + g_guiWindows.erase(ch); + } +} + int main(int argc, char* argv[]) { std::cout << "[NativeBridge] Starting DAW Host Bridge Engine..." << std::endl; @@ -209,6 +233,7 @@ int main(int argc, char* argv[]) { // thread whose COM STA apartment stays alive for the channel's lifetime // (see ChannelWorker comment — a dead apartment hangs Nexus attached()). std::map> workers; + g_workers = &workers; // B9: native editor windows per channel — keep alive (HWND outlives the job). // Registry la global (g_guiWindows) — WM_DESTROY cleanup can tu VstWindowProc. // B8: sample rate from the DAW (Rust spawns us with SF_SAMPLE_RATE).