Production-grade infrastructure for Adobe ExtendScript.
|
ESON ESB64 ESARR ESSTR ESCHARS ESHTTP ESTIMER ESRAND ESUUID ESENV ESPATH ESFS ESHASH ESLOG |
ESPACK ESMIN ESABI VectorIPC ESTC ESDB COMTool ESsemble |
Also from the same team: ArcFit.dev, deterministic arc warp for Illustrator.
- Why VectorIPC?
- Features
- Which artifact should I use?
- Get the Release
- Installation
- Quick Start
- API
- Validation
- Performance
- Security Model
- Compatibility
- Engine quirks that shaped the design
- Development
- Repository layout
- Known limitations
- Credits
- License
Adobe scripting and native plug-ins frequently need work that should not live in the host process: persistence, networking, compression, expensive parsing, long-running services, or crash-isolated native code. Reimplementing that boundary per product means reimplementing framing, deadlines, cancellation, connection lifetime, security, and host-specific adapters every time.
VectorIPC separates those concerns:
flowchart LR
ES["ExtendScript / ExternalObject"] --> VIPC["VectorIPC"]
Native["Native plug-in / C / C++"] --> VIPC
VIPC --> Helper["Out-of-process helper"]
The transport owns local IPC mechanics. Applications own operation IDs, payload schemas, serializers, helper lifecycle, and business logic.
VectorIPC v0.1.3 is intentionally small: one fixed binary envelope, one public C ABI, persistent Windows named pipes, a header-only C++ ownership layer, and one ExtendScript ExternalObject adapter. The wire contains opaque bytes; JSON, ESON, CBOR, protobuf, flat structs, or no payload at all are application choices.
- Fixed 32-byte wire envelope: explicit little-endian encode/decode under
VIPC/1.0; the in-memory ABI descriptor is separate from wire layout. - Stable C ABI v1: fixed-width public scalar types with compile-time size/offset assertions for
vipc_errorandvipc_message. - C++17 ownership façade: move-only
vectoripc::Channel,Server, and non-throwingError; no second runtime or protocol. - Persistent full-duplex channels: one send and one reader-side operation may run concurrently; same-direction overlap returns
VIPC_ERR_BUSY. - Non-consuming readiness wait:
vipc_channel_wait_readable()lets a long-lived server poll idle channels without starting a framed read; readiness timeout is non-destructive and leaves the channel synchronized. A definitive peer-close/I-O failure closes the channel, sovipc_channel_is_open()stays honest. Readiness is poll-based and bounded by the 15.625 ms system timer tick, not by the nominal 2 ms poll constant — see docs/BENCHMARKS.md. - Bounded operations: one deadline covers the whole send/receive/accept operation; once a framed receive starts, ambiguous timeout/cancellation states still poison the channel instead of risking frame desynchronization.
- 16 MiB native wire limit: exact-boundary tests exercise payload sizes through
VIPC_MAX_PAYLOAD_BYTES. - Local Windows security boundary: current-user DACL,
PIPE_REJECT_REMOTE_CLIENTS, Windows-session-scoped endpoints, and peer PID/session metadata. - Recoverable listener state: transient clients that connect and disappear before accept cannot permanently strand the server listener.
- ExternalObject adapter v3: up to 16 generation-tagged logical sessions in one loaded DLL, independent channels/staging buffers, stale-handle rejection; its host ABI is supplied by ESABI v0.3.0.
- ExtendScript wrapper v5: dependency-free core wrapper with optional ESON, ESB64, and ESCHARS composition.
- Release reproducibility:
npm run release:manifestemits exact commit/tag metadata, per-file and aggregate SHA-256 digests, and a tagged source archive.
| Native C | Native C++ | ExtendScript | |
|---|---|---|---|
| Primary surface | <vectoripc/vipc.h> |
<vectoripc/vipc.hpp> |
VectorIPC.jsx |
| Binary | vectoripc.lib |
same vectoripc.lib |
VectorIPCExternalObject.dll |
| Runtime model | direct C ABI | header-only RAII over C ABI | synchronous ExternalObject calls |
| Serialization | application-owned | application-owned | application-owned; optional ESON helpers |
| Best for | helpers, plug-ins, native tools | C++ plug-ins/helpers | Illustrator ExtendScript |
Rule of thumb: share the service endpoint when components belong to the same application, but give each client its own channel.
VectorIPC v0.1.3 is the current public pre-release.
Release assets include:
vector-ipc-v0.1.3-windows-x64.zip: installed Windows x64 package: static library, C/C++ headers, CMake package files, ExternalObject DLL/wrapper, and MIT license;vector-ipc-v0.1.3-source.zip: deterministic tagged source archive;vector-ipc-v0.1.3.lock.json: exact commit, compatibility versions, and per-file/aggregate SHA-256 digests;SHA256SUMS.txt: checksums for the published lock, source archive, and Windows package.
For source consumers, pin v0.1.3 rather than tracking the mutable main branch.
include(FetchContent)
set(VIPC_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(VIPC_BUILD_BENCHMARKS OFF CACHE BOOL "" FORCE)
set(VIPC_BUILD_EXTERNALOBJECT OFF CACHE BOOL "" FORCE)
FetchContent_Declare(
vectoripc
GIT_REPOSITORY https://github.com/thelabcorner/vector-ipc.git
GIT_TAG v0.1.3
)
FetchContent_MakeAvailable(vectoripc)
target_link_libraries(my_target PRIVATE VectorIPC::vectoripc)For an installed package:
find_package(VectorIPC 0.1 CONFIG REQUIRED)
target_link_libraries(my_target PRIVATE VectorIPC::vectoripc)Build with VIPC_BUILD_EXTERNALOBJECT=ON (the default on Windows). The adapter accepts installed ESABI exactly at 0.3.0; otherwise it fetches the peeled v0.3.0 commit 3e99040c43cef573b477ad372b2a3a96c4d3a7d7 as a private build dependency. Then deploy:
build/Release/VectorIPCExternalObject.dll
src/adapters/externalobject/VectorIPC.jsx
The wrapper remains dependency-free. ESON, ESB64, and ESCHARS are optional codecs used only when explicitly supplied or already loaded.
#include <vectoripc/vipc.hpp>
#include <array>
#include <cstdint>
int main() {
vectoripc::Channel channel;
vectoripc::Error error;
if (vectoripc::Channel::connect(
"my-helper",
2000,
channel,
&error) != VIPC_OK) {
return 1;
}
const std::array<std::uint8_t, 4> request{{1, 2, 3, 4}};
vectoripc::Message message{};
message.kind = VIPC_KIND_REQUEST;
message.flags = VIPC_FLAG_NONE;
message.operation = VIPC_APP_OPERATION_MIN;
message.payload_size = static_cast<std::uint32_t>(request.size());
message.correlation_id = 1;
if (channel.send(message, request.data(), 2000, &error) != VIPC_OK) {
return 1;
}
return 0;
}$.evalFile(File("/absolute/path/to/VectorIPC.jsx"));
var ipc = VectorIPC.loadPath(
"C:/absolute/path/to/VectorIPCExternalObject.dll"
);
ipc.connect("my-helper", 2000);
var response = ipc.requestBinaryString(
0x100,
String.fromCharCode(0, 1, 2, 255),
2000
);
ipc.dispose();For structured messages, requestESON(..., ESON) composes with ESON without making ESON part of the transport.
| Surface | v0.1.3 |
|---|---|
| Product version | 0.1.3 |
| C ABI | 1 |
| Wire protocol | VIPC/1.0 |
| Wire header | 32 bytes |
| Native max payload | 16 MiB |
| ExternalObject adapter | 3 |
| ExtendScript wrapper | 5 |
| ExternalObject max payload | 256 KiB |
| ExternalObject logical sessions | 16 |
These domains are deliberately independent. A product-version bump does not imply an ABI or wire break.
Core lifecycle and transport functions:
vipc_server_create(...)
vipc_server_accept(...)
vipc_server_destroy(...)
vipc_client_connect(...)
vipc_channel_wait_readable(...)
vipc_channel_send(...)
vipc_channel_receive(...)
vipc_channel_destroy(...)Endpoint tokens are application identifiers, not OS paths. Valid tokens use [A-Za-z0-9._-]; Windows maps them to:
\\.\pipe\VectorIPC.<windows-session-id>.<endpoint>
Application operation IDs begin at VIPC_APP_OPERATION_MIN (0x00000100). VectorIPC reserves 0x00000000..0x000000FF for transport/runtime control.
See docs/PROTOCOL.md, docs/NATIVE.md, and docs/EXTERNALOBJECT.md.
The v0.1.3 native/package candidate was qualified on Windows x64 with MSVC 19.44.35228. The ExternalObject adapter/wrapper are byte-unchanged from v0.1.2, whose live Adobe Illustrator 30.6.0 build 109R qualification remains the current adapter evidence.
| Check | Command | Result |
|---|---|---|
| ES3 wrapper + native Release suite + installed package consumers | npm test |
pass; 12/12 CTest targets, C11 and C++17 installed consumers |
| Protocol fuzz | included in npm test |
250,000 valid round trips + 250,000 mutated 32-byte headers |
| Readiness primitive | included in npm test |
repeated idle timeouts, peer close, reader-slot VIPC_ERR_BUSY, long-idle duplex survival |
| MSVC static analysis | npm run test:analyze |
pass |
| AddressSanitizer | npm run test:asan |
pass; 12/12 targets |
| Exact payload boundary | npm run test:stress |
pass through 16 MiB |
| Timeout/cancellation resources | npm run test:stress |
2,000 timeout cycles, handles 69 → 69 |
| Accept/write cancellation | npm run test:stress |
2,000 accepts + 500 blocked writes, handles 69 → 69 |
| Readiness stress | npm run test:stress |
200 idle rounds, then 20 × 5,000 ms (100 s) long-idle window |
| Full duplex | npm run test:stress |
100,000 messages in each direction |
| Small-frame soak | npm run test:stress |
1,000,000 verified frames |
| Idle readiness cost | npm run bench:idle |
0.104 % of one core over 300 s; blocked control 0.000 ms; wake median 8.116 ms / p95 14.972 ms |
| Illustrator integration | v0.1.2 live evidence (adapter/wrapper unchanged in v0.1.3) | adapter v3 / wrapper v5 / raw bytes / sessions / ESON + ESB64 + ESCHARS pass |
The protocol decoder is transactional: failed header validation leaves the caller's output descriptor untouched. The mutation fuzzer checks that property across the invalid-header corpus.
Native measurements below are from the documented Windows test host: AMD Ryzen 9 5900X, Windows 11 Pro build 22631, Release x64, MSVC 19.44.35228, Windows SDK 10.0.26100.0. Each case uses a separate helper process and one persistent VectorIPC channel.
| Payload each way | Median RTT | p95 | Duplex user-payload throughput |
|---|---|---|---|
| 0 B | 10.2 µs | 12.7 µs | N/A |
| 1 KiB | 20.0 µs | 23.7 µs | 91.6 MiB/s |
| 16 KiB | 20.8 µs | 35.1 µs | 1.40 GiB/s |
| 64 KiB | 22.0 µs | 43.1 µs | 4.69 GiB/s |
| 256 KiB | 63.8 µs | 111.8 µs | 6.57 GiB/s |
| 1 MiB | 432.6 µs | 621.9 µs | 4.24 GiB/s |
| 4 MiB | 1.480 ms | 2.141 ms | 4.81 GiB/s |
Helper-process spawn → connected measured 27.575 ms median-of-three-run-medians. A separate repeated 1 KiB baseline measured 12.8 µs median RTT and 124.8 MiB/s median duplex payload, illustrating the scheduler sensitivity of microsecond-scale local IPC measurements.
Live Illustrator 30.6.0 measurements include the ExternalObject boundary:
| Path | Payload | Measured result |
|---|---|---|
| raw adapter v3 round trip | 7 B | 19 µs median-of-run-medians |
hardened UTF-8 requestText echo |
64 KiB | 1.15 ms median-of-run-medians |
hardened UTF-8 requestText echo |
256 KiB | 4.44 ms median-of-run-medians |
Representation dominates large ExtendScript payloads: the documented 64 KiB numeric-array pack measured about 4.0 s, versus about 0.11 s for the binary-string path. See docs/BENCHMARKS.md for methodology, repeated runs, and scripting-lane comparisons.
VectorIPC's v0.1.3 Windows trust boundary is local OS identity, not application authentication:
- named pipes use a DACL restricted to the current user;
PIPE_REJECT_REMOTE_CLIENTSrejects remote clients;- endpoint names are scoped by Windows session;
- clients request
SecurityIdentification, not impersonation; - peer PID and session metadata are exposed to the application;
- malformed frames, unknown flags, invalid versions, oversized payloads, and truncated streams fail closed;
- deadlines bound host-visible waits, and uncertain cancellation states poison the affected channel.
A different process running as the same user in the same session remains inside that OS trust boundary. Applications requiring stronger peer identity should add an application-level handshake, capability/build check, per-launch nonce, or equivalent policy above VectorIPC.
Payload bytes are untrusted application data. VectorIPC does not deserialize, evaluate, or execute them.
| Target | Status |
|---|---|
| Windows x64 native C11 | supported and release-tested |
| Header-only C++17 façade | supported and release-tested |
CMake install / find_package(VectorIPC 0.1) |
supported; C11 + C++17 consumers tested |
| Adobe Illustrator 30.6.0 ExternalObject | live-certified |
| ESON / ESB64 / ESCHARS composition | live-certified in Illustrator 30.6.0 |
Native Adobe .aip integration |
same C/C++ ABI intended for direct linking; not separately host-certified in v0.1.3 |
| macOS / POSIX | explicit unsupported stub in v0.1.3; Unix-domain transport planned |
| 32-bit Windows | not a v0.1.3 release target |
The generated CMake package uses SameMinorVersion: a 0.1.x package satisfies a 0.1 request, while the package smoke test proves that 0.1.3 does not satisfy a 0.2 request.
All host-specific observations below were reproduced in Adobe Illustrator 30.6.0.
ExternalObject DLLs can remain mapped after unload(). Live verification therefore never loads the canonical build output directly. Each probe stages a uniquely named content copy under the user's temporary vector-ipc-live directory, so Illustrator module caching cannot lock or pollute the repository/build tree.
Large ES3 numeric arrays are an expensive bulk representation. At 64 KiB, numeric-array packing measured about 4.0 s on the documented host; binary-string packing measured about 0.11 s. VectorIPC keeps arbitrary binary on the wire and exposes binary-string/text paths so callers are not forced through indexed numeric arrays.
Invalid UTF-16 can be silently normalized at the host boundary. A live probe showed Illustrator 30.6.0 normalize a lone surrogate into an empty ExternalObject string. The wrapper validates Unicode scalar structure before crossing that boundary.
ExternalObject method binding is a real compatibility surface. Repository probes observed selective/reproducible binding failures per DLL build. The adapter therefore exposes one numeric vipc(command, ...) method and multiplexes commands behind that single binding.
Requirements for the certified Windows path:
- CMake 3.20+
- Visual Studio 2022 Build Tools with the x64 C/C++ toolchain
- Node.js for repository tooling
- Python only for live Illustrator probe orchestration
npm test
npm run test:analyze
npm run test:asan
npm run test:stress
npm run verify
# Requires a running Illustrator instance:
npm run verify:live
# Measurements, not release thresholds:
npm run bench
npm run bench:matrix
npm run bench:live
npm run bench:live:matrix
npm run bench:live:wrapper
npm run bench:live:text -- --size 65536 --samples 5
npm run bench:live:decodeRelease locks are generated only from a clean, correctly tagged tree:
npm run release:manifestThe manifest records the exact commit/tag, ABI/protocol/adapter/wrapper versions, per-file and aggregate SHA-256 digests, and a tagged source archive.
flowchart TD
Repo["vector-ipc/"]
Repo --> Include["include/vectoripc/<br/>Public C/C++ headers"]
Repo --> Src["src/"]
Repo --> Tests["tests/<br/>Unit, fuzz, boundary, stress, package tests"]
Repo --> Bench["benchmarks/<br/>Native transport benchmarks"]
Repo --> Tools["tools/<br/>Build, analysis, live probes, release tooling"]
Repo --> Docs["docs/<br/>Protocol, architecture, adapter, benchmark docs"]
Src --> Core["core/<br/>Protocol/status core"]
Src --> Platform["platform/"]
Src --> Adapters["adapters/externalobject/<br/>Native adapter + ES3 wrapper"]
Platform --> Win["win/<br/>Windows named-pipe transport"]
Platform --> Posix["posix/<br/>Explicit v0.1 unsupported stub"]
Durable design decisions are recorded in docs/DECISIONS.md.
- v0.1.3 implements the transport only on Windows; the POSIX source is intentionally an unsupported stub.
vipc_channel_destroy()andvipc_server_destroy()are not cross-thread cancellation APIs. Use finite deadlines, let in-flight work return, join the owning worker, then destroy the object.vipc_channel_wait_readable()is aPeekNamedPipe()poll.Sleep()rounds up to the 15.625 ms system timer tick, so readiness latency is tick-bounded (measured 8.1 ms median / 15.0 ms p95) rather than the 2 ms the poll constant names. A product needing sub-tick readiness should use a different mechanism, not a tighter constant.- The ExternalObject adapter is synchronous at the Illustrator call boundary even though its logical sessions own independent native channels.
- The ExternalObject adapter caps payloads at 256 KiB even though the native wire cap is 16 MiB.
- Large legacy ExtendScript numeric arrays remain expensive; text/binary-string representations are the preferred bulk paths.
- Helper spawning, helper updates, authentication handshakes, application serializers, and product lifecycle policy are intentionally above the transport.
VectorIPC's Adobe-host integration builds on the public ExtendScript/ExternalObject ecosystem documented by docsforadobe and Adobe's scripting interfaces. The Windows transport is built on documented Win32 named-pipe, overlapped-I/O, ACL, and process/session APIs.
ArcFit.dev provided the public production precedent for isolating Illustrator work behind a bounded native/helper boundary; VectorIPC generalizes that pattern into a product-neutral transport rather than reusing product-specific protocol code.
MIT. See LICENSE.