Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 187 additions & 5 deletions WIP.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/Reference/CEF/CefBrowser/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,7 @@ Private Sub CefBrowser1_SourceChanged(ByVal IsNewDocument As Boolean)
End Sub
```

### See Also
## See Also

- [CefEnvironmentOptions](EnvironmentOptions) -- pre-creation configuration carried by [**EnvironmentOptions**](#environmentoptions)
- [CefLogSeverity](../Enumerations/CefLogSeverity), [cefPrintOrientation](../Enumerations/cefPrintOrientation) -- the package's two user-facing enumerations
Expand Down
1 change: 1 addition & 0 deletions docs/Reference/Packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ These packages are built into twinBASIC and are always available, even offline.
- [CEF Package](CEF/) -- the **CefBrowser** control wrapping the Chromium Embedded Framework: cross-platform-ready browser embedding with a choice of three Chromium runtimes (v49 / v109 / v145); currently in BETA
- [WebView2 Package](WebView2/) -- the **WebView2** control wrapping the Microsoft Edge runtime, plus its surrounding wrapper objects (request / response / headers / environment options) and the `wv2…` enumerations
- [WinEventLogLib Package](WinEventLogLib/) -- writes Windows Event Log entries from twinBASIC; the generic **EventLog**(*Of EventIds, Categories*) class handles registration, registry setup, and the per-event `ReportEventW` call, with message-table resources for *EventIds* and *Categories* synthesised into the EXE at compile time
- [WinNamedPipesLib Package](WinNamedPipesLib/) -- Windows named pipes as twinBASIC objects with an asynchronous IOCP-driven I/O model; **NamedPipeServer** + **NamedPipeServerConnection** on the host side, **NamedPipeClientManager** + **NamedPipeClientConnection** on the client side, with message-boundary semantics and a cookie-based correlation pattern across `AsyncRead` / `AsyncWrite` and their matching events
147 changes: 147 additions & 0 deletions docs/Reference/WinNamedPipesLib/NamedPipeClientConnection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
---
title: NamedPipeClientConnection
parent: WinNamedPipesLib Package
permalink: /tB/Packages/WinNamedPipesLib/NamedPipeClientConnection
has_toc: false
---

# NamedPipeClientConnection class
{: .no_toc }

One client-side connection to a named pipe. Produced by [**NamedPipeClientManager.Connect**](NamedPipeClientManager#connect). Carries the connection-lifecycle events ([**Connected**](#connected), [**Disconnected**](#disconnected)) and the message events ([**MessageReceived**](#messagereceived), [**MessageSent**](#messagesent)), plus the [**AsyncRead**](#asyncread) / [**AsyncWrite**](#asyncwrite) / [**AsyncClose**](#asyncclose) methods that drive them.

The class is tagged `[COMCreatable(False)]` and its constructor takes a package-private interface — reach instances only through [**NamedPipeClientManager.Connect**](NamedPipeClientManager#connect).

> [!IMPORTANT]
> The package `_README.txt` states: *"you MUST call **AsyncClose** on the client side, otherwise the connection is left alive when the object goes out of scope"*. Either call [**AsyncClose**](#asyncclose) explicitly before dropping the last reference, **or** let the object terminate cleanly through its `Class_Terminate` (which calls [**AsyncClose**](#asyncclose) automatically). Holding the reference forever — in a module-level **Collection**, for example — without calling [**AsyncClose**](#asyncclose) keeps the pipe handle open and the IOCP thread alive.

```tb
Private manager As NamedPipeClientManager
Private WithEvents connection As NamedPipeClientConnection

Private Sub Form_Load()
Set manager = New NamedPipeClientManager
Set connection = manager.Connect("MyService")
End Sub

Private Sub connection_Connected()
connection.AsyncWrite StrConv("hello", vbFromUnicode)
End Sub

Private Sub connection_MessageReceived(ByRef Cookie As Variant, ByRef Data() As Byte)
Debug.Print "reply: " & StrConv(Data, vbUnicode)
End Sub

Private Sub Form_Unload(Cancel As Integer)
connection.AsyncClose
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.

* TOC
{:toc}

## Properties

### CustomData
{: .no_toc }

A per-connection opaque slot the consumer can attach state to — typically a session object or a pending-replies dictionary tied to this one connection. **Variant**, default **Empty**. The package never reads or writes this field.

### Handle
{: .no_toc }

