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:
@@ -6213,11 +6213,11 @@ const ensureAndOpenVstGui = async (trackId, instrumentId) => {
|
||||
try { await openVstGuiRetry(instrumentId, bch, 20); } catch (e) { console.warn('[Bridge] openNativeGUI fail:', e); }
|
||||
};
|
||||
// V8 bug 4 + V9 bug 3/6/7: C++ load instrument ASYNC (worker thread) — OPEN_GUI
|
||||
// som → C++ defer (chi log stderr, invoke van tra Ok) → retry PHAI chay DU so
|
||||
// lan, khong `return true` som. Mo GUI co the can cho den khi assign() xong
|
||||
// (Nexus ~1-2s) — 20 lan x 500ms, window reuse khi load xong.
|
||||
// som → C++ defer va tu fulfil khi assign() xong (pending-GUI sweep) → khong
|
||||
// can spam. 20 lan x 500ms tao deferred storm (50+ event/moi restart) va
|
||||
// re-attach view dang attached (crash USER32 0xc000041d). Cap 5.
|
||||
const openVstGuiRetry = async (instrumentId, bch, attempts) => {
|
||||
const n = (attempts && attempts > 0) ? attempts : 20;
|
||||
const n = Math.min((attempts && attempts > 0) ? attempts : 5, 5);
|
||||
for (let i = 0; i < n; i++) {
|
||||
try { await window.NativeBridgeService.openNativeGUI(instrumentId, bch); }
|
||||
catch (e) { console.warn('[Bridge] openNativeGUI retry', i + 1, e); }
|
||||
|
||||
@@ -46,6 +46,10 @@ public:
|
||||
// with the channel worker pumping messages (see bridge main.cpp).
|
||||
virtual bool reloadForGUI() { return true; }
|
||||
virtual bool attachView(void* /*parentWindowHandle*/) { return false; }
|
||||
// True while the editor view is attached (GUI window open). The bridge uses
|
||||
// it to dedupe repeated OPEN_GUI (frontend retry spam) — re-attaching a
|
||||
// view that is already attached corrupts plugins (Nexus createView→null).
|
||||
virtual bool hasAttachedView() const { return false; }
|
||||
|
||||
// Real-time Audio PCM Float32 rendering loop
|
||||
virtual void processAudioBlock(float* outputL, float* outputR, uint32_t numSamples) = 0;
|
||||
|
||||
@@ -27,6 +27,7 @@ public:
|
||||
bool openGUI(void* parentWindowHandle) override;
|
||||
bool reloadForGUI() override;
|
||||
bool attachView(void* parentWindowHandle) override;
|
||||
bool hasAttachedView() const override;
|
||||
void closeGUI() override;
|
||||
void setChannel(uint32_t ch) { channel_ = ch; }
|
||||
bool needsReload() const override { return hasAttachedOnce_ && !guiAttached_; }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user