ZplVirtualPrinter
public sealed class ZplVirtualPrinter : IAsyncDisposableVirtual network label printer: a TCP listener (raw port 9100 protocol) that accepts ZPL or EPL jobs, renders them with ZplRenderer / EplRenderer (one per connection, i.e. its own printer memory), raises LabelReceived and optionally writes the images to ZplVirtualPrinter.OutputDirectory.
Chaos printer:
ZplVirtualPrinter.Faults simulates printer conditions (paper/ribbon out, head open, over/under temperature, pause, buffer full, out of memory, cutter fault, RFID void) and communication faults (disconnect after n bytes, communication timeout, latency, slow receive, partial writes, power off), set directly or scheduled deterministically (after n labels/bytes/cuts, on a command, after a time, seeded random). They show on the wire like on a Zebra printer: labels stay in the buffer while printing is stopped, ~HS, ~HQES and SGD device.host_status / head.latch report the flags, a full buffer stops reading the socket. Recovery per the ZPL manual: ~PS resumes from pause, ~JA cancels all buffered formats, ~JP cancels the next format and pauses, ~JR resets (buffer cleared, pause off; physical conditions remain until cleared), ~JK cuts in delayed cut mode. Behaviour and manual references: docs-internal/chaos-printer.md. Host queries ~HS, ~HI, ~HM and ~HQ (ES, SN, OD, PH) are answered from the simulated state;
ZplVirtualPrinter.HostQueryHandler overrides them. Connections are accepted concurrently but their data is processed one connection at a time, like on a Zebra printer with its single print engine ("first come and first served"; a connection that has sent half a format keeps the others waiting until the format is complete, the connection closes or
ZplVirtualPrinter.IdleTimeout ends it). ZplVirtualPrinter.ConcurrentConnections restores processing every connection in parallel (load tests).Constructors
| Signature | Description |
|---|---|
# ZplVirtualPrinter(int port = 9100, IPAddress? address = default) | port: TCP port (0 = any free port, see ZplVirtualPrinter.Port after ZplVirtualPrinter.Start). · address: Listen address (default: loopback; use IPAddress.Any for the network). |
Properties
| Property | Type | Description |
|---|---|---|
ConcurrentConnections { get; set; } | bool | Process the data of all connections in parallel (behaviour of earlier versions, for load tests). Default false: like a Zebra printer, connections are accepted at the same time but served one after the other, first come first served – a connection holds the printer from its first data until it has no incomplete format left (Zebra developer forum: "only one print engine … taken on a first come and first served basis"; PrintSecure guide P1099957: the raw ports allow multiple connections served first come first served, ip.port_single_conn refuses a second one). |
Faults { get; } | ZplPrinterFaults | Printer conditions and communication faults (chaos printer). |
Firmware { get; set; } | string | |
HonorPauseCount { get; set; } | bool | ^PQ p with o = N pauses the printer after every group of p labels, like a Zebra printer (resume with ~PS). Default false: such jobs print through (behaviour of earlier versions). |
HostQueryHandler { get; set; } | Func<string, string> | Custom host query answers: gets the command (e.g. "~HQES"), returns the response or null for the default. |
IdleTimeout { get; set; } | TimeSpan | Closes a connection without data for this long (default 60 s). |
IsRunning { get; } | bool | |
LabelPrintTime { get; set; } | TimeSpan | Time to print one label (default 0: instantly). With a print time the printer works through its buffer in the background, so ~JA/~JP can cancel labels that have not printed yet and ~HS reports the labels remaining. |
MaxJobBytes { get; set; } | int | Upper bound of buffered unprocessed data per connection (default 64 MB). |
MemoryBytes { get; set; } | long | Printer memory for downloads (~DG, ~DY, ~DU, ^DF) per connection in bytes; 0 = default of ZplResourceStore.MaxTotalBytes. Reported by ~HM. |
Model { get; set; } | string | Model reported by ~HI. |
OutputDirectory { get; set; } | string | When set, each rendered label is saved here ("label-yyyyMMdd-HHmmss-n.png"). |
Port { get; } | int | The bound port (valid after ZplVirtualPrinter.Start). |
Profile { get; } | PrinterProfile | The profile applied with ZplVirtualPrinter.ApplyProfile, null for the generic virtual printer. |
ReceiveBufferBytes { get; set; } | int | Receive buffer: while the formats waiting to print hold this many bytes, the buffer-full flag is set and the printer stops reading (0 = unlimited). |
RenderSettings { get; init; } | ZplRenderSettings | Render settings used for each new connection (dpi, label size, output format PNG/PDF/..., ZplRenderSettings.CutterInstalled). |
RfidSimulator { get; set; } | ZplRfidSimulator | RFID tag simulation (default null: off). When set, each label with RFID commands is encoded into a simulated Gen2 tag from ZplRfidSimulator.NextTag; a failed encoding or read-back voids the label like ZplFaultKind.RfidVoid (^RS tries and error handling). ^HV responses are sent to the host. ~RVE / ~RVD report "_+,n_" / "_-,n_" per RFID label with or without simulation. |
SerialNumber { get; set; } | string | |
SilentHostStatusOnError { get; set; } | bool | No ~HS / device.host_status answer while media out, ribbon out, head open or head over temperature (note in the 2026 ZPL manual, ~HS). Default false: answered with the flags set, as the older manual and the Link-OS SDK expect. |
TimeProvider { get; init; } | TimeProvider | Clock of the simulation (default TimeProvider.System): ZplVirtualPrinter.LabelPrintTime, fault timers (ZplFaultTrigger.After, ZplFaultRule.ClearAfter), latency, slow receive and partial writes wait on it. A manual clock (e.g. FakeTimeProvider) makes timed scenarios deterministic in tests. |
Methods
| Method Signature | Returns | Description |
|---|---|---|
# ApplyProfile(PrinterProfile profile) | ZplVirtualPrinter | Makes the virtual printer behave like profile (call before ZplVirtualPrinter.Start): resolution, label width = printhead width, cutter as installed (PrinterProfile.InstalledOptions; cutter mode on a printer without cutter raises a cutter fault), download memory = user RAM + user flash, and the identity answers: ~HI "model,firmware,dots/mm,RAM KB," and SGD getvar device.product_name, appl.name, head.resolution.in_dpi, ezpl.print_width, device.languages, device.unique_id, device.host_identification. An existing ZplVirtualPrinter.HostQueryHandler keeps priority. Values that the profile does not know keep the defaults. See docs-internal/printer-twin.md for which parts of these answers are documented and which are assumptions. |
# DisposeAsync() | ValueTask | |
# GetStatus() | ZplVirtualPrinterStatus | Snapshot of the simulated printer: conditions, buffer, counters and the current ~HS / ~HQES answers. |
# Start() | void | |
# StopAsync() | Task | |
# WaitForIdleAsync(TimeSpan timeout) | Task<bool> | Waits until the buffer is empty and no label is printing (or printing is stopped by a condition); false on timeout. |
Events
| Event | Handler Type | Description |
|---|---|---|
Error | EventHandler<Exception> | Raised when a connection fails (bad data, renderer errors, IO); the listener keeps running. |
JobReceived | EventHandler<VirtualJobEventArgs> | |
LabelCut | EventHandler<VirtualCutEventArgs> | The cutter cut after a label (cutter, delayed cut with ~JK, kiosk ^CN, RFID void with ^PQ e). |
LabelDropped | EventHandler<VirtualLabelEventArgs> | A label was lost (ZplFaultKind.DropLabels): rendered but not printed. |
LabelReceived | EventHandler<VirtualLabelEventArgs> |

