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); }
|
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
|
// 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
|
// som → C++ defer va tu fulfil khi assign() xong (pending-GUI sweep) → khong
|
||||||
// lan, khong `return true` som. Mo GUI co the can cho den khi assign() xong
|
// can spam. 20 lan x 500ms tao deferred storm (50+ event/moi restart) va
|
||||||
// (Nexus ~1-2s) — 20 lan x 500ms, window reuse khi load xong.
|
// re-attach view dang attached (crash USER32 0xc000041d). Cap 5.
|
||||||
const openVstGuiRetry = async (instrumentId, bch, attempts) => {
|
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++) {
|
for (let i = 0; i < n; i++) {
|
||||||
try { await window.NativeBridgeService.openNativeGUI(instrumentId, bch); }
|
try { await window.NativeBridgeService.openNativeGUI(instrumentId, bch); }
|
||||||
catch (e) { console.warn('[Bridge] openNativeGUI retry', i + 1, e); }
|
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).
|
// with the channel worker pumping messages (see bridge main.cpp).
|
||||||
virtual bool reloadForGUI() { return true; }
|
virtual bool reloadForGUI() { return true; }
|
||||||
virtual bool attachView(void* /*parentWindowHandle*/) { return false; }
|
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
|
// Real-time Audio PCM Float32 rendering loop
|
||||||
virtual void processAudioBlock(float* outputL, float* outputR, uint32_t numSamples) = 0;
|
virtual void processAudioBlock(float* outputL, float* outputR, uint32_t numSamples) = 0;
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ public:
|
|||||||
bool openGUI(void* parentWindowHandle) override;
|
bool openGUI(void* parentWindowHandle) override;
|
||||||
bool reloadForGUI() override;
|
bool reloadForGUI() override;
|
||||||
bool attachView(void* parentWindowHandle) override;
|
bool attachView(void* parentWindowHandle) override;
|
||||||
|
bool hasAttachedView() const override;
|
||||||
void closeGUI() override;
|
void closeGUI() override;
|
||||||
void setChannel(uint32_t ch) { channel_ = ch; }
|
void setChannel(uint32_t ch) { channel_ = ch; }
|
||||||
bool needsReload() const override { return hasAttachedOnce_ && !guiAttached_; }
|
bool needsReload() const override { return hasAttachedOnce_ && !guiAttached_; }
|
||||||
|
|||||||
@@ -635,6 +635,10 @@ bool Vst3Instrument::attachView(void* parentWindowHandle) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Vst3Instrument::hasAttachedView() const {
|
||||||
|
return state_ != nullptr && guiAttached_;
|
||||||
|
}
|
||||||
|
|
||||||
bool Vst3Instrument::reload() {
|
bool Vst3Instrument::reload() {
|
||||||
#ifndef HAVE_VST3SDK
|
#ifndef HAVE_VST3SDK
|
||||||
return false;
|
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();
|
if (auto* i = g_engine->get(ch)) i->closeGUI();
|
||||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||||
g_hwndToCh.erase(hwnd);
|
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 {
|
} else {
|
||||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||||
g_hwndToCh.erase(hwnd);
|
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 };
|
g_pendingGui[guiCh] = { (void*)arg1, pluginId };
|
||||||
return;
|
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>();
|
if (!workers[guiCh]) workers[guiCh] = std::make_unique<ChannelWorker>();
|
||||||
void* hwnd = (void*)arg1;
|
void* hwnd = (void*)arg1;
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@@ -405,6 +420,13 @@ int main(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
std::cerr << "[dbg] openGUI thread start hwnd=" << hwnd
|
std::cerr << "[dbg] openGUI thread start hwnd=" << hwnd
|
||||||
<< " plugin=" << arg2 << " ch=" << guiCh << std::endl;
|
<< " 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
|
#ifdef _WIN32
|
||||||
// Option B: chi 1 editor VST mo tai 1 thoi diem toan
|
// Option B: chi 1 editor VST mo tai 1 thoi diem toan
|
||||||
// bridge. Instance thu 2 cua CUNG plugin (Nexus) attach
|
// bridge. Instance thu 2 cua CUNG plugin (Nexus) attach
|
||||||
@@ -468,9 +490,17 @@ int main(int argc, char* argv[]) {
|
|||||||
if (ok)
|
if (ok)
|
||||||
std::cout << "[NativeBridge] GUI attached hwnd=" << hwnd
|
std::cout << "[NativeBridge] GUI attached hwnd=" << hwnd
|
||||||
<< " plugin=" << arg2 << " ch=" << guiCh << std::endl;
|
<< " plugin=" << arg2 << " ch=" << guiCh << std::endl;
|
||||||
else
|
else {
|
||||||
std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << hwnd
|
std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << hwnd
|
||||||
<< " plugin=" << arg2 << std::endl;
|
<< " 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():
|
// DONG cua so editor dang mo cua channel TRUOC khi assign():
|
||||||
// thay the inst (VST3 -> SF2/inst khac) ma editor con song ->
|
// thay the inst (VST3 -> SF2/inst khac) ma editor con song ->
|
||||||
// old inst destructor goi view->removed() tren HWND con hoat
|
// old inst destructor goi view->removed() tren HWND con hoat
|
||||||
// dong -> plugin block -> treo bridge. DestroyWindow chay tren
|
// dong -> plugin block -> treo bridge.
|
||||||
// CUNG worker thread so huu window; WM_DESTROY goi closeGUI()
|
// QUAN TRONG: window duoc tao tren MAIN thread (handleOpenGui
|
||||||
// dung thu tu. Phai erase map TRUOC DestroyWindow (WM_DESTROY
|
// goi create_native_vst_window o main loop) — DestroyWindow tu
|
||||||
// handler lay g_guiMutex lai — khong duoc giu lock khi destroy).
|
// 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;
|
HWND hToDestroy = nullptr;
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||||
@@ -527,7 +560,7 @@ int main(int argc, char* argv[]) {
|
|||||||
g_hwndToCh.erase(hToDestroy);
|
g_hwndToCh.erase(hToDestroy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hToDestroy && IsWindow(hToDestroy)) DestroyWindow(hToDestroy);
|
if (hToDestroy && IsWindow(hToDestroy)) PostMessageA(hToDestroy, WM_CLOSE, 0, 0);
|
||||||
#endif
|
#endif
|
||||||
std::cerr << "[dbg] load thread start ch=" << ch
|
std::cerr << "[dbg] load thread start ch=" << ch
|
||||||
<< " type=" << (int)t << " path=" << path << std::endl;
|
<< " type=" << (int)t << " path=" << path << std::endl;
|
||||||
|
|||||||
Reference in New Issue
Block a user