fix(bridge): VSTi GUI crash on instrument switch — PostMessage WM_CLOSE instead of cross-thread DestroyWindow

- LOAD job posts WM_CLOSE to window created on main thread (DestroyWindow from worker crashed 0xc000041d, muting all tracks)
- handleOpenGui + attach jobs skip when Vst3Instrument::hasAttachedView() (state_ && guiAttached_)
- attach failure posts WM_CLOSE; post_close_gui erases entry only when it points at the same hwnd
- cap openVstGuiRetry at 5 to stop frontend retry spam
This commit is contained in:
2026-08-14 10:18:22 +07:00
parent f7f20e3a0a
commit 35499b614c
5 changed files with 54 additions and 12 deletions
+4
View File
@@ -635,6 +635,10 @@ bool Vst3Instrument::attachView(void* parentWindowHandle) {
#endif
}
bool Vst3Instrument::hasAttachedView() const {
return state_ != nullptr && guiAttached_;
}
bool Vst3Instrument::reload() {
#ifndef HAVE_VST3SDK
return false;
+41 -8
View File
@@ -197,12 +197,17 @@ static void post_close_gui(uint32_t ch, HWND 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);
// Chi erase neu map van tro den HWND NAY — neu reopen da tao
// window moi (race: close cu + OPEN_GUI moi), khong duoc xoa
// entry cua window moi (con tro treo -> leak window moi).
auto wit = g_guiWindows.find(ch);
if (wit != g_guiWindows.end() && wit->second == hwnd) g_guiWindows.erase(wit);
});
} else {
std::lock_guard<std::mutex> lock(g_guiMutex);
g_hwndToCh.erase(hwnd);
g_guiWindows.erase(ch);
auto wit = g_guiWindows.find(ch);
if (wit != g_guiWindows.end() && wit->second == hwnd) g_guiWindows.erase(wit);
}
}
@@ -361,6 +366,16 @@ int main(int argc, char* argv[]) {
g_pendingGui[guiCh] = { (void*)arg1, pluginId };
return;
}
// Dedupe OPEN_GUI spam (frontend openVstGuiRetry): view dang
// attached -> GUI da mo, khong post attach job lan nua. Re-attach
// tren view dang attached lam plugin loi (Nexus createView null).
if (auto* i0 = instruments.get(guiCh)) {
if (i0->hasAttachedView()) {
std::cerr << "[dbg] openGUI: view already attached ch=" << guiCh
<< " — skip" << std::endl;
return;
}
}
if (!workers[guiCh]) workers[guiCh] = std::make_unique<ChannelWorker>();
void* hwnd = (void*)arg1;
#ifdef _WIN32
@@ -405,6 +420,13 @@ int main(int argc, char* argv[]) {
}
std::cerr << "[dbg] openGUI thread start hwnd=" << hwnd
<< " plugin=" << arg2 << " ch=" << guiCh << std::endl;
if (auto* inst0 = instruments.get(guiCh)) {
if (inst0->hasAttachedView()) {
std::cerr << "[dbg] openGUI: view already attached ch=" << guiCh
<< " — skip" << std::endl;
return;
}
}
#ifdef _WIN32
// Option B: chi 1 editor VST mo tai 1 thoi diem toan
// bridge. Instance thu 2 cua CUNG plugin (Nexus) attach
@@ -468,9 +490,17 @@ int main(int argc, char* argv[]) {
if (ok)
std::cout << "[NativeBridge] GUI attached hwnd=" << hwnd
<< " plugin=" << arg2 << " ch=" << guiCh << std::endl;
else
else {
std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << hwnd
<< " plugin=" << arg2 << std::endl;
#ifdef _WIN32
// Attach that — khong co view (VD: channel la SF2/SFZ hoac plugin
// loi). Dong ngay cua so vo nghia de khong con window treo trong
// registry; WM_CLOSE -> main pump destroy tren main thread (owner).
// PostMessage an toan cross-thread (khong nhu DestroyWindow).
PostMessageA((HWND)hwnd, WM_CLOSE, 0, 0);
#endif
}
}
});
};
@@ -513,10 +543,13 @@ int main(int argc, char* argv[]) {
// DONG cua so editor dang mo cua channel TRUOC khi assign():
// thay the inst (VST3 -> SF2/inst khac) ma editor con song ->
// old inst destructor goi view->removed() tren HWND con hoat
// dong -> plugin block -> treo bridge. DestroyWindow chay tren
// CUNG worker thread so huu window; WM_DESTROY goi closeGUI()
// dung thu tu. Phai erase map TRUOC DestroyWindow (WM_DESTROY
// handler lay g_guiMutex lai — khong duoc giu lock khi destroy).
// dong -> plugin block -> treo bridge.
// QUAN TRONG: window duoc tao tren MAIN thread (handleOpenGui
// goi create_native_vst_window o main loop) — DestroyWindow tu
// worker thread la cross-thread (MSDN cam), gay crash USER32
// 0xc000041d. Post WM_CLOSE -> main pump destroy window tren
// DUNG thread so huu no. Registry da erase o tren nen WM_DESTROY
// khong goi closeGUI tren inst cu (inst moi chua co view).
HWND hToDestroy = nullptr;
{
std::lock_guard<std::mutex> lock(g_guiMutex);
@@ -527,7 +560,7 @@ int main(int argc, char* argv[]) {
g_hwndToCh.erase(hToDestroy);
}
}
if (hToDestroy && IsWindow(hToDestroy)) DestroyWindow(hToDestroy);
if (hToDestroy && IsWindow(hToDestroy)) PostMessageA(hToDestroy, WM_CLOSE, 0, 0);
#endif
std::cerr << "[dbg] load thread start ch=" << ch
<< " type=" << (int)t << " path=" << path << std::endl;