fix: GUI VST native window qua bridge + audio path bridge (SF2 preview/play câm)
- open_vst_gui: bo WebviewWindowBuilder/thread, chi push_control type=4 (hwnd=0 -> bridge tao window) - main.cpp: create_native_vst_window (class SonicForge_Native_VST3_Class, 800x600, khong TOPMOST), tao trong ChannelWorker job, map guiWindows, capture arg2 by value (fix dangling) - app.jsx: guard isBridgeActive() 8 cho -> bridge active thi moi note di router -> pushEvent -> bridge (truoc day SF2 cam vi HAS_PYFLUIDSYNTH=FALSE -> /soundfont-render 501; VST3 path cu dung nativeSf/Carla) - E2E: SF2 NOTE_ON qua dispatchMidiEvent -> SHM peak 0.029745; Nexus GUI native hwnd OK (license/preset) - docs: TASKS.md + TEST_NOTES.md ghi batch fix + ket qua; gitignore vendor/junk
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
// native_bridge/src/gui_probe.cpp — standalone VST3 GUI attach probe.
|
||||
// Debug tool (NOT shipped with the DAW): loads a VST3 with Vst3Instrument and
|
||||
// tests openGUI under different thread/pump/parent-window setups so we can
|
||||
// find why view->attached() hangs inside the bridge.
|
||||
#include "Vst3Instrument.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
static void pump_for(int secs) {
|
||||
auto t0 = std::chrono::steady_clock::now();
|
||||
while (std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::steady_clock::now() - t0).count() < secs) {
|
||||
MSG msg;
|
||||
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessageW(&msg);
|
||||
}
|
||||
Sleep(5);
|
||||
}
|
||||
}
|
||||
|
||||
static void sleep_for(int secs) { Sleep((DWORD)(secs * 1000)); }
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 2) {
|
||||
printf("usage: gui_probe <plugin.vst3> [variant] [secs] [hwnd]\n"
|
||||
"variants: main_own | worker_own_nopump | worker_own_pump | worker_foreign_nopump | worker_foreign_pump | bridge_like | same_thread\n");
|
||||
return 2;
|
||||
}
|
||||
std::string path = argv[1];
|
||||
std::string variant = argc > 2 ? argv[2] : "main_own";
|
||||
int secs = argc > 3 ? atoi(argv[3]) : 20;
|
||||
|
||||
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||
|
||||
// Watchdog: never let a stuck plugin hang this probe forever.
|
||||
std::thread([secs]() {
|
||||
Sleep((DWORD)(secs + 10) * 1000);
|
||||
printf("WATCHDOG-KILL\n");
|
||||
fflush(stdout);
|
||||
TerminateProcess(GetCurrentProcess(), 0);
|
||||
}).detach();
|
||||
|
||||
HWND ownHwnd = 0;
|
||||
{
|
||||
WNDCLASSEX wc = {};
|
||||
wc.cbSize = sizeof(wc);
|
||||
wc.lpfnWndProc = DefWindowProc;
|
||||
wc.hInstance = GetModuleHandle(nullptr);
|
||||
wc.lpszClassName = "GuiProbeClass";
|
||||
RegisterClassEx(&wc);
|
||||
ownHwnd = CreateWindowEx(0, "GuiProbeClass", "GuiProbe", WS_OVERLAPPEDWINDOW,
|
||||
0, 0, 800, 600, nullptr, nullptr, wc.hInstance, nullptr);
|
||||
printf("ownHwnd=%p\n", (void*)ownHwnd);
|
||||
}
|
||||
|
||||
void* targetHwnd = (void*)ownHwnd;
|
||||
if (argc > 4) targetHwnd = (void*)(uintptr_t)strtoull(argv[4], nullptr, 0);
|
||||
|
||||
printf("loading %s\n", path.c_str());
|
||||
fflush(stdout);
|
||||
Vst3Instrument inst;
|
||||
bool isDeferred = (variant == "bridge_like" || variant == "same_thread");
|
||||
bool ok = true;
|
||||
if (!isDeferred) {
|
||||
ok = inst.loadPlugin(path, 44100.0);
|
||||
printf("load=%d\n", ok ? 1 : 0);
|
||||
fflush(stdout);
|
||||
if (!ok) return 1;
|
||||
}
|
||||
|
||||
std::atomic<bool> done{false};
|
||||
std::atomic<bool> result{false};
|
||||
|
||||
auto attach_job = [&]() {
|
||||
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||
printf("[%s] openGUI start hwnd=%p\n", variant.c_str(), targetHwnd);
|
||||
fflush(stdout);
|
||||
bool r = inst.openGUI(targetHwnd);
|
||||
printf("[%s] openGUI returned=%d\n", variant.c_str(), r ? 1 : 0);
|
||||
fflush(stdout);
|
||||
result = r;
|
||||
done = true;
|
||||
};
|
||||
|
||||
if (variant == "main_own") {
|
||||
attach_job(); // editorhost pattern: attached on main thread, pump after
|
||||
pump_for(secs);
|
||||
} else if (variant == "worker_own_nopump" || variant == "worker_foreign_nopump") {
|
||||
std::thread t(attach_job);
|
||||
sleep_for(secs);
|
||||
if (!done.load()) { printf("RESULT: TIMEOUT (no pump)\n"); return 3; }
|
||||
} else if (variant == "worker_own_pump" || variant == "worker_foreign_pump") {
|
||||
std::thread t(attach_job);
|
||||
pump_for(secs);
|
||||
if (!done.load()) { printf("RESULT: TIMEOUT (pump concurrent)\n"); return 3; }
|
||||
} else if (variant == "bridge_like") {
|
||||
// Bridge pattern: plugin loaded on thread A which then EXITS (COM
|
||||
// apartment dies), openGUI on separate thread B, main pumps.
|
||||
std::thread loadA([&]() {
|
||||
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||
printf("[bridge_like] loadPlugin on thread A\n");
|
||||
fflush(stdout);
|
||||
bool r = inst.loadPlugin(path, 44100.0);
|
||||
printf("[bridge_like] load=%d (thread A exiting)\n", r ? 1 : 0);
|
||||
fflush(stdout);
|
||||
CoUninitialize();
|
||||
});
|
||||
loadA.join();
|
||||
std::thread t(attach_job); // thread B, after A exited
|
||||
pump_for(secs);
|
||||
if (!done.load()) { printf("RESULT: TIMEOUT (apartment dead)\n"); return 3; }
|
||||
} else if (variant == "same_thread") {
|
||||
// Load and openGUI on the SAME worker thread, kept alive; main pumps.
|
||||
std::thread t([&]() {
|
||||
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||
printf("[same_thread] loadPlugin on worker\n");
|
||||
fflush(stdout);
|
||||
bool r = inst.loadPlugin(path, 44100.0);
|
||||
printf("[same_thread] load=%d\n", r ? 1 : 0);
|
||||
fflush(stdout);
|
||||
if (r) attach_job();
|
||||
});
|
||||
pump_for(secs);
|
||||
if (!done.load()) { printf("RESULT: TIMEOUT (same_thread)\n"); return 3; }
|
||||
} else {
|
||||
printf("unknown variant %s\n", variant.c_str());
|
||||
return 2;
|
||||
}
|
||||
|
||||
printf("RESULT: done=%d attached_ok=%d\n", done.load() ? 1 : 0, result.load() ? 1 : 0);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user