fix(bridge): closeGUI on channel worker thread — WM_DESTROY runs on main, view->removed() must run on the worker apartment or plugin corrupts and next attach hangs

This commit is contained in:
2026-08-13 17:06:32 +07:00
parent 2c890aef54
commit 55d232c659
+33 -8
View File
@@ -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 // 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 // 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). // 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 InstrumentEngineManager* g_engine = nullptr;
static std::mutex g_guiMutex; static std::mutex g_guiMutex;
static std::map<uint32_t, void*> g_guiWindows; // channel -> HWND (keep window alive) static std::map<uint32_t, void*> g_guiWindows; // channel -> HWND (keep window alive)
static std::map<HWND, uint32_t> g_hwndToCh; // HWND -> channel (WM_DESTROY cleanup) static std::map<HWND, uint32_t> g_hwndToCh; // HWND -> channel (WM_DESTROY cleanup)
static std::map<uint32_t, std::unique_ptr<ChannelWorker>>* g_workers = nullptr;
static LRESULT CALLBACK VstWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { static LRESULT CALLBACK VstWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
if (uMsg == WM_DESTROY) { if (uMsg == WM_DESTROY) {
INativeInstrument* instToClose = nullptr; uint32_t ch = UINT32_MAX;
{ {
std::lock_guard<std::mutex> lock(g_guiMutex); std::lock_guard<std::mutex> lock(g_guiMutex);
auto it = g_hwndToCh.find(hwnd); auto it = g_hwndToCh.find(hwnd);
if (it != g_hwndToCh.end()) { if (it != g_hwndToCh.end()) ch = it->second;
uint32_t ch = it->second;
g_hwndToCh.erase(it);
g_guiWindows.erase(ch);
if (g_engine) instToClose = g_engine->get(ch);
}
} }
if (instToClose) instToClose->closeGUI(); if (ch != UINT32_MAX) post_close_gui(ch, hwnd);
} }
return DefWindowProcA(hwnd, uMsg, wParam, lParam); return DefWindowProcA(hwnd, uMsg, wParam, lParam);
} }
@@ -171,6 +169,32 @@ private:
bool stop_ = false; 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<std::mutex> lock(g_guiMutex);
g_hwndToCh.erase(hwnd);
g_guiWindows.erase(ch);
});
} else {
std::lock_guard<std::mutex> lock(g_guiMutex);
g_hwndToCh.erase(hwnd);
g_guiWindows.erase(ch);
}
}
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
std::cout << "[NativeBridge] Starting DAW Host Bridge Engine..." << std::endl; 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 // thread whose COM STA apartment stays alive for the channel's lifetime
// (see ChannelWorker comment — a dead apartment hangs Nexus attached()). // (see ChannelWorker comment — a dead apartment hangs Nexus attached()).
std::map<uint32_t, std::unique_ptr<ChannelWorker>> workers; std::map<uint32_t, std::unique_ptr<ChannelWorker>> workers;
g_workers = &workers;
// B9: native editor windows per channel — keep alive (HWND outlives the job). // B9: native editor windows per channel — keep alive (HWND outlives the job).
// Registry la global (g_guiWindows) — WM_DESTROY cleanup can tu VstWindowProc. // Registry la global (g_guiWindows) — WM_DESTROY cleanup can tu VstWindowProc.
// B8: sample rate from the DAW (Rust spawns us with SF_SAMPLE_RATE). // B8: sample rate from the DAW (Rust spawns us with SF_SAMPLE_RATE).