G4.1: sandbox plugin host (plugin_host.exe) PoC — Nexus runs in child process, GUI + audio via SHM, crash isolation verified
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
// native_bridge/src/plugin_host_main.cpp - G4.1 sandbox VST3 host (child process).
|
||||
// Proof-of-concept (NOT shipped): runs ONE VST3 out-of-process. The plugin's
|
||||
// own crash can only kill this process, never the DAW/bridge. Audio + MIDI +
|
||||
// heartbeat travel over a child-owned SHM mapping reusing the SharedAudioBufferIPC
|
||||
// layout, so the bridge can later adopt the same struct without layout churn.
|
||||
//
|
||||
// argv: --shm <name> --path <vst3> [--sr <rate>] [--block <n>] [--parent <pid>]
|
||||
#include "Vst3Instrument.h"
|
||||
#include "SharedMemoryIPC.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
static bool parent_alive(uint32_t pid) {
|
||||
#ifdef _WIN32
|
||||
HANDLE h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
|
||||
if (!h) return false;
|
||||
CloseHandle(h);
|
||||
return true;
|
||||
#else
|
||||
return pid == 0 || (kill(pid, 0) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
std::string shmName, path;
|
||||
double sr = 44100.0;
|
||||
uint32_t block = AUDIO_BLOCK_SIZE;
|
||||
uint32_t parentPid = 0;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (strcmp(argv[i], "--shm") == 0 && i + 1 < argc) shmName = argv[++i];
|
||||
else if (strcmp(argv[i], "--path") == 0 && i + 1 < argc) path = argv[++i];
|
||||
else if (strcmp(argv[i], "--sr") == 0 && i + 1 < argc) sr = atof(argv[++i]);
|
||||
else if (strcmp(argv[i], "--block") == 0 && i + 1 < argc) block = (uint32_t)atoi(argv[++i]);
|
||||
else if (strcmp(argv[i], "--parent") == 0 && i + 1 < argc) parentPid = (uint32_t)strtoul(argv[++i], nullptr, 0);
|
||||
}
|
||||
if (shmName.empty() || path.empty()) {
|
||||
printf("usage: plugin_host --shm <name> --path <vst3> [--sr rate] [--block n] [--parent pid]\n");
|
||||
return 2;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||
#endif
|
||||
printf("[plugin_host] pid=%lu shm=%s path=%s sr=%.0f block=%u\n",
|
||||
(unsigned long)GetCurrentProcessId(), shmName.c_str(), path.c_str(), sr, block);
|
||||
fflush(stdout);
|
||||
|
||||
// PoC: the child owns the mapping. Bridge adoption (G4.2) opens instead.
|
||||
ShmHandle* shm = shm_create(shmName.c_str());
|
||||
if (!shm) {
|
||||
printf("[plugin_host] FAILED shm_create %s\n", shmName.c_str());
|
||||
return 3;
|
||||
}
|
||||
SharedAudioBufferIPC* ipc = shm_ptr(shm);
|
||||
|
||||
Vst3Instrument inst;
|
||||
inst.init(sr, block); // sets sampleRate_/maxBlockSize_ used by setupProcessing
|
||||
if (!inst.loadPlugin(path, sr)) {
|
||||
printf("[plugin_host] loadPlugin failed\n");
|
||||
return 4;
|
||||
}
|
||||
|
||||
// GUI on the main thread (editorhost pattern: attach then pump below).
|
||||
HWND hwnd = nullptr;
|
||||
{
|
||||
WNDCLASSEX wc = {};
|
||||
wc.cbSize = sizeof(wc);
|
||||
wc.lpfnWndProc = DefWindowProc;
|
||||
wc.hInstance = GetModuleHandle(nullptr);
|
||||
wc.lpszClassName = "PluginHostClass";
|
||||
RegisterClassEx(&wc);
|
||||
hwnd = CreateWindowEx(0, "PluginHostClass", "PluginHost", WS_OVERLAPPEDWINDOW,
|
||||
0, 0, 800, 600, nullptr, nullptr, wc.hInstance, nullptr);
|
||||
}
|
||||
printf("[plugin_host] ownHwnd=%p openGUI...\n", (void*)hwnd);
|
||||
fflush(stdout);
|
||||
bool gui = inst.openGUI(hwnd);
|
||||
printf("[plugin_host] openGUI=%d\n", gui ? 1 : 0);
|
||||
fflush(stdout);
|
||||
|
||||
// Heartbeat ~10Hz, independent of the audio loop (G3.1 pattern).
|
||||
std::thread([ipc]() {
|
||||
for (;;) {
|
||||
Sleep(100);
|
||||
ipc->heartbeat++;
|
||||
}
|
||||
}).detach();
|
||||
|
||||
for (;;) {
|
||||
MSG msg;
|
||||
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessageW(&msg);
|
||||
}
|
||||
uint32_t n = ipc->midiQueueCount > 64 ? 64 : ipc->midiQueueCount;
|
||||
for (uint32_t i = 0; i < n; ++i) {
|
||||
const SharedAudioBufferIPC::MidiEventIPC& e = ipc->midiQueue[i];
|
||||
switch (e.command) {
|
||||
case 0x9:
|
||||
if (e.velocity > 0) inst.noteOn(e.channel, e.pitch, e.velocity / 127.0f, 0);
|
||||
else inst.noteOff(e.channel, e.pitch, 0);
|
||||
break;
|
||||
case 0x8: inst.noteOff(e.channel, e.pitch, 0); break;
|
||||
case 0xB: inst.controlChange(e.channel, e.pitch, e.data2); break;
|
||||
case 0xC: inst.programChange(e.channel, e.data2); break;
|
||||
case 0xE: inst.pitchBend(e.channel, e.data2 | (uint32_t(e.data3) << 7)); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
ipc->midiQueueCount = 0;
|
||||
inst.processAudioBlock(ipc->masterLeft, ipc->masterRight, block);
|
||||
ipc->bridgeWriteIndex++;
|
||||
if (parentPid && !parent_alive(parentPid)) {
|
||||
printf("[plugin_host] parent gone - exiting\n");
|
||||
break;
|
||||
}
|
||||
Sleep(1);
|
||||
}
|
||||
inst.closeGUI();
|
||||
#ifdef _WIN32
|
||||
CoUninitialize();
|
||||
#endif
|
||||
shm_close(shm);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user