@i-scope/iscope-bridge
v0.3.2
Published
Win32 npm distribution of iScopeBridge.exe (JSON-RPC stdio → Oscilloscope COM). Not a Node.js library: no main entry — use @i-scope/iscope-bridge-client from TypeScript.
Maintainers
Readme
packages/iscope-bridge/ — iScopeBridge.exe, JSON-RPC міст до Oscilloscope.exe COM
Part of the
@i-scope/debuggerSDK — один із семи leaf-пакетів, що агрегуються мета-umbrella@i-scope/debugger. Цей npm-пакет — це binary-tarball з готовимiScopeBridge.exe(Win32 native). Більшість користувачів НЕ ставлять його напряму — він приходить транзитивно через@i-scope/iscope-bridge-client(а той — через@i-scope/dap-adapter,@i-scope/mcp-server, або через meta@i-scope/debugger). Прямийnpm install @i-scope/iscope-bridgeмає сенс лише коли треба прицільно винести бінарник на диск (CI artefact pinning, redistributable bundle для не-Node хоста, тощо).
Standalone C++ ATL-додаток (~150-200 KB, нуль runtime-залежностей), який виступає мостом між IDE-клієнтами (@i-scope/iscope-bridge-client) і Oscilloscope.exe. Спілкування з клієнтами — через JSON-RPC 2.0 over Content-Length framed stdio.
Архітектура
IDE client iScopeBridge.exe Oscilloscope.exe
───────────────────── ───────────────────── ─────────────────────
@i-scope/iscope-bridge-client Main.cpp ScopeAppControl COM
IScopeBridge (EventEmitter) ──────► wmain → "serve" subcommand ScopeAppDbgCtrl COM
↓ ↑
child_process.spawn ◄─stdin/stdout─► JsonRpc (CL framing + reader thread) │
Content-Length frames ↓ │
Server.cpp (8 method handlers) │
↓ │
ComBridge.cpp ──────CoCreateInstance──┤
↓ IDispatch::Invoke │
DispatchSink ◄──IConnectionPoint──────┘
(events: trace/stop/break/error)Threading у helper.exe:
- Main thread = STA COM thread:
CoInitializeEx(COINIT_APARTMENTTHREADED), тримає всіIDispatchхендли, виконуєInvoke, обробляє sink callbacks через message pump (GetMessage/DispatchMessage). - Reader thread: блокується на
ReadFile(stdin), парсить framed JSON, посилаєWM_JSONRPC_REQUESTчерезPostThreadMessageу main thread. - Stdout writes серіалізуються через
CRITICAL_SECTION(sink-нотифікації + responses + diagnostic-помилки можуть конкурувати).
Verifying the bundled binary
The npm tarball ships both the pre-built iScopeBridge.exe
(bin/Win32/Release/) and its full C++ source (src/*.cpp,
src/*.h, iScopeBridge.vcxproj, iScopeBridge.sln,
scripts/run-msbuild.cjs). This lets any consumer audit the binary
end-to-end without leaving npm:
Inspect what is in the tarball. On npmjs.com click "Code" tab on the package page, or run locally:
npm pack @i-scope/iscope-bridge --dry-runCheck the binary's PE architecture & published hash. The bundled binary is 32-bit Windows x86 (PE machine
0x014C=IMAGE_FILE_MACHINE_I386). Architecture match with the 32-bitOscilloscope.exeis not strictly required — the bridge talks to Oscilloscope as aCLSCTX_LOCAL_SERVER(out-of-process) COM client, and Windows transparently cross-arch-marshalls IDispatch calls through the DCOM proxy/stub infrastructure (theOLEAUT32.DLLuniversalIDispatch_RemoteInvokemarshaller handles it for free). Existence proof: the upstreamOscilloscope.exedebugger plugin ships as a x64 Notepad++ plugin and drives the same ia32 COM surface without issue. We stay on ia32 for two pragmatic reasons:- Diagnostic simplicity — ProcMon / Spy++ / OleView see helper and Oscilloscope identically (same bitness in all tools), no "open the other OleView" dance.
- Smaller resident set — ia32 helper is ~50% lighter on pointer-heavy data structures than the x64 equivalent (saves ~5–10 MB RSS for a long-lived debug helper).
Verify locally:
$bin = "node_modules\@i-scope\iscope-bridge\bin\Win32\Release\iScopeBridge.exe" Get-FileHash $bin -Algorithm SHA256 # SHA256 of the published binary is recorded in CHANGELOG.md # under the matching version heading. $pe = [System.IO.File]::ReadAllBytes($bin) $off = [System.BitConverter]::ToInt32($pe, 0x3C) $mach = [System.BitConverter]::ToUInt16($pe, $off + 4) "MachineType: 0x{0:X4}" -f $mach # expect 0x014C (ia32)Rebuild from source and compare. With Visual Studio 2017+ Build Tools (v141 toolset + Win10 SDK 19041 + Static ATL), the in-tarball source can be rebuilt one-command and SHA-compared:
cd node_modules\@i-scope\iscope-bridge node scripts\run-msbuild.cjs # invokes MSBuild via vswhere Get-FileHash bin\Win32\Release\iScopeBridge.exe -Algorithm SHA256Note: MSBuild Release builds with
/GL(whole-program optimisation) and/LTCG:incrementalare not byte-stable across machines (PE timestamp, build-id, link-pass ordering all vary). Hash equality after a local rebuild is therefore not the assertion to make. The assertion is: "the published binary is a faithful build of the in-tarball source" — established by inspecting source + building it + running the resulting binary against the JSON-RPC methods listed below.Authenticode signature. The bundled binary is Authenticode-signed. Verify locally:
$bin = "node_modules\@i-scope\iscope-bridge\bin\Win32\Release\iScopeBridge.exe" Get-AuthenticodeSignature $bin # Expect: Status = Valid
Subcommands
| Команда | Призначення |
| --- | --- |
| iScopeBridge.exe | smoke test (default): GetStatus, OpenFile, Stop + registration probe |
| iScopeBridge.exe open <path.mwf> | завантажити .mwf один раз і вийти (для clean experiments) |
| iScopeBridge.exe run <path.mwf> <path.ajs> | повний цикл: open + advise sink + ExecuteFile + pump до STOP |
| iScopeBridge.exe serve | JSON-RPC server mode — основний режим для розширення |
JSON-RPC методи (server-handled)
Усі параметри — UTF-8 JSON. ID-цілочисельні. Wire-формат — Content-Length-framed.
| Метод | Params | Result | Помилки |
| --- | --- | --- | --- |
| initialize | {autoLaunch?: bool, oscPath?: string} | {server, version, connected: bool, weLaunched: bool, pid: int} | -32000 ComError |
| shutdown | {closePolicy?: 'never'\|'ifWeLaunched'} | {closed: bool} (потім helper закривається) | — |
| getStatus | {} | {status: int, name: "Idle"\|...} | -32001 NotConnected |
| openFile | {path: string} | {ok: bool} | -32002 FileNotFound, -32602 InvalidParams |
| executeFile | {file: string, breakpoints?: string} | {ok: bool} (потім дивись notif dbg/*) | -32002 FileNotFound, -32000 ComError |
| setBreakpoint | {line: int, set: bool} | null | -32602 InvalidParams |
| resumeExec | {action: int} (STEPACT_*) | null | -32602 InvalidParams |
| getStackContent | {req: int, list: string} | {content: string} (VLST raw BSTR) | -32000 ComError |
| ping | будь-що | echoes params back | — |
JSON-RPC notifications (server-pushed)
| Метод | Params | Тригер |
| --- | --- | --- |
| dbg/breakpointErrors | {positions: string} | DBGEID_BRKPNT_ERR_LST (1) |
| dbg/breakHit | {line: int, col: int, reason: int} | DBGEID_BRKEXECUTION (2) |
| dbg/error | {line: int, col: int, info: string} | DBGEID_ERROR (3) |
| dbg/stop | {} | DBGEID_STOP (4) |
| dbg/trace | {level: int, text: string} | DBGEID_TRACE (5) |
line/col уже 1-based (server віддає 0-based, helper нормалізує).
Приклади
Клієнтський API — @i-scope/iscope-bridge-client:
import { IScopeBridge, resolveHelperPath } from '@i-scope/iscope-bridge-client';
const br = new IScopeBridge({ helperPath: resolveHelperPath() });
await br.start();
await br.initialize();
br.on('trace', e => console.log(`[${e.level}] ${e.text}`));
br.on('stop', () => console.log('script done'));
br.on('error', e => console.log(`error at ${e.line}:${e.col}: ${e.info}`));
await br.openFile({ path: 'C:\\Oscillograms\\test.mwf' });
await br.executeFile({ file: 'C:\\scripts\\analyzer.ajs' });Edge-cases та фіксації
- Boolean-результат
OpenFile/ExecuteFileненадійний.falseповертається і на успіх теж. Перевіряти стан черезgetStatusабо побічні ефекти (отриманняdbg/trace). - Невалідний шлях файлу блокує COM-thread на модальному діалозі. Helper.exe валідує шлях через
GetFileAttributesWперед Invoke і повертаєJsonRpc::FileNotFound(-32002). ExecuteFile2-й BSTR-параметр —L"", неnullptr. NULL-BSTR через DEC marshaler дававRPC_E_SERVERFAULT.IDispatch::Invokeдля multi-arg методів —rgvarg[]у зворотному порядку оголошення параметрів. Загорнуто вComBridge::InvokeMethod(disp, dispid, BSTR a, BSTR b, ...).DBGEID_ERRORне спрацьовує для звичайних runtime-помилок таthrowу скрипті. ЛишеDBGEID_STOP. Для виявлення помилок робити pollinggetStatusпісляdbg/stop— якщоGSTID_Error(4), помилка була.- Sink тримається advised увесь час між
executeFileвикликами — для зменшення ризику втратити marshaling proxies. Розривається лише приshutdown. - Реєстрація COM-об'єктів —
REGCLS_MULTIPLEUSE. Реконнект післяStopпрацює без перезапускуOscilloscope.exe. - Початкові breakpoints задає
executeFile.breakpoints— comma-separated list of line numbers.setBreakpointпередexecuteFileне реєструє breakpoint.setBreakpointпрацює під час паузи (динамічне додавання і зняття).
Oscilloscope lifecycle
Helper також керує процесом Oscilloscope.exe за принципом ownership-based lifecycle: закриває лише те, що сам запустив. Зовнішньо запущений Oscilloscope (через Start menu, інший плагін, попередній сеанс helper-а) у безпеці.
Стейт у Server:
| Поле | Значення |
| --- | --- |
| oscProc_ | HANDLE запущеного процесу (nullptr якщо ми не запускали або вже закрили) |
| oscPid_ | PID Oscilloscope.exe (заповнюється завжди при connected=true, навіть для зовнішнього процесу) |
| weLaunched_ | true лише якщо саме цей сеанс helper-а запустив Oscilloscope (через autoLaunch=true коли FindRunningOscilloscope() повернув 0) |
Параметри initialize:
{ "autoLaunch": true, "oscPath": "C:\\Program Files (x86)\\USB Oscilloscope v5\\Oscilloscope.exe" }autoLaunch: false(default) — helper тільки приєднується до існуючого процесу. Якщо Oscilloscope не запущений —ComError.autoLaunch: true— helper викликаєFindRunningOscilloscope(). Якщо повернуло 0, запускає вказанийoscPathчерезCreateProcessі чекає до 15 с поки COM-class objects зареєструються (CoCreateInstanceполлінгом кожні 250 мс). При успіхуweLaunched_ = true. Якщо Oscilloscope вже існує —weLaunched_ = false.oscPath— опційно, default = стандартний v5-шлях.
Result envelope initialize:
{ "server": "iScopeBridge", "version": "0.1.0", "connected": true, "weLaunched": true, "pid": 12345 }Параметри shutdown:
{ "closePolicy": "ifWeLaunched" }| Політика | Поведінка |
| --- | --- |
| "never" | Не торкається Oscilloscope взагалі. Навіть наш власний процес залишиться живим — корисно для діагностичних сеансів, де користувач хоче подивитися на стан Oscilloscope після виходу з helper. |
| "ifWeLaunched" (default) | Якщо weLaunched_ === true — PostMessage(WM_CLOSE) всім top-level вікнам процесу + WaitForSingleObject(5s) + fallback TerminateProcess. Інакше нічого не робить. |
| "always" | Не підтримується. |
Result envelope shutdown:
{ "closed": true }closed: true означає, що Oscilloscope був реально закритий цим викликом. false — або ми не власник, або політика заборонила, або процес уже не існує.
Тестові сценарії:
- A: auto-launch + auto-close — pre: Oscilloscope killed; init({autoLaunch:true}) ⇒ weLaunched=true; shutdown({ifWeLaunched}) ⇒ closed=true; pid disappears within 8 s.
- B: attach + protect — pre: Oscilloscope started externally; init({autoLaunch:true}) ⇒ weLaunched=false; shutdown({ifWeLaunched}) ⇒ closed=false; pid still alive after 3 s.
Структура файлів
iScopeBridge.vcxproj Win32, v141, Win10 SDK 19041, /MT, static ATL, console subsystem
iScopeBridge.sln
src/
Constants.h CLSIDs, IIDs, DISPIDs, enums
ComBridge.{h,cpp} CoCreateInstance + InvokeMethod overloads (0/1/2 BSTR + raw N-arg)
DispatchSink.{h,cpp} IDispatch sink, manual switch DISPID 1..5, refcount, IID_IScopeAppDbgEvents
Json.{h,cpp} мінімальний JSON parse/build (ne vendor lib)
JsonRpc.{h,cpp} Content-Length framing + reader thread + serialized stdout
Server.{h,cpp} 8 method handlers + 5 sink-to-notification adapters + process lifecycle
ProcLifecycle.{h,cpp} FindRunningOscilloscope + LaunchAndAwaitCom (retry) + GracefulClose
Main.cpp wmain з 4 subcommands
bin/Win32/{Release,Debug}/iScopeBridge.exeRelated packages
This package is one of seven leaves of the
@i-scope/debugger SDK
meta-umbrella. The full family:
| Package | Role |
|---------|------|
| @i-scope/debugger | meta — one-install entry point for the whole SDK |
| @i-scope/mcp-server | MCP server for AI agents (22 tools) |
| @i-scope/dap-adapter | DAP server (AjsDebugSession) — embeddable + stdio |
| @i-scope/iscope-bridge-client | Node JSON-RPC client to the native helper |
| @i-scope/iscope-bridge (you are here) | Win32 native helper binary (iScopeBridge.exe) |
| @i-scope/source-map-bridge | TS ↔ AJS source-map manager |
| @i-scope/vlst-parser | Parser for VLST locals/Watch blobs |
| @i-scope/com-protocol-types | TS-mirror of COM DISPID / event constants |
License
MIT — see LICENSE.