The underlying Win32 file handle returned by `CreateFileW("\\.\pipe\<PipeName>")`. **LongPtr**. Exposed for low-level / debugging use — most consumers can ignore it. Do not call `CloseHandle` on this value yourself; use [**AsyncClose**](#asyncclose) so the IOCP loop and the parent manager's bookkeeping stay consistent.

### PipeName
{: .no_toc }

The leaf pipe name this connection was opened against — the same value that was passed to [**NamedPipeClientManager.Connect**](NamedPipeClientManager#connect). **String**. Read-only in practice; the package sets it from the constructor argument and never changes it.

## Events

### Connected
{: .no_toc }

Fires once the asynchronous `CreateFileW` started by [**NamedPipeClientManager.Connect**](NamedPipeClientManager#connect) has succeeded and the pipe is ready for message exchange.

Syntax: *connection*_**Connected**()

### Disconnected
{: .no_toc }

Fires once the pipe 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: *connection*_**Disconnected**()

### MessageReceived
{: .no_toc }

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

Syntax: *connection*_**MessageReceived**(**ByRef** *Cookie* **As Variant**, **ByRef** *Data*() **As Byte**)

*Cookie*
: The opaque correlation value originally passed to the [**AsyncRead**](#asyncread) that produced this read — or **Empty** if the read came from the auto-issued reads driven by [**NamedPipeClientManager.ContinuouslyReadFromPipe**](NamedPipeClientManager#continuouslyreadfrompipe).

*Data*
: The message payload. See [Working with `Data() As Byte` in events](.#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.

### MessageSent
{: .no_toc }

Fires when a previously-issued [**AsyncWrite**](#asyncwrite) has completed.

Syntax: *connection*_**MessageSent**(**ByRef** *Cookie* **As Variant**)

*Cookie*
: The opaque correlation value that was passed to the originating [**AsyncWrite**](#asyncwrite) call.

## Methods

### AsyncClose
{: .no_toc }

Cancels every outstanding I/O against this connection and closes the underlying pipe handle. Eventually triggers the [**Disconnected**](#disconnected) event once the cancellation completes. Automatically invoked from `Class_Terminate` when the last reference to the connection drops.

Syntax: *connection*.**AsyncClose**

> [!IMPORTANT]
> See the class intro: the README requires that either this method runs (explicitly, or through `Class_Terminate`) before the connection is considered finished.

### AsyncRead
{: .no_toc }

Manually issues an asynchronous read against this connection.

Syntax: *connection*.**AsyncRead** [ *Cookie* [, *OverlappedStruct* ] ]

*Cookie*
: *optional* A **Variant** correlation value, surfaced as the *Cookie* parameter of the matching [**MessageReceived**](#messagereceived) event. Default **Empty**.

*OverlappedStruct*
: *optional* A **LongPtr** to a pre-allocated `OVERLAPPED_CUSTOM` structure. **Internal use only** — the IOCP machinery passes this when re-issuing a read after `ERROR_MORE_DATA`. Consumer code should always omit this parameter.

Only needed when the parent manager's [**ContinuouslyReadFromPipe**](NamedPipeClientManager#continuouslyreadfrompipe) is **False**; otherwise the IOCP loop keeps a read pending automatically and explicit calls are redundant.

### AsyncWrite
{: .no_toc }

Sends a message to the server.

Syntax: *connection*.**AsyncWrite** *Data*() [, *Cookie* ]

*Data*
: *required* A **Byte()** array carrying the bytes to send. An uninitialised or zero-length array is a no-op.

*Cookie*
: *optional* A **Variant** correlation value, surfaced as the *Cookie* parameter of the matching [**MessageSent**](#messagesent) event. Default **Empty**.

Returns immediately; the actual transmission runs through the IOCP loop. The completion fires [**MessageSent**](#messagesent) on this connection.

## See Also

- [WinNamedPipesLib package](.) -- overview, IOCP / event-marshalling architecture, cookie pattern, `Data()` lifetime caveat, the **AsyncClose** rule
- [NamedPipeClientManager class](NamedPipeClientManager) -- the manager that produced this connection
- [NamedPipeServerConnection class](NamedPipeServerConnection) -- the server-side counterpart
120 changes: 120 additions & 0 deletions docs/Reference/WinNamedPipesLib/NamedPipeClientManager.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
---
title: NamedPipeClientManager
parent: WinNamedPipesLib Package
permalink: /tB/Packages/WinNamedPipesLib/NamedPipeClientManager
has_toc: false
---

# NamedPipeClientManager class
{: .no_toc }

The client-side coordinator. Owns a Windows I/O Completion Port and a pool of worker threads shared by every [**NamedPipeClientConnection**](NamedPipeClientConnection) it produces, and hands out connection objects through [**Connect**](#connect). One [**NamedPipeClientManager**](.) typically lives for the lifetime of the consuming process and brokers many connections — to one or several servers — through that shared IOCP infrastructure. Instantiate with **New**.

Configure the public fields (all four have sensible defaults), call [**Connect**](#connect) for each pipe the application wants to dial, and respond to the [**NamedPipeClientConnection**](NamedPipeClientConnection) events. The first [**Connect**](#connect) lazily creates the completion port and starts the worker threads; subsequent calls reuse them.

```tb
Private manager As NamedPipeClientManager
Private WithEvents connection As NamedPipeClientConnection

Private Sub Form_Load()
Set manager = New NamedPipeClientManager
Set connection = manager.Connect("MyService")
End Sub

Private Sub connection_Connected()
Dim payload() As Byte = StrConv("hello", vbFromUnicode)
connection.AsyncWrite payload
End Sub

Private Sub Form_Unload(Cancel As Integer)
connection.AsyncClose ' required — see README
manager.Stop ' or just let the manager go out of scope
End Sub
```

See the package [overview](.) for the IOCP / event-marshalling architecture, the cookie correlation pattern, the transient lifetime of `Data() As Byte` inside events, and the mandatory `AsyncClose` rule for client connections.

* TOC
{:toc}

## Properties

The four configuration fields are read once on the first [**Connect**](#connect) call and propagated to every [**NamedPipeClientConnection**](NamedPipeClientConnection) created through this manager. Subsequent changes affect connections opened thereafter but **not** connections that already exist — set the fields before the first [**Connect**](#connect).

### ContinuouslyReadFromPipe
{: .no_toc }

When **True** (the default), each [**NamedPipeClientConnection**](NamedPipeClientConnection) keeps a read pending against its pipe at all times — every [**MessageReceived**](NamedPipeClientConnection#messagereceived) is followed by an automatic `AsyncRead` issued from inside the IOCP thread. Set to **False** to handle reads one-at-a-time; each [**MessageReceived**](NamedPipeClientConnection#messagereceived) handler must then call [**NamedPipeClientConnection.AsyncRead**](NamedPipeClientConnection#asyncread) to receive the next message. **Boolean**, default **True**.

### FreeThreadingEvents
{: .no_toc }

Controls where the [**NamedPipeClientConnection**](NamedPipeClientConnection) events are raised. When **False** (the default), the IOCP worker threads marshal each event to the main UI thread through the manager's 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**.

### MessageBufferSize
{: .no_toc }

The size, in bytes, of the per-completion `ReadFile` buffer initially allocated for each client 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 throughput for sustained large-message traffic.

### NumThreadsIOCP
{: .no_toc }

The number of IOCP worker threads created when [**Connect**](#connect) is first called. **Long**, default **1**. One thread is enough for most scenarios; raise this to allow concurrent event handlers under [**FreeThreadingEvents**](#freethreadingevents) = **True**, or to keep up with heavy traffic on multi-core hardware.

## Methods

### Connect
{: .no_toc }

Opens an asynchronous connection to a named pipe on the local machine.

Syntax: *manager*.**Connect**( *PipeName* ) **As NamedPipeClientConnection**

*PipeName*
: *required* The leaf name of the pipe to dial — the package prepends `\\.\pipe\` itself. Raises run-time error 5 *"cannot start without specifying a pipe name"* if empty.

Lazy on first call: creates the completion port and starts [**NumThreadsIOCP**](#numthreadsiocp) worker threads. Returns immediately with a [**NamedPipeClientConnection**](NamedPipeClientConnection) in the not-yet-connected state. The actual `CreateFileW` runs asynchronously on an IOCP worker and fires [**Connected**](NamedPipeClientConnection#connected) on the returned object once the pipe is open.

Raises run-time error 5 *"unable to create an IOCP port"* if `CreateIoCompletionPort` fails on the first call.

### FindNamedPipes
{: .no_toc }

Enumerates the named pipes currently published on the local machine.

Syntax: *manager*.**FindNamedPipes** ( [ *Pattern* ] ) **As Collection**

*Pattern*
: *optional* A wildcard pattern matched against the leaf pipe name (no `\\.\pipe\` prefix; the package adds it). `*` matches any sequence, `?` matches any single character. Default `"*"` — return every pipe.

Returns a **Collection** of **String** values, each a leaf pipe name suitable to pass to [**Connect**](#connect). Useful as a discovery step when the consumer doesn't know the exact server name in advance:

```tb
Dim names As Collection = manager.FindNamedPipes("MyService_*")
Dim name As Variant
For Each name In names
Debug.Print "found: " & name
Next
```

### Stop
{: .no_toc }

Cancels every outstanding I/O on every connection produced by this manager, 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**](#stop) on a manager that has not connected anything — or has already been stopped — is a no-op. Automatically invoked from `Class_Terminate`, so a manager going out of scope cleans up implicitly.

Syntax: *manager*.**Stop**

[**NamedPipeClientConnection**](NamedPipeClientConnection) objects produced by this manager remain valid as references after [**Stop**](#stop), but their underlying pipe handles are closed and they cannot perform I/O.

### New
{: .no_toc }

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

Syntax: **New NamedPipeClientManager**

## See Also

- [WinNamedPipesLib package](.) -- overview, IOCP / event-marshalling architecture, cookie pattern, `Data()` lifetime caveat, **AsyncClose** rule
- [NamedPipeClientConnection class](NamedPipeClientConnection) -- the per-connection object returned by [**Connect**](#connect)
- [NamedPipeServer class](NamedPipeServer) -- the server-side counterpart
Loading
Loading