From f94b2fa7575e2c3014b39995534684bf3f36180d Mon Sep 17 00:00:00 2001 From: locphamtran Date: Sat, 15 Aug 2026 17:11:47 +0700 Subject: [PATCH] =?UTF-8?q?wip=20walkthrough=202=20bridge:=20jobEvent=5F/M?= =?UTF-8?q?sgWaitForMultipleObjects=20pump,=20SetThreadPriority=20+=20Powe?= =?UTF-8?q?rThrottling,=20isBypassed=20silence=20quanh=20assign/unload,=20?= =?UTF-8?q?closeGUI=20view->removed(),=20reloadForGUI=20reuse=20instance,?= =?UTF-8?q?=20bo=20mute=20khi=20GUI=20mo,=20CoInitializeEx=20MTA=20?= =?UTF-8?q?=E2=80=94=20CH=C6=AFA=20verified:=20driver=20test=201/3=20PASS?= =?UTF-8?q?=20(AV=20c0000005=20Nexus!GetPluginFactory=20luc=20attachView?= =?UTF-8?q?=20ch5,=20nghi=20MTA/bo=20mute),=20exe=20deploy=20giu=20ban=200?= =?UTF-8?q?cec3d3=20(28104a47)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- native_bridge/include/INativeInstrument.h | 1 + .../include/NativeInstrumentEngine.h | 5 + native_bridge/include/Vst3Instrument.h | 1 + native_bridge/src/NativeInstrumentEngine.cpp | 31 +- native_bridge/src/Vst3Instrument.cpp | 58 ++-- native_bridge/src/main.cpp | 276 ++++++++++-------- 6 files changed, 220 insertions(+), 152 deletions(-) diff --git a/native_bridge/include/INativeInstrument.h b/native_bridge/include/INativeInstrument.h index 5973fa7..23530a1 100644 --- a/native_bridge/include/INativeInstrument.h +++ b/native_bridge/include/INativeInstrument.h @@ -51,6 +51,7 @@ public: // with the channel worker pumping messages (see bridge main.cpp). virtual bool reloadForGUI() { return true; } virtual bool attachView(void* /*parentWindowHandle*/) { return false; } + virtual void resizeView(int /*w*/, int /*h*/) {} // 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). diff --git a/native_bridge/include/NativeInstrumentEngine.h b/native_bridge/include/NativeInstrumentEngine.h index 8dfa266..e478af2 100644 --- a/native_bridge/include/NativeInstrumentEngine.h +++ b/native_bridge/include/NativeInstrumentEngine.h @@ -12,6 +12,7 @@ #include #include #include +#include // FluidSynth (.sf2 / .sf3) class FluidSynthInstrument : public INativeInstrument { @@ -119,6 +120,9 @@ public: size_t count() const { std::lock_guard lock(mu_); return channels_.size(); } + void setBypassed(bool on) { isBypassed_.store(on); } + bool isBypassed() const { return isBypassed_.load(); } + private: static std::unique_ptr create_instrument(InstrumentType type); @@ -134,6 +138,7 @@ private: // compare inside -- Windows plugin paths are case-insensitive. bool channelQuiet(uint32_t ch) const; std::function editorOpenFn_; + std::atomic isBypassed_{false}; }; #endif // NATIVE_INSTRUMENT_ENGINE_H diff --git a/native_bridge/include/Vst3Instrument.h b/native_bridge/include/Vst3Instrument.h index c7fc992..29689bd 100644 --- a/native_bridge/include/Vst3Instrument.h +++ b/native_bridge/include/Vst3Instrument.h @@ -27,6 +27,7 @@ public: bool openGUI(void* parentWindowHandle) override; bool reloadForGUI() override; bool attachView(void* parentWindowHandle) override; + void resizeView(int w, int h) override; bool hasAttachedView() const override; void closeGUI() override; bool reload() override; diff --git a/native_bridge/src/NativeInstrumentEngine.cpp b/native_bridge/src/NativeInstrumentEngine.cpp index d876c72..0e5e0ee 100644 --- a/native_bridge/src/NativeInstrumentEngine.cpp +++ b/native_bridge/src/NativeInstrumentEngine.cpp @@ -13,6 +13,8 @@ #include #include #include +#include +#include // ----------------------------------------------------------------- // 1. SOUNDFONT ENGINE (.SF2 / .SF3) VIA FLUIDSYNTH C API @@ -194,7 +196,17 @@ bool InstrumentEngineManager::assign(uint32_t channel, InstrumentType type, // flows to it again. reloadingCh_[channel] = false; } - oldInst.reset(); + if (oldInst) { + isBypassed_.store(true); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + try { + oldInst.reset(); + } catch (...) { + isBypassed_.store(false); + throw; + } + isBypassed_.store(false); + } return true; } @@ -211,7 +223,17 @@ void InstrumentEngineManager::unload(uint32_t channel) { paths_.erase(channel); reloadingCh_[channel] = false; } - oldInst.reset(); + if (oldInst) { + isBypassed_.store(true); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + try { + oldInst.reset(); + } catch (...) { + isBypassed_.store(false); + throw; + } + isBypassed_.store(false); + } } std::string InstrumentEngineManager::pathOf(uint32_t channel) { @@ -313,6 +335,11 @@ void InstrumentEngineManager::allNotesOff() { } void InstrumentEngineManager::renderAll(float* outputL, float* outputR, uint32_t numSamples) { + if (isBypassed_.load()) { + std::memset(outputL, 0, numSamples * sizeof(float)); + std::memset(outputR, 0, numSamples * sizeof(float)); + return; + } std::lock_guard lock(mu_); std::memset(outputL, 0, numSamples * sizeof(float)); std::memset(outputR, 0, numSamples * sizeof(float)); diff --git a/native_bridge/src/Vst3Instrument.cpp b/native_bridge/src/Vst3Instrument.cpp index e603533..822fca3 100644 --- a/native_bridge/src/Vst3Instrument.cpp +++ b/native_bridge/src/Vst3Instrument.cpp @@ -97,7 +97,13 @@ constexpr ParamID kHostMidiProgramChange = 0x3000; // Minimal IComponentHandler so the plugin can inform the host of param edits. class HostComponentHandler : public IComponentHandler { public: - tresult queryInterface(const char*, void** v) override { + tresult queryInterface(const char* iid, void** v) override { + if (std::memcmp(iid, Steinberg::Vst::IComponentHandler::iid, 16) == 0 || + std::memcmp(iid, Steinberg::FUnknown::iid, 16) == 0) { + *v = static_cast(this); + addRef(); + return kResultOk; + } *v = nullptr; return kNoInterface; } @@ -112,7 +118,13 @@ public: // Minimal IPlugFrame so plugins can resize their editor view. class HostPlugFrame : public Steinberg::IPlugFrame { public: - tresult queryInterface(const char*, void** v) override { + tresult queryInterface(const char* iid, void** v) override { + if (std::memcmp(iid, Steinberg::IPlugFrame::iid, 16) == 0 || + std::memcmp(iid, Steinberg::FUnknown::iid, 16) == 0) { + *v = static_cast(this); + addRef(); + return kResultOk; + } *v = nullptr; return kNoInterface; } @@ -531,18 +543,6 @@ bool Vst3Instrument::reloadForGUI() { #else auto* s = static_cast(state_); if (!s || !s->controller) return false; - if (s->view && guiAttached_) return true; - if (hasAttachedOnce_ && !guiAttached_) { - // Reopen GUI sau khi dong: mot so plugin (Nexus...) hang o - // view->attached() LAN 2 tren cung component instance. Tao lai inst - // moi tren CUNG worker thread (COM STA con song) roi attach lai. - // PHAN NAY PHAI chay tren worker thread — apartment cua channel song - // o day; chay tren thread tam thi apartment moi chet ngay sau do - // (reopen lan sau -> treo nhu bridge_like). - if (!reload()) return false; - s = static_cast(state_); - if (!s || !s->controller) return false; - } return true; #endif } @@ -598,6 +598,16 @@ bool Vst3Instrument::attachView(void* parentWindowHandle) { #endif } +void Vst3Instrument::resizeView(int w, int h) { +#ifdef HAVE_VST3SDK + auto* s = static_cast(state_); + if (s && s->view) { + Steinberg::ViewRect rect(0, 0, w, h); + s->view->onSize(&rect); + } +#endif +} + bool Vst3Instrument::hasAttachedView() const { return state_ != nullptr && guiAttached_; } @@ -632,16 +642,16 @@ void Vst3Instrument::closeGUI() { return; #else auto* s = static_cast(state_); - // Do NOT call s->view->removed(): Nexus's removed() enters an internal - // modal wait for a message only this thread's message pump can dispatch - // — but this thread is inside removed(), so it deadlocks forever - // (observed: closeGUI job never returns; wdisp flood from the editor). - // Nulling the view and letting the next terminate() (reload / destructor) - // tear the editor windows down on this same thread is the deterministic - // path out. Callers (post_close_gui / load job) additionally destroy the - // editor's child windows right here on this thread so the plugin stops - // pumping; the leaked editor object is inert once its windows are gone. - if (s) s->view = nullptr; + if (s && s->view) { + if (guiAttached_) { + try { + s->view->removed(); + } catch (...) { + std::cerr << "[Vst3Instrument] Exception inside view->removed()" << std::endl; + } + } + s->view = nullptr; + } guiAttached_ = false; #endif } diff --git a/native_bridge/src/main.cpp b/native_bridge/src/main.cpp index 7470ddb..859e628 100644 --- a/native_bridge/src/main.cpp +++ b/native_bridge/src/main.cpp @@ -190,6 +190,33 @@ static LRESULT CALLBACK VstWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARA } } if (ch != UINT32_MAX) post_close_gui(ch, hwnd); + } else if (uMsg == WM_ERASEBKGND) { + return 1; // Do not paint background to prevent white flashes + } else if (uMsg == WM_PAINT) { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hwnd, &ps); + EndPaint(hwnd, &ps); + return 0; + } else if (uMsg == WM_SIZE) { + int w = LOWORD(lParam); + int h = HIWORD(lParam); + HWND child = FindWindowExA(hwnd, nullptr, nullptr, nullptr); + if (child) { + MoveWindow(child, 0, 0, w, h, TRUE); + } + uint32_t ch = UINT32_MAX; + { + std::lock_guard lock(g_guiMutex); + auto it = g_hwndToCh.find(hwnd); + if (it != g_hwndToCh.end()) ch = it->second; + } + if (ch != UINT32_MAX && g_engine) { + auto* inst = g_engine->get(ch); + if (inst) { + inst->resizeView(w, h); + } + } + return 0; } return DefWindowProcA(hwnd, uMsg, wParam, lParam); } @@ -209,18 +236,17 @@ static std::map> g_pendingGui; // B9: native Win32 window for the VST editor (replaces the WebView2 surface — // the HTML window was drawn ON TOP of the plugin GUI). MUST be created on the // ChannelWorker thread so the worker's idle message pump services its messages. +static std::once_flag registerWindowOnce; static void* create_native_vst_window(const char* title) { #ifdef _WIN32 static const char* kWndClass = "SonicForge_Native_VST3_Class"; - static bool registered = false; - if (!registered) { + std::call_once(registerWindowOnce, []() { WNDCLASSA wc = {}; wc.lpfnWndProc = VstWindowProc; wc.hInstance = GetModuleHandleA(nullptr); wc.lpszClassName = kWndClass; RegisterClassA(&wc); - registered = true; - } + }); HWND hwnd = CreateWindowExA(0, kWndClass, title ? title : "VST", WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, @@ -242,6 +268,9 @@ static void* create_native_vst_window(const char* title) { class ChannelWorker { public: ChannelWorker() { +#ifdef _WIN32 + jobEvent_ = CreateEvent(nullptr, FALSE, FALSE, nullptr); +#endif th_ = std::thread([this] { #ifdef _WIN32 { @@ -256,6 +285,9 @@ public: // Global hooks fail from an exe module — install per thread, and // this thread creates the plugin editor windows. SetWindowsHookExW(WH_CBT, ImeCbtHookProc, GetModuleHandleW(nullptr), GetCurrentThreadId()); + + // Elevate VST GUI thread priority to prevent throttling + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); #endif std::unique_lock lk(mu_); for (;;) { @@ -268,18 +300,14 @@ public: lk.lock(); continue; } - cv_.wait_for(lk, std::chrono::milliseconds(5)); - // Pump THIS thread's message queue while idle — VST editor - // windows are created on this thread, their messages must be - // dispatched here or the editor freezes after attach. lk.unlock(); MSG msg; - // Cap the idle pump: a busy editor (Nexus posts timer/paint - // messages continuously) otherwise starves this worker's job - // queue forever — post_close_gui never runs, Option B's 5s - // close wait times out, two editors stay alive (deadlock). - for (int pumped = 0; pumped < 16; ++pumped) { + bool hadMessages = false; + // Cap the idle pump per iteration to avoid starving the job queue, + // but pump up to 128 messages for high GUI responsiveness. + for (int pumped = 0; pumped < 128; ++pumped) { if (!PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) break; + hadMessages = true; // CRASH FIX (0xc000041d): JUCE editor child windows of a // plugin DLL are owned by the FIRST worker that ran the // DLL's global JUCE MessageManager - not by the channel @@ -307,7 +335,7 @@ public: if (std::strncmp(cls, "JUCE_", 5) == 0) { DWORD ot = GetWindowThreadProcessId(msg.hwnd, nullptr); if (ot) { - std::lock_guard lk(g_juceTidsMutex); + std::lock_guard lock(g_juceTidsMutex); g_juceOwnerTids.insert(ot); } } @@ -328,6 +356,19 @@ public: } } lk.lock(); + if (!hadMessages && jobs_.empty()) { +#ifdef _WIN32 + if (jobEvent_) { + lk.unlock(); + MsgWaitForMultipleObjects(1, &jobEvent_, FALSE, 10, QS_ALLINPUT); + lk.lock(); + } else { + cv_.wait_for(lk, std::chrono::milliseconds(2)); + } +#else + cv_.wait_for(lk, std::chrono::milliseconds(2)); +#endif + } } #ifdef _WIN32 { @@ -343,14 +384,30 @@ public: std::lock_guard lk(mu_); stop_ = true; } +#ifdef _WIN32 + if (jobEvent_) { + SetEvent(jobEvent_); + } +#endif cv_.notify_all(); if (th_.joinable()) th_.join(); +#ifdef _WIN32 + if (jobEvent_) { + CloseHandle(jobEvent_); + jobEvent_ = nullptr; + } +#endif } void post(std::function job) { { std::lock_guard lk(mu_); jobs_.push_back(std::move(job)); } +#ifdef _WIN32 + if (jobEvent_) { + SetEvent(jobEvent_); + } +#endif cv_.notify_all(); } // Run job on THIS worker and wait (5s cap) until it finished. Used to @@ -372,6 +429,11 @@ public: doneCv.notify_all(); }); } +#ifdef _WIN32 + if (jobEvent_) { + SetEvent(jobEvent_); + } +#endif cv_.notify_all(); std::unique_lock dk(doneMx); return doneCv.wait_for(dk, std::chrono::seconds(5), [&] { return done; }); @@ -383,6 +445,9 @@ private: std::condition_variable cv_; std::deque> jobs_; bool stop_ = false; +#ifdef _WIN32 + HANDLE jobEvent_ = nullptr; +#endif }; // --- same-plugin-DLL silence helpers ---------------------------------------- @@ -573,46 +638,18 @@ static void post_close_gui(uint32_t ch, HWND hwnd) { // Silence first: the audio loop must not process() the instance // while we tear its editor down on this thread. g_engine->setReloading(ch, true); - // Close-in-flight: until the fresh instance is loaded, no other - // job may unmute this channel (its plugin is being torn down). { std::lock_guard lk(g_attachMutex); g_closeInFlight[ch] = true; } if (auto* i = g_engine->get(ch)) { - // closeGUI() nulls the view WITHOUT view->removed() (Nexus - // removed() deadlocks this worker - modal wait for a message - // only its own pump can dispatch, but it is inside removed()). try { i->closeGUI(); } catch (...) { std::cerr << "[NativeBridge] closeGUI EXCEPTION ch=" << ch << " — plugin threw" << std::endl; } - // CRASH FIX: the editor was dropped WITHOUT view->removed() — - // the instance's editor state is dangling, so the audio loop - // must NOT process() it. Rebuild a fresh instance NOW on this - // worker (its COM STA apartment is alive); reload() == - // terminate + loadPlugin (createInstance). Same-plugin - // channels are silenced so no second thread enters this DLL - // during createInstance (2 threads in one DLL -> Nexus exits - // silently, observed in probes). Serialized with other loads - // by g_loadMutex. - std::vector sp = same_plugin_channels(ch); - for (uint32_t y : sp) { - g_engine->setReloading(y, true); - } #ifdef _WIN32 - // CRASH FIX (0xc000041d): destroy the editor's child windows - // on the thread that OWNS them (the plugin's JUCE - // MessageManager thread - a single worker), never - // cross-thread. Wait for each destroy to finish (owner worker - // serializes it with its pump) BEFORE reload() - otherwise - // reload's createInstance enters the DLL while the owner - // thread is still inside the plugin wndproc (2 threads in one - // DLL -> Nexus crash). Kept OUTSIDE g_loadMutex: the wait - // must not block other loads (the owner worker may itself be - // waiting on that lock). disable_ime_contexts(hwnd); if (hwnd && IsWindow(hwnd)) { while (HWND c = FindWindowExA(hwnd, nullptr, nullptr, nullptr)) { @@ -620,51 +657,13 @@ static void post_close_gui(uint32_t ch, HWND hwnd) { } } #endif - { - // reload() = terminate + loadPlugin (createInstance) — - // serialized with all other plugin-DLL entry points. - // Nexus may throw a C++ exception here too; catch it, - // treat as failed reload, and let the restore below - // unmute the channel (instance may be broken; next - // load/assign rebuilds it). - std::lock_guard lg(g_loadMutex); - try { - i->reload(); - } catch (...) { - std::cerr << "[NativeBridge] closeGUI reload EXCEPTION ch=" << ch - << " — plugin threw (createInstance)" << std::endl; - } - } - // Editor detached + fresh instance loaded: drop editor-open - // state (channelQuiet falls back to reloading flags only). - clear_editor_open(ch); - // createInstance is done: unmuting is safe again. But if an - // attach for the SAME plugin path is still running (its - // createView must not race process() on any same-path - // channel), leave the silence in place — the attach job's - // restore unmutes everything once attachView finished. - { - std::lock_guard lk(g_attachMutex); - g_closeInFlight[ch] = false; - } - bool pathBusy = false; - { - std::lock_guard lk(g_attachMutex); - std::string p = lower_plugin_path(ch); - pathBusy = !p.empty() && - std::find(g_attachPaths.begin(), g_attachPaths.end(), p) != g_attachPaths.end(); - } - if (!pathBusy) { - for (uint32_t y : sp) unmute_if_not_closing(y, "closeGUI"); - g_engine->setReloading(ch, false); - } - } else { - // Instrument already gone (unloaded) — nothing to rebuild. - g_engine->setReloading(ch, false); - clear_editor_open(ch); + } + clear_editor_open(ch); + { std::lock_guard lk(g_attachMutex); g_closeInFlight[ch] = false; } + g_engine->setReloading(ch, false); }); } } @@ -672,6 +671,24 @@ static void post_close_gui(uint32_t ch, HWND hwnd) { int main(int argc, char* argv[]) { std::cout << "[NativeBridge] Starting DAW Host Bridge Engine..." << std::endl; #ifdef _WIN32 + CoInitializeEx(nullptr, COINIT_MULTITHREADED); + + // Disable Power Throttling (Efficiency Mode) for this background process +#ifndef PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION +#define PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION 0x00000004 +#endif +#ifndef PROCESS_POWER_THROTTLING_EXECUTION_SPEED +#define PROCESS_POWER_THROTTLING_EXECUTION_SPEED 0x00000001 +#endif + PROCESS_POWER_THROTTLING_STATE powerState = {}; + powerState.Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION; + powerState.ControlMask = PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION | PROCESS_POWER_THROTTLING_EXECUTION_SPEED; + powerState.StateMask = 0; // Turn off throttling + SetProcessInformation(GetCurrentProcess(), ProcessPowerThrottling, &powerState, sizeof(powerState)); + + // Elevate main audio processing thread to time-critical priority + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); + // DPI: declare per-monitor v2 FIRST (before any window is created). // Without it, on scaled displays (125/150%) the plugin's own renderer // paints at 100% into an OS-scaled window -> blank/white panel with dark @@ -751,10 +768,8 @@ int main(int argc, char* argv[]) { // Editor-open predicate: channelQuiet() checks whether ANY editor is // attached for the channel's plugin DLL path (refcounted in // g_editorPathCount, updated by the open/close GUI jobs). - instruments.setEditorOpenPredicate([](const std::string& lp) { - std::lock_guard lk(g_editorMutex); - auto it = g_editorPathCount.find(lp); - return it != g_editorPathCount.end() && it->second > 0; + instruments.setEditorOpenPredicate([](const std::string& /*lp*/) { + return false; }); // Per-channel persistent workers: loadPlugin + openGUI run on the SAME // thread whose COM STA apartment stays alive for the channel's lifetime @@ -868,44 +883,9 @@ int main(int argc, char* argv[]) { if (!workers[guiCh]) { workers[guiCh] = std::make_unique(); } - void* hwnd = (void*)arg1; -#ifdef _WIN32 - // Window PHAI thuoc MAIN thread (audio loop pump nay dispatch - // messages cua no moi vong lap). Tao/cap nhat window ngay tai day. - HWND nativeHwnd = nullptr; - { - std::lock_guard lock(g_guiMutex); - auto it = g_guiWindows.find(guiCh); - if (it != g_guiWindows.end()) nativeHwnd = (HWND)it->second; - } - if (hwnd == 0) { - if (nativeHwnd && IsWindow(nativeHwnd)) { - hwnd = (void*)nativeHwnd; - SetWindowTextA(nativeHwnd, pluginId.c_str()); - ShowWindow(nativeHwnd, SW_SHOW); - SetForegroundWindow(nativeHwnd); - // Reuse: cap nhat USERDATA (channel+1) - inst CU da bi thay - // the boi assign() -> WM_DESTROY sau nay lookup inst MOI. - SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1)); - } else { - nativeHwnd = (HWND)create_native_vst_window(pluginId.c_str()); - if (!nativeHwnd) { - std::cerr << "[NativeBridge] GUI create window FAILED plugin=" << pluginId << std::endl; - return; - } - { - std::lock_guard lock(g_guiMutex); - g_guiWindows[guiCh] = nativeHwnd; // keep window alive - g_hwndToCh[nativeHwnd] = guiCh; // WM_DESTROY cleanup - } - SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1)); - hwnd = (void*)nativeHwnd; - } - } -#endif - workers[guiCh]->post([&instruments, guiCh, hwnd, arg2 = pluginId]() { + workers[guiCh]->post([&instruments, guiCh, arg1, arg2 = pluginId]() { if (!instruments.get(guiCh)) { - std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << hwnd + std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << (void*)arg1 << " plugin=" << arg2 << " ch=" << guiCh << " (no instrument loaded)" << std::endl; return; } @@ -914,6 +894,44 @@ int main(int argc, char* argv[]) { return; } } + void* hwnd = (void*)arg1; +#ifdef _WIN32 + HWND nativeHwnd = nullptr; + { + std::lock_guard lock(g_guiMutex); + auto it = g_guiWindows.find(guiCh); + if (it != g_guiWindows.end()) nativeHwnd = (HWND)it->second; + } + if (hwnd == 0) { + if (nativeHwnd && IsWindow(nativeHwnd)) { + hwnd = (void*)nativeHwnd; + SetWindowTextA(nativeHwnd, arg2.c_str()); + ShowWindow(nativeHwnd, SW_SHOW); + SetForegroundWindow(nativeHwnd); + // Reuse: cap nhat USERDATA (channel+1) - inst CU da bi thay + // the boi assign() -> WM_DESTROY sau nay lookup inst MOI. + SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1)); + } else { + nativeHwnd = (HWND)create_native_vst_window(arg2.c_str()); + if (!nativeHwnd) { + std::cerr << "[NativeBridge] GUI create window FAILED plugin=" << arg2 << std::endl; + return; + } + { + std::lock_guard lock(g_guiMutex); + g_guiWindows[guiCh] = nativeHwnd; // keep window alive + g_hwndToCh[nativeHwnd] = guiCh; // WM_DESTROY cleanup + } + SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1)); + hwnd = (void*)nativeHwnd; + } + } +#endif + if (auto* inst0 = instruments.get(guiCh)) { + if (inst0->hasAttachedView()) { + return; + } + } #ifdef _WIN32 // Option B: chi 1 editor VST mo tai 1 thoi diem toan // bridge. Instance thu 2 cua CUNG plugin (Nexus) attach @@ -1045,7 +1063,10 @@ int main(int argc, char* argv[]) { // std::terminate the worker). Treat as attach failure; // the else branch below closes the empty window. try { + instruments.setBypassed(true); + sleep_ms(10); ok = inst->reloadForGUI(); + instruments.setBypassed(false); // IME recursion fix: strip the editor's IMC before // attachView creates the editor child — a live IMC on // a JUCE_ window + plugin wndproc (ImmIsUIMessageW -> @@ -1054,6 +1075,7 @@ int main(int argc, char* argv[]) { if (ok) disable_ime_contexts((HWND)hwnd); if (ok) ok = inst->attachView(hwnd); } catch (...) { + instruments.setBypassed(false); std::cerr << "[NativeBridge] GUI attach EXCEPTION ch=" << guiCh << " — plugin threw (createView/attached)" << std::endl; ok = false; @@ -1105,7 +1127,8 @@ int main(int argc, char* argv[]) { #endif std::cout << "[NativeBridge] GUI attached hwnd=" << hwnd << " plugin=" << arg2 << " ch=" << guiCh - << " (channel muted while editor open)" << std::endl; + << " (channel active with editor open)" << std::endl; + instruments.setReloading(guiCh, false); } else { // Attach failed -> no editor running -> safe to process again. @@ -1396,6 +1419,7 @@ int main(int argc, char* argv[]) { UnmapViewOfFile(shmIPC); CloseHandle(hMapFile); timeEndPeriod(1); + CoUninitialize(); #else std::free(shmIPC); #endif