NamedPipeServer class

Hosts one named pipe and accepts an unbounded number of concurrent client connections, each represented by a NamedPipeServerConnection. The class owns a Windows I/O Completion Port and a configurable pool of worker threads that drive every connection’s reads, writes, and connect notifications. Instantiate with New.

Configure the public fields (PipeName is required, the others have sensible defaults), call Start, and respond to the lifecycle events as clients arrive and exchange messages. The package opens the underlying pipe as PIPE_TYPE_MESSAGE / PIPE_READMODE_MESSAGE — message boundaries on the wire match message boundaries seen by the consumer.

Private WithEvents server As NamedPipeServer

Private Sub Form_Load()
    Set server = New NamedPipeServer
    server.PipeName = "MyService"
    server.Start
End Sub

Private Sub server_ClientConnected(Connection As NamedPipeServerConnection)
    Debug.Print "client " & Connection.Handle & " arrived"
End Sub

Private Sub server_ClientMessageReceived( _
        Connection As NamedPipeServerConnection, _
        ByRef Cookie As Variant, _
        ByRef Data() As Byte)
    Connection.AsyncWrite Data        ' echo it back
End Sub

See the package overview for the IOCP / event-marshalling architecture, the cookie correlation pattern, and the transient lifetime of Data() As Byte inside events.

Properties

ContinuouslyReadFromPipe

When True (the default), the server keeps a read pending against every connected client at all times — every ClientMessageReceived is followed by an automatic AsyncRead issued from inside the IOCP thread. Set to False to handle reads one-at-a-time; each ClientMessageReceived handler must then call NamedPipeServerConnection.AsyncRead to receive the next message. Boolean, default True.

FreeThreadingEvents

Controls where the lifecycle and message events are raised. When False (the default), the IOCP worker threads marshal each event to the main UI thread through a hidden message-only window, and the consuming process must be pumping a Win32 message loop. When True, events fire directly on whichever IOCP worker thread received the completion — no message-loop dependency, but the consumer’s event handlers must be thread-safe. Boolean, default False.

Set this before calling Start; it is read once when the worker threads are created and propagated to every NamedPipeServerConnection.

MessageBufferSize

The size, in bytes, of the per-completion ReadFile buffer initially allocated for each connection. Long, default 131072 (128 KiB). Does not cap the maximum message size — on ERROR_MORE_DATA the IOCP loop allocates a larger overflow buffer and re-issues the read — but the initial size affects how often that overflow path runs, and so affects throughput for sustained large-message traffic.

NumThreadsIOCP

The number of IOCP worker threads created by Start. Long, default 1. One thread is enough for most scenarios because every blocking call inside the worker is an overlapped Win32 operation that releases the thread immediately. Raise this to allow multiple ClientMessageReceived handlers to run concurrently under FreeThreadingEvents = True, or to keep up with heavy traffic on multi-core hardware. Set this before calling Start.

PipeName

The name the pipe is published under. String, no default. The Win32 pipe namespace path is \\.\pipe\<PipeName> — the package prepends \\.\pipe\ itself; pass just the leaf name.

Important

PipeName must be set to a non-empty value before Start, or Start raises run-time error 5 (“cannot start without specifying a pipe name”).

Events

ClientConnected

Fires after a client’s ConnectNamedPipe has completed and the connection is ready for message exchange.

Syntax: server_ClientConnected(Connection As NamedPipeServerConnection)

Connection
The newly-connected client’s server-side connection object. Hold the reference if you need per-client state across messages — the same instance is passed to every event for this client. Note that Cookie / Tag-style storage is available through NamedPipeServerConnection.CustomData.

ClientDisconnected

Fires once the client has dropped and every outstanding asynchronous I/O against the connection has returned. The connection object is no longer usable for I/O after this event.

Syntax: server_ClientDisconnected(Connection As NamedPipeServerConnection)

Connection
The connection that has just shut down. Its IsConnected is False.

ClientMessageReceived

Fires when a complete message has been read from the pipe.

Syntax: server_ClientMessageReceived(Connection As NamedPipeServerConnection, ByRef Cookie As Variant, ByRef Data() As Byte)

Connection
The connection the message came from.
Cookie
The opaque correlation value originally passed to the NamedPipeServerConnection.AsyncRead that produced this read — or Empty if the read came from the auto-issued reads driven by ContinuouslyReadFromPipe.
Data
The message payload. See Working with Data() As Byte in events on the package overview for the transient-buffer lifetime caveat — copy the bytes out before the handler returns if you need them.

ClientMessageSent

Fires when a previously-issued NamedPipeServerConnection.AsyncWrite has completed (or when an AsyncBroadcast message reaches each individual client).

Syntax: server_ClientMessageSent(Connection As NamedPipeServerConnection, ByRef Cookie As Variant)

Connection
The connection the write went out on.
Cookie
The opaque correlation value that was passed to the originating AsyncWrite call.

ServerReady

Fires once, after Start, when every IOCP worker thread has joined the completion-port loop and the first connection listener is published. Use this as the “the server is now accepting connections” signal.

Syntax: server_ServerReady()

Methods

AsyncBroadcast

Issues an AsyncWrite against every currently-connected client.

Syntax: server.AsyncBroadcast Data() [, Cookie ]

Data
required The message bytes to send.
Cookie
optional A Variant correlation value, attached to each per-client ClientMessageSent event. Default Empty.

The set of recipients is snapshotted under a lock at the start of the call. Clients connecting after the snapshot do not receive this broadcast; clients disconnecting after the snapshot but before their per-client write completes simply fail that individual write silently.

ManualMessageLoopEnter

Runs a Win32 message loop on the calling thread until ManualMessageLoopLeave is called from another thread (or any handler raises a WM_USER_QUITTING posting).

Syntax: server.ManualMessageLoopEnter

Intended for console / service hosts that do not have a Forms-style message pump of their own but want the default (FreeThreadingEvents = False) marshalled-event semantics. UI hosts already pump messages naturally and do not need this method.

ManualMessageLoopLeave

Posts a WM_USER_QUITTING message to the hidden marshalling window, causing the ManualMessageLoopEnter loop on the other thread to break out cleanly. Safe to call from any thread.

Syntax: server.ManualMessageLoopLeave

Start

Creates the I/O Completion Port, spins up NumThreadsIOCP worker threads, and publishes the first connection listener under \\.\pipe\<PipeName>. Fires ServerReady when every worker has joined.

Syntax: server.Start

Raises run-time error 5 “cannot start without specifying a pipe name” if PipeName is empty, or “unable to create an IOCP port” if CreateIoCompletionPort fails.

Idempotent: calling Start while the server is already running is a no-op.

Stop

Cancels every outstanding I/O on every connection, posts the IOCP shutdown sentinel to each worker, waits for the threads to exit, closes every pipe handle, and frees the completion port. Idempotent: calling Stop on a server that has not been started — or has already been stopped — is a no-op. Automatically invoked from Class_Terminate, so a server going out of scope cleans up implicitly.

Syntax: server.Stop

New

Constructs a server in the not-yet-started state. Creates the hidden STATIC-class message window used to marshal IOCP-thread completions back to the UI thread.

Syntax: New NamedPipeServer

See Also