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:
@@ -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<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<uint32_t, std::unique_ptr<ChannelWorker>>* 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<std::mutex> 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<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[]) {
|
||||
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<uint32_t, std::unique_ptr<ChannelWorker>> 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).
|
||||
|
||||
Reference in New Issue
Block a user