Metadata-Version: 2.4
Name: dynwinrt
Version: 0.1.0rc23
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: Microsoft :: Windows
Summary: Python bindings for dynamic WinRT API invocation
Author: Microsoft
License: MIT
Requires-Python: >=3.11, <3.15
Description-Content-Type: text/markdown
Project-URL: Homepage, https://github.com/microsoft/dynwinrt
Project-URL: Issues, https://github.com/microsoft/dynwinrt/issues
Project-URL: Repository, https://github.com/microsoft/dynwinrt

# dynwinrt

`dynwinrt` is the native CPython runtime for Python projections generated by
[`dynwinrt-codegen`](https://pypi.org/project/dynwinrt-codegen/). It supports
CPython 3.11–3.14 on Windows x64 and ARM64.

## Install

```powershell
python -m pip install --pre dynwinrt dynwinrt-codegen
dynwinrt-codegen generate --namespace Windows.Foundation --class-name Uri `
  --lang py --output generated_uri
```

Generated package manifests pin `dynwinrt` to the exact version of
`dynwinrt-codegen` that produced them. The runtime wheel includes
`__init__.pyi` and `py.typed` for static type checking.
Do not mix generated bindings with an older runtime wheel. Upgrade to the
matching runtime version and regenerate all Python bindings together; generated
interface overloads fail explicitly when a required native guard is missing.
Generated Python packages record the full producer version separately.
Upgrades, downgrades, and legacy unstamped output require manually cleaning
the dedicated codegen output and regenerating the complete bindings selection;
incompatible generation fails without changing existing output.
See the [producer-version and path contracts](../../tools/dynwinrt-codegen/python/README.md#producer-version-and-regeneration).

Generated `IReference<T>` values are projected as `T | None`; native values,
`None`, and generated `IReference_*` wrappers are accepted as inputs.

### Nullability in type stubs

WinRT metadata does not record which values can be null, and most APIs raise
an exception instead of returning null. The generated `.pyi` stubs therefore
type the values you receive as non-null by default: method and property
results, async results and out values. For example,
`StorageFolder.create_file_async()` returns `WinRTCoroutine[StorageFile]`.

These values keep `| None`:

- `IReference<T>` values, projected as `T | None` everywhere;
- results of `Try*` members, such as `try_get_item_async()` or
  `JsonObject.try_parse()`, where null means "not found";
- results of Windows SDK members whose documentation says they can return
  null, such as `Accelerometer.get_default()`,
  `DispatcherQueue.get_for_current_thread()` or
  `StorageFolder.get_parent_async()`. The codegen embeds this list, derived
  from the Windows SDK API reference; it does not cover Windows App SDK
  (`Microsoft.*`) APIs;
- `Object`/`IInspectable` values (`DynWinRTValue | None`) and delegate-typed
  values, which are often null.

`Windows.Data.Xml.Dom` also keeps `| None` for an absent document root or DTD,
a node's owner document, parent and previous/next sibling, missing attribute
lookups (including namespace-aware lookups), and the previous attribute
returned by `set_attribute_node()`/`set_attribute_node_ns()`. Cache the result
and guard it before reading a member:

```python
root = document.document_element
if root is not None:
    print(root.tag_name)
```

These are exact reviewed null-result facts, not a blanket rule for WinRT
references. They tighten the static declarations; native/runtime null
conversion and the broader inline `.py`/`--no-pyi` annotations are unchanged.
Class documentation facts also have exact declaring-interface aliases for
standalone rendering; this does not enable exclusive interfaces such as
`IXmlDocument` or `IXmlElement` as public CLI roots.

Reference-type elements read from WinRT collection interfaces are always typed
`T | None`, including vectors, views, iterables, iterators, map keys and values,
and key-value-pair keys and values. Here reference means the projection's
supported COM-pointer shapes: `Object`, interfaces, runtime classes, delegates,
and parameterized interfaces. Async wrappers are not collection element
shapes. Reference-type elements of returned WinRT arrays are also `T | None`;
the array value itself remains non-null. String, GUID, scalar, enum, and struct
array elements and keys remain non-null. A view or iterator obtained from a
mutable collection can expose a null slot, and WinRT collection interfaces do
not retain enough provenance for the stubs to distinguish that case. For
example, a custom `IVector<IJsonValue>` can hold `IJsonValue | None`, and
`get_files_async()` returns
`WinRTCoroutine[Sequence[StorageFile | None]]`. Value-type elements remain
non-null.

Mutable collections accept `None` when their element, map-key, or map-value
type is a WinRT reference type and store a real null WinRT value. This includes
`append()`, `insert()`, index and slice assignment, `extend()`, `update()` and
`setdefault()`. String, GUID, scalar, enum, and struct keys and value-type
elements reject `None` with `TypeError`.

Stock `Windows.Data.Json.JsonArray` and `JsonObject` are exceptions: their
native APIs reject a null `IJsonValue` pointer. Generated wrappers check the
receiver's native class before mutation, including generic `IVector<IJsonValue>`
and `IMap<String, IJsonValue>` views, raw `DynWinRTArray` inputs, `replace_all`,
slice assignment, `extend` and `update`. A rejected null leaves the JSON
collection unchanged. Use `JsonValue.create_null_value()` to store JSON
**semantic** null; it is a non-null `IJsonValue` object. Their class stubs type
elements as non-null, and the generated `.py` input annotations agree even
with `--no-pyi`. Generic interface annotations retain `| None` because custom
implementations can store a native null and a view's origin is only known at
runtime.

Other arguments keep accepting `None` where they did before. The stubs are
optimistic, like the generated TypeScript declarations: the runtime still
returns `None` when a WinRT API returns null, so check the API documentation
when a result can legitimately be absent. The inline annotations of the
generated `.py` modules, which `typing.get_type_hints()` and `--no-pyi` output
expose, still mark every object result `| None`.

## Async WinRT operations

Generated async methods return typed, asyncio-compatible operation objects:

```python
operation = writer.store_async()
stored_bytes = await operation

task = asyncio.create_task(writer.store_async())
stored_bytes = await task

async with asyncio.TaskGroup() as group:
    task = group.create_task(writer.store_async())
```

Generated methods return `WinRTCoroutine[T]` and
`WinRTCoroutineWithProgress[T, P]`, which retain the structural
`WinRTAsync[T]` and `WinRTAsyncWithProgress[T, P]` contracts while also being
typed as coroutines for `asyncio.create_task()`. The concrete runtime wrappers
remain private.

Regenerated bindings no longer block inside async methods. Existing code that
expects an immediate result must use `await operation` or `operation.wait()`.

`asyncio` task cancellation calls `IAsyncInfo.Cancel()` on the underlying
WinRT operation. Operations with supported progress values also expose
`operation.progress(callback)`. Fast operations can finish before registration;
in that case no future progress exists and registration is a no-op.

An operation can be awaited directly more than once; every direct await observes
the same converted completion, failure, or cancellation. Its coroutine driver is
one-shot like a native Python coroutine, so pass a given operation to
`create_task()`, `TaskGroup.create_task()`, or `ensure_future()` only once.
Directly awaiting that operation after its task completes remains supported.
Calling `close()` cancels pending WinRT work and permanently closes the coroutine
driver.

For scripts without an event loop, `operation.wait()` remains available as an
explicit blocking API. It rejects started operations when called from a running
asyncio loop or an STA thread, where blocking could freeze or deadlock the
caller.

WinRT HRESULT failures raise `OSError` (or a standard `OSError` subclass) with
the signed HRESULT in `error.winerror`. The exception message preserves
restricted WinRT error information when Windows provides it.

## Python-native values

Generated collection projections implement the standard `collections.abc`
protocols: vectors behave as sequences, maps as mappings, and WinRT iterables
and iterators work with `iter()` and `next()`. Mutable vectors support indexing,
slicing, assignment, insertion, and deletion; mutable maps support standard
mapping assignment and deletion.

Bulk writes use the same checked inputs as item assignment: `update()` accepts
mappings or iterables of key/value pairs, plus keyword entries for string-keyed
maps; `extend()` and `+=` accept iterables of valid vector inputs. They do not
change the types returned by reads. `extend()` returns `None`, and `+=` returns
the same wrapper.

If metadata already exposes a native member with a helper's Python name, that
native method, property or compatibility alias keeps its existing meaning.
Use an explicit collection interface view for the standard collection helper
instead; generated helper refinements never replace a native entry point.
When a vector has a native `extend`, `+=` keeps its inherited dispatch to that
member rather than gaining a new bulk-write refinement.

`setdefault(key, default)` returns the value as `self[key]` reads it, including
after inserting a missing key. For an `Object`-valued map such as `PropertySet`,
a generated wrapper with a valid `_obj` can be a write input, but reads and
`setdefault()` still return `DynWinRTValue | None`, not that input wrapper or an
automatically unboxed Python value. The input remains usable. A present key
leaves its default unused, while a missing key requires a valid default
(`None` can be omitted only for nullable value types). Explicit fallback
defaults passed to `get()` or `pop()` are not stored and remain unchanged.
Native failures propagate; ordinary bulk writes can retain entries already
written before a later failure. The stock JSON null preflight described above
still rejects null inputs before mutation.

Sequence containment, `index()`, `count()`, and `remove()`, mapping equality,
and value/item-view membership compare WinRT references by canonical COM
identity, including different generated class/interface views and raw
`DynWinRTValue` carriers of the same object. This is reference identity, not a
runtime class's `equals()` or content comparison. Python scalars and generated
structs retain their normal equality, and released or wrong-thread native
access still raises. Wrapper/value `==` and hashing outside these collection
operations are unchanged.
Mapping equality also preserves entry counts: distinct Python keys are not
collapsed into one entry just because they alias the same native reference.

Raw `Object` collections do not implicitly box or unbox for comparison:
different boxes remain different references. An opt-in `object_value_view()`
compares its converted Python values normally and any remaining native
references by COM identity.

Method inputs accept normal Python sequences and mappings in place of compatible
WinRT collection interfaces, subject to the producer limits below.
Byte arrays accept `bytes` and `bytearray`; GUID,
`DateTime`, and `TimeSpan` values use `uuid.UUID`, `datetime.datetime`, and
`datetime.timedelta`.

Generated `Windows.Storage.Streams.Buffer` and `IBuffer` projections also
provide copied byte conversion:

```python
winrt_buffer = IBuffer.from_bytes(bytearray(b"\x00\x01\xff"))
data: bytes = winrt_buffer.to_bytes()
```

Both directions copy exactly `Length` bytes. Mutating the input or releasing
the WinRT object does not change the returned `bytes`, and no native buffer
pointer is exposed.

Exceptions raised by Python event or delegate callbacks are reported through
`sys.unraisablehook`. The originating WinRT invocation receives
`0xA0EE4005` (`PYWINRT_E_UNRAISABLE_PYTHON_EXCEPTION`) instead of unconditional
success. Generated delegate parameters accept normal Python callables. WinRT
chooses the callback thread, so callbacks must not assume they run on the
registration thread or an asyncio event-loop thread. Keep each token returned by
`on_*` and pass it to the matching `off_*` when the subscription is no longer
needed. For callback-style cleanup, `subscribe_*` returns an idempotent
unsubscribe function. `once_*` subscribes for at most one callback invocation.

A Python callable passed as a delegate (an event handler, a callback
parameter such as `ThreadPool.run_async(handler)`, or a delegate-typed
property) receives the delegate's arguments as projected Python values, typed
from the delegate's `Invoke` signature. WinRT `Object` arguments stay
`DynWinRTValue | None`, and `IReference<T>` arguments are native values or
`None`. Delegate-typed callback arguments are raw `DynWinRTValue | None`
because a null native delegate is passed to the callable as `None`.
Async-operation arguments stay raw `DynWinRTValue` objects so a
callback projection cannot take over or cancel the operation's completion.
For example, `map_changed` handlers of `PropertySet`, `StringMap`,
`ValueSet`, and other `IObservableMap<K, V>` implementations receive the
`IObservableMap<K, V>` projection, which is a mutable mapping, and an
`IMapChangedEventArgs<K>` with `collection_change` and `key`. An existing
native delegate, such as one built with `DynWinRtDelegate.create`, is passed
through unchanged, and its callback keeps receiving raw `DynWinRTValue`
arguments. `on_*` and `subscribe_*` accept those native delegates.
`once_*` requires a Python callable because it must wrap the callback to remove
the subscription after the first invocation.

Delegate-accepting constructors accept the `DynWinRtDelegate` object, but their
stubs intentionally do not advertise its raw `DynWinRTValue`. A single raw
value passed to a runtime class is reserved for wrapping an existing native
instance before constructor overload dispatch. Keep the delegate object for a
constructor, or pass the raw delegate to a named factory/method instead.

Callback parameter annotations are non-null by default except for `Object`,
`IReference<T>`, and delegate-typed arguments. This is an intentionally
optimistic typing policy, not a guarantee from the `Invoke` metadata: WinMD
carries no nullability information, and the runtime still passes `None` when
WinRT supplies a null reference.
Precise callback signatures live in the generated `.pyi` contract. Executable
`.py` methods use the cycle-safe runtime annotation
`Callable[..., object] | DynWinRTValue | DynWinRtDelegate`, so
`typing.get_type_hints()` resolves without eagerly importing the callback
argument graph.

Projected callback arguments are created outside the lifetime scope that was
active when the callback was subscribed. Short-lived arguments are therefore
released when their Python wrappers are dropped instead of accumulating in a
long-lived UI/application scope. If a callback retains an argument, its wrapper
owns the native reference and remains valid after the callback and after that
subscription-time scope closes; drop it normally or call `release_projected()`
when deterministic release is needed.

```python
def changed(sender: IObservableMap_String_Object, args: IMapChangedEventArgs_String) -> None:
    if args.collection_change == CollectionChange.ItemInserted:
        print(args.key, sender[args.key])

unsubscribe = properties.subscribe_map_changed(changed)
```

Async-operation arguments deliberately stay raw. A work item can inspect its
`IAsyncInfo` status without installing another completion handler:

```python
from dynwinrt import DynWinRTValue, release_projected
from generated.windows.foundation import AsyncStatus, IAsyncInfo
from generated.windows.system.threading import ThreadPool

def work(action: DynWinRTValue) -> None:
    info = IAsyncInfo.from_value(action)
    try:
        if info.status == AsyncStatus.Canceled:
            return
        # Do work, polling info.status when cooperative cancellation is needed.
    finally:
        release_projected(info)

operation = ThreadPool.run_async(work)
```

The low-level `DynWinRTValue.call_0()` and `call_1()` helpers require an
Object holding the intended interface, not a raw Async value. They reject an
Async receiver with `RuntimeError` before native dispatch: its `IAsyncInfo`
identity is not proof of the caller-supplied vtable slot and signature. Cast
to the specific interface IID first when making a metadata-checked low-level
call, or use the generated async wrapper and its `wait()`/await API.

`ThreadPool.run_async(handler)` intentionally retains its original single
argument and exact callback annotation, so mypy can infer the type of an
unannotated callback lambda. Its priority and options overloads remain
separate as `run_with_priority_async(handler, priority)` and
`run_with_priority_and_options_async(handler, priority, options)`. These are
distinct names in both generated Python and `.pyi`; passing priority or options
to `run_async` is not supported.

WinRT flags enums are projected as `enum.IntFlag`. Most overloaded methods share one
Python name with runtime type/arity dispatch and `typing.overload` declarations.
That name is the documented (CLR) method name, so `StorageFile.CopyAsync`
overloads are all `copy_async(...)`. The unique `[Overload]` ABI names emitted by
earlier releases, such as `copy_overload` or `launch_file_with_options_async`,
remain available as compatibility aliases that keep calling the same overload.
A method keeps its earlier name when the documented name would clash with
another member, such as a property or the generated `close()`. Interface
parameters of overloads accept any object that implements the interface, such
as a runtime class instance or a `DynWinRTValue`, including a raw async
operation that implements `IAsyncInfo`. Native QueryInterface failures other
than `E_NOINTERFACE` propagate instead of silently choosing another overload.
Activatable runtime classes use normal constructors, for example
`Uri("https://example.com")`. Constructor overloads come only from WinMD
`ActivatableAttribute` and public `ComposableAttribute` declarations. Classes
without that metadata, including system-returned classes and protected-only
composition, raise a class-named `TypeError` on normal construction and their
stubs expose no public constructor. Native return values still use the internal
`_from_native`/`DynWinRTValue` wrapping path.

### Creating WinRT collections

`DynWinRTValue.create_vector(items, element_type)` and
`DynWinRTValue.create_map(keys, values, key_type, value_type)` keep their public
signatures. They validate element identity, native layout, ownership, and the
complete set of closed collection IIDs before creating a native object.
These limits apply to **producing** collections, including conversion of Python
sequences/mappings, not to consuming collections returned by Windows.

| Element / key / value | x64 | ARM64 | i686 |
|---|---|---|---|
| Boolean, integers, Char16, enum, HRESULT | Supported | Supported | Up to 4 bytes; I64/U64 rejected |
| HSTRING; owned nullable interface/object references | Supported | Supported | Supported |
| POD structs in populated vectors or maps | Sizes 1, 2, 4, 8 | Up to 8 bytes, non-HFA only | Up to 4 bytes |
| Additional POD structs in **empty vectors only** | Larger than 8 bytes | Larger than 16 bytes, non-HFA only | None |
| Top-level F32/F64/GUID; structs recursively containing HSTRING or references | Rejected, even empty | Rejected, even empty | Rejected, even empty |

POD means a validated, reference-free native layout. HFA means an aggregate of
one to four same-width floating-point values, including nested aggregates.
Thus `Point`/`Size` work on x64 but are rejected on ARM64 even when empty;
`PointInt32` works on x64 and ARM64. ARM64 also rejects empty `RectInt32`,
`Rect`, and `BasicGeoposition` vectors. `ManipulationDelta` (20 bytes, five floats,
non-HFA) supports empty
vectors on x64 and ARM64. Large map keys/values are rejected even for empty maps.

Structs and typed enums require exact type identity, not a matching byte size
or shape. Scalars require the matching value variant, except U16 inputs for
Char16, I32 inputs for signed enum/HRESULT, U32 inputs for unsigned enum, and
range-checked I32 inputs for I8/U8/Char16. For example, `from_i32(255)` is
accepted for U8; `from_i32(257)` raises instead of wrapping. Reference inputs
are retained and queried for the declared IID; an incompatible interface raises, while
`DynWinRTValue.null_value()` remains a null reference.

WinRT enums preserve their metadata-declared `Int32` or `UInt32` backing type.
`DynWinRTType.enum_type(name, names, values)` remains signed by default. Pass
`DynWinRTType.u32_type()` as its fourth argument for a `UInt32` flags enum;
generated wrappers supply this automatically. Enum member lookup,
`enum_value()`, `get_enum_int()`, `to_number()`, and `to_int()` preserve values
through `0xffffffff`. Unsigned enum arrays and struct fields use the U32
accessors, and their generic interface IIDs include the `u4` signature.
Update the runtime and regenerate wrappers together.

For admitted POD structs, vector `IndexOf` and map key operations compare
metadata-declared fields by value, including nested fields, and ignore padding.
Floating fields use numerical equality: `+0` and `-0` match, while NaN does not
match even the same NaN bits. This also applies to collection views; a
NaN-containing struct map key is not found by lookup. Stored field bits are
not normalized.
Map construction uses the same comparison as `Insert`: the last value for an
equal key wins, retaining the first key's field bits and iteration position.

The Python helpers use validated typed core factories. Rust callers can use
the safe `create_vector_from_values` / `create_map_from_values` factories for
the same checks. The metadata-free Rust `create_value_vector`, `create_vector`,
and `create_map` constructors require `unsafe`: callers must prove the native
ABI, ownership, element types, and complete IID set themselves.
The metadata-free `create_value_vector` retains packed-byte equality, including
padding, rather than metadata-directed field equality.
Unsupported layouts raise
`RuntimeError`; HRESULT-backed type, range, or QueryInterface failures raise
`OSError`. Complete native struct collection support is tracked in
[microsoft/dynwinrt#161](https://github.com/microsoft/dynwinrt/issues/161).

## Raw object projection

Use `project_as(value, Type)` when metadata returns `Object`/`IInspectable` but
the application knows the concrete generated type. This is common with XAML
APIs such as `XamlReader.load()` and `FrameworkElement.find_name()`:

```python
from dynwinrt import project_as
from generated.microsoft.ui.xaml.controls import Button, StackPanel
from generated.microsoft.ui.xaml.markup import XamlReader

raw_panel = XamlReader.load(XAML)
if raw_panel is None:
    raise RuntimeError("XamlReader returned no value")
panel = project_as(raw_panel, StackPanel)

raw_button = panel.find_name("Submit")
if raw_button is None:
    raise RuntimeError("Submit was not found")
button = project_as(raw_button, Button)
```

`project_as()` accepts generated runtime classes only and borrows its input:
the raw value or source wrapper remains valid. The returned wrapper owns the
QueryInterface result, participates in the active
`projected_lifetime_scope()`, and preserves the projection identity cache.
Classes with a verifiable default-interface IID remain valid projection
targets even when metadata exposes them only through `Object`/`IInspectable`.
Projection always performs QueryInterface, so a static-only declaration cannot
produce a wrapper unless the input actually implements that default interface.
Incompatible types raise the ordinary WinRT `OSError`. Static-only metadata
classes with no instance surface are not projection targets.

### Explicit boxing and unboxing of `Object` values

Generic WinRT `Object`/`IInspectable` results and parameters remain raw
`DynWinRTValue` instances: an `Object` may be any runtime object, not only a
boxed value. Convert explicitly where the application expects a boxed
`Windows.Foundation.IPropertyValue`, such as the values of
`DeviceInformation.properties` or a `PropertySet`:

```python
from datetime import datetime, timezone

from dynwinrt import to_winrt_object, unbox_object
from dynwinrt.values import PropertyType, UInt32

raw = device_information.properties["System.Devices.DeviceInstanceId"]
instance_id = unbox_object(raw)

properties.insert("count", to_winrt_object(5))                   # Int32
properties.insert("port", to_winrt_object(UInt32(8080)))         # UInt32
properties.insert("size", to_winrt_object(2**40, PropertyType.UInt64))
properties.insert("when", to_winrt_object(datetime.now(timezone.utc)))
```

`unbox_object(value, *, preserve_type=False)` borrows its argument. `None` and
WinRT null return `None`. If the object does not implement `IPropertyValue`,
the exact same Python object is returned, so identity and later projection
remain intact. Boxed values map to Python values:

- numbers, Boolean, string and character values use `int`, `float`, `bool`
  and `str`; GUIDs use `uuid.UUID`;
- `DateTime` uses a timezone-aware UTC `datetime` and `TimeSpan` a
  `timedelta`, truncated to microseconds like other positions; a `DateTime`
  beyond `datetime.max` raises `OverflowError`;
- `Point`, `Size` and `Rect` use the immutable `dynwinrt.values` types;
- `UInt8Array` uses `bytes`, other arrays use lists, and `InspectableArray`
  elements are unboxed recursively by the same rules.

`Empty`, `Inspectable`, `OtherType` and `OtherTypeArray` boxes, including
nested ones, and native getter failures raise `OSError`.

With `preserve_type=True`, values whose plain Python form would box as a
different type come back as `dynwinrt.values` tags (`UInt8`, `Int16`,
`UInt16`, `UInt32`, `Int64`, `UInt64`, `Single`, `Char16`), and every array
except `bytes` comes back as a typed array such as `UInt32Array` or
`InspectableArray`. `to_winrt_object()` then restores the exact
`PropertyType` and value. It creates a new box, so the original box's COM
identity is not preserved.

`to_winrt_object(value, property_type=None)` returns a `DynWinRTValue` and
never guesses a WinRT type:

- `None` becomes WinRT null; a `DynWinRTValue` holding an object is returned
  unchanged, and a projected wrapper returns its native `DynWinRTValue`;
- `bool`, `float`, `str`, a timezone-aware `datetime`, `timedelta`,
  `uuid.UUID`/`WinGUID`, `bytes`/`bytearray`/`memoryview`, and
  `dynwinrt.values` tags, typed arrays and `Point`/`Size`/`Rect` box as their
  exact type; a naive `datetime` raises `ValueError`;
- a plain `int` boxes as `Int32` only; other values raise `OverflowError`;
- a list or tuple boxes only when all elements have the same unambiguous
  type, for example all `str` (`StringArray`) or all in-range plain `int`
  (`Int32Array`); empty and mixed lists raise `TypeError`;
- enum members raise `TypeError` unless an integer `property_type` is given.
  Boxing a WinRT enum as `IReference<T>` is not supported yet.

`property_type` accepts a `dynwinrt.values.PropertyType` member or its
integer value and converts the value to exactly that type, validating its
range. `InspectableArray` elements, including `None`, follow the rules above.

The `dynwinrt.values` types behave like the plain value: tags compare, hash,
format, pickle and serialize to JSON like `int`, `float` or `str`, and
arithmetic returns plain numbers. They live in `dynwinrt.values` rather than
`dynwinrt`, so they never shadow generated `Windows.Foundation` names such as
`Point` or `PropertyType`.

Use `wrapper.as_interface(InterfaceClass)` when converting an existing
wrapper to an interface view. Use `InterfaceClass.from_value(raw)` for a raw
`DynWinRTValue`. `as_interface()` accepts generated interface classes only;
passing a runtime class raises `TypeError` that points to `project_as()`. Do not
call the internal `_from_native()` method from application code.
Legacy direct construction with `InterfaceClass(raw)` also checks the
interface IID before retaining or caching the pointer. A non-implementing
object raises `E_NOINTERFACE` before any interface method can dispatch; a
successful view owns its own QueryInterface reference and does not consume the
raw source, even when the view is returned from the identity cache. Stubs
require a `DynWinRTValue` for explicit raw projection, but Python's type system
cannot prove its runtime IID.

### Views of `Object`-valued maps

`dynwinrt.values.object_value_view(mapping, *, preserve_type=False)` is an
opt-in view that applies these conversions to the values of a generated map
whose values are `Object`: `PropertySet`, `ValueSet`,
`DeviceInformation.properties`, the map returned by
`StorageItemContentProperties.retrieve_properties_async()`, and other
`IMap<K, Object>` or `IMapView<K, Object>` maps with `str` or `uuid.UUID`
keys:

```python
from dynwinrt.values import UInt32, object_value_view

view = object_value_view(properties)
view["count"] = 5                    # to_winrt_object(5): Int32
view["port"] = UInt32(8080)          # UInt32
count = view["count"]                # 5
box = view.raw["count"]              # the native DynWinRTValue box
```

`PropertySet` and `ValueSet` `map_changed` callbacks can also call
`object_value_view(sender)` on their projected `IObservableMap<String, Object>`
sender. The returned view keeps `.raw is sender`; null entries still read as
`None`.

- The view is live: every operation goes to the map, and the view holds no
  WinRT reference of its own. An `IMap` gets a `MutableObjectValueView`, a
  `collections.abc.MutableMapping`; an `IMapView` gets a read-only
  `ObjectValueView`, a `Mapping`. Releasing the underlying generated wrapper
  makes native reads and writes through the view raise the usual released-object
  error; a live WinRT null entry still reads as `None`.
- Reads return `None` for WinRT null, a runtime object that is not a box as
  its `DynWinRTValue`, and `unbox_object(value, preserve_type=preserve_type)`
  for a box. Where `unbox_object` raises for a box without a Python form, the
  view returns the box's `DynWinRTValue`, so `dict(view)` does not fail on one
  odd entry: an unsupported `PropertyType` such as `OtherType` anywhere inside
  the box, or a `DateTime` outside the range of `datetime`. Other errors
  propagate, including the same HRESULT from a failing getter for an otherwise
  supported `PropertyType`. Each read returns a new `DynWinRTValue` for a
  runtime object, so compare those with `identity_raw()`.
- Writes store `to_winrt_object(value)` and raise its errors unchanged: a
  plain `int` boxes as `Int32` only, and an empty or mixed list needs a typed
  array or an explicit `property_type`.
- `view.raw` is the generated map, which keeps returning native
  `DynWinRTValue` objects. Use it to keep a box's COM identity: with
  `preserve_type=True`, a value that is read and written back keeps its
  `PropertyType`, but it is a new box.
- The generated wrapper declares the exact `IMap`/`IMapView` interface used by
  its mapping methods, and `QueryInterface` confirms support for that IID. A
  wrapper for another map on the same COM identity is rejected rather than
  dispatching through the wrong vtable. Other maps, such as `StringMap` or
  `JsonObject`, raise `TypeError`; the type stubs reject them too. Runtime-class
  wrappers generated without this declaration fail closed; reproject them with
  `value.as_interface(IMap_String_Object)` (or the Guid/read-only equivalent).
- Generated `IPropertySet` wrappers, such as `ApplicationDataContainer.values`,
  are not Python mappings. Pass
  `container.values.as_interface(IMap_String_Object)` instead.

## COM apartments and cleanup

Use `RoApartment` to initialize COM for a thread and balance every successful
initialization:

```python
from dynwinrt import RO_INIT_SINGLETHREADED, RoApartment

with RoApartment(RO_INIT_SINGLETHREADED):
    use_winrt()
```

`RoApartment()` uses `RO_INIT_MULTITHREADED`, the same as
`RoApartment(RO_INIT_MULTITHREADED)`. Nested contexts using the same
model are supported. Requesting a conflicting model raises `OSError` with
`RPC_E_CHANGED_MODE`. The low-level `ro_initialize()` API remains available, but
each successful call, including `S_FALSE`, must be paired with one
`ro_uninitialize()` call on the same thread. `ro_uninitialize()` rejects calls
without a matching `dynwinrt.ro_initialize()` on that thread, including calls
intended to balance an initialization made by another library. Only this
library's successful initializations count toward its managed apartment depth.

Do not close the final managed apartment from a synchronous native-to-Python
callback (for example, a `PropertySet.map_changed` handler). `close()`,
`__exit__()`, and `ro_uninitialize()` raise `RuntimeError` **before**
`RoUninitialize` in that situation. A named `RoApartment` remains active; wait
for both the callback and its outer native call to return, release any retained
native callback values, then retry `apartment.close()` on the owner thread.
Non-final nested initializations may still be balanced inside the callback.
`ro_uninitialize()` cannot consume a `RoApartment` initialization.

If a final `RoApartment` is dropped during a callback (including an unnamed
`with RoApartment():` whose `__exit__` failed), its initialization stays in a
same-thread pending lease rather than being uninitialized inside the native
stack. After the native call returns, recover the lease explicitly:

```python
apartment = RoApartment.recover_pending()  # on the original OS thread
# Release any remaining native PropertySet / callback references first.
apartment.close()
```

`recover_pending()` raises if called inside a callback or if no lease is
pending on that thread; calling it from another thread cannot consume the
owner's lease. It does not close or release anything automatically. Retain a
named apartment when possible so `.close()` can simply be retried. A failed
owner cleanup is a different pending lease: use
`retry_pending_apartment_close()` (or recover and close the guard) after fixing
the release failure. Each pending lease may be claimed only once; the retry
helper cannot consume a callback-deferred lease. An implicit drop without an
earlier failed close emits a native stderr diagnostic.

An apartment is bound to the OS thread on which `RoApartment()` was created.
Calling `__enter__()`, `close()`, or `__exit__()` on another thread raises
`RuntimeError` without changing its state; retry on the creating thread.
If its last Python reference is instead dropped on a different thread, native
uninitialization is **not** attempted there: a native stderr diagnostic
identifies the pending lease, which the creating thread can explicitly retrieve
using `RoApartment.recover_pending()` and close after native references are
released.
Do not depend on Python garbage collection to close an apartment.

If releasing an apartment-owned native callback runs a captured object's
finalizer, a successful same-model `ro_initialize()` in that finalizer remains
active after the outer close and must still be paired with `ro_uninitialize()`.
The outer close consumes only its own initialization, not the newly acquired
one. Manual closes and pending-close retries reserve their in-progress lease
before owner cleanup, including cleanup-failure leases queued by a foreign-thread
guard drop. If cleanup fails, that exact lease remains retryable without
discarding any initialization or pending lease acquired during cleanup. Owners
already released by the drain stay released.

WinRT is never initialized implicitly. A call on a thread without an apartment
raises `OSError` with `CO_E_NOTINITIALIZED` in `error.winerror`; its message
explains how to open one.

Generated runtime classes that implement `IClosable` support `with` and an
idempotent `close()` method. Prefer deterministic cleanup instead of relying on
Python garbage collection.

## Standalone WinRT interface implementations

Generated, supported non-generic WinRT interfaces provide `.implementation()`
and `.implement()` factories. They create standalone, `IInspectable`-rooted
objects; they do not activate or register an OS class, compose a WinUI control,
or implement Classic COM interfaces. `.implement()` returns a generic management
handle with a stable typed `.value`, not a type cast or the raw handler:

```python
from generated.windows.foundation import IClosable, IStringable

class Label:
    def to_string(self) -> str:
        return "Python-backed WinRT object"

    def close(self) -> None:
        pass

handler = Label()
with IStringable.implement(
    handler, interfaces=[(IClosable, handler)]
) as impl:
    print(impl.value.to_string())
```

The generated handler protocols describe snake-case methods and their input
and output types. Additional interface descriptors provide separate native
views sharing the same object identity. Every required interface must be
included; an incomplete implementation is rejected before publication.
`release_projected(view)` releases a view independently without disposing or
releasing its controller. The advanced `from_implementation(impl)` path accepts
both new handles and low-level native owners; positional descriptors remain
supported. The primary `.value` needs no `release_projected` in the common
pattern. It is created lazily once, and `release()`/`dispose()` release that
view along with the handle's owner reference. `.value` raises after either
operation and never recreates a released view.
Heterogeneous convenience lists have per-pair static checking for interfaces
from the same generated package. For heterogeneous combinations across
generated packages, use positional typed `.implementation(...)` descriptors.
Runtime list acceptance and the generic homogeneous-list overload are unchanged.

The management implementation ships as `dynwinrt/_implementation.py` beside the
native extension. Tracebacks and coverage use this installed source file; no
build checkout is needed. The native module's public class identities and
exports are unchanged.

The low-level runtime surface is:

- `DynWinRTImplementationMethod(name, vtable_index, signature)`: an immutable
  method definition using the existing `DynWinRTMethodSig`.
- `DynWinRTInterfacePlan.create(name, interface_type, methods,
  required_iids=())`: an immutable, validated interface definition. Methods
  occupy every slot starting at slot 6; required-interface membership is
  checked when composing the complete implementation.
- `DynWinRTImplementationDescriptor(plan, dispatch)`: a generated interface
  plan and synchronous `dispatch(vtable_index, args)` callable.
- `DynWinRTImplementation.create(interfaces, callback,
  runtime_class_name=None)`: the native owner. Its callback receives
  `(interface_index, vtable_index, list[DynWinRTValue])` and must return a
  `list[DynWinRTValue]` in signature output order, with any logical return last.
  A void callback returns `[]`, not `None`.

Prefer generated plans rather than hand-authoring native signatures. The
runtime validates scalar, GUID, HSTRING, complete struct, managed-reference,
and pass/receive/fill-array contracts. For fill arrays the **callback** receives
a `UInt32` capacity and must return an array of exactly that length. Existing
outbound `DynWinRTMethodHandle` calls still take a preallocated array value for
a fill-array argument. `DynWinRTValue.from_hresult()` constructs an exact
signed HRESULT value, including for typed HRESULT arrays.

Callbacks are synchronous and run only on the creating native thread, with a
fresh copy of the `contextvars` context captured at creation. Reentrant calls
are supported. Async callables and coroutine/awaitable results are rejected;
no event loop is scheduled or blocked. Already-created native WinRT async
values can be passed through like other managed native values. Arbitrary
generic interface implementations, cross-thread dispatch, and Python
subinterpreters are not supported.

Callable wrappers for received native delegates can prepare a reusable
`DynWinRTDelegateMethod.create(iid, signature)` and call
`method.invoke(value, args)`. The convenience
`value.invoke_delegate(iid, signature, args)` remains available and uses the
same invocation path. The IID and signature must describe the same WinRT
delegate's `Invoke` method, including computed closed-generic IIDs. This calls
slot 3 on the delegate's `IUnknown` root and returns all outputs as a list
(`[]` for void). It borrows the Python value, owns a native pin through the
call, and propagates failed HRESULTs. Arguments use the existing outbound
`invoke_all()` contract, including preallocated fill-array values; these
helpers do not infer signatures or turn arbitrary objects into delegates.
Generated received-delegate callables use positional-only arguments in both
their Protocol declarations and runtime wrappers; ordinary projected methods
continue to accept their existing keyword arguments.

### Implementation lifetime and callback failures

`to_value()` returns an independently owned native reference. `release()`
drops only the owner's reference; callbacks remain alive while native
consumers retain other references, even after the Python owner wrapper is
gone. `disconnect()` disables all views and releases captured Python handlers
without dropping the owner's native reference. `dispose()` disconnects and
releases, and is repeat-safe. The owner context manager calls `dispose()`.
An in-flight callback can finish safely when it calls any of these methods;
subsequent calls after disconnection fail with `RO_E_CLOSED`.

GC tracing is deliberately conservative: captured Python references are
reported as owner edges only when that owner is the sole native reference.
This collects ordinary handler/self/owner cycles without treating native
consumers as collectable Python references. Cycles that retain a native alias,
or that have requested native weak references, may require **explicit
`dispose()`**, including a handler capturing a typed handle whose primary view
has already been created. Callback invocation and `to_value()` require the creating thread;
foreign-thread `to_value()` raises `OSError` with `RPC_E_WRONG_THREAD`.
Owner release, disposal, and garbage collection can run on another Python
thread without leaking the owner's native reference. This does not make
arbitrary wrapped Windows objects agile. Prefer deterministic cleanup;
unexpected GC cleanup failures are reported to `sys.unraisablehook`.
Interpreter shutdown separately closes the callback gate and drops
captured handlers; late native calls cannot enter a finalized interpreter.

Python callback exceptions and invalid Python output containers are reported
to `sys.unraisablehook` and fail the native call with
`PYWINRT_E_UNRAISABLE_PYTHON_EXCEPTION` (`0xA0EE4005`). Native output-contract
mismatches also fail rather than returning fabricated values. `take_error()`
returns and clears the latest native callback diagnostic; `is_closed` is a
read-only property.

## Experimental WinUI support

When the required WinUI metadata is generated, `Application.create()` installs
`XamlControlsResources` and configures unpackaged resource resolution.
`Application.create_with_metadata_provider(...)` is available when the
application supplies its own provider.

Python retains the validated WinUI implementation DLL after its first actual
WinUI activation until the process terminates. One named native owner keeps
one ordinary loader reference to `Microsoft.UI.Xaml.dll`; repeated activation
does not accumulate references. Retained native module handles determine
identity, so legal differences in path spelling or casing do not imply a
different runtime. If WinUI activation requires the DLL-probing
fallback, its successful loader reference is transferred to the same owner
(one reference per required implementation DLL), rather than leaked per call.
Imports, ordinary WinRT activation, and COM apartment initialization do not
load or retain WinUI or CoreMessaging.
When a WinUI metadata provider loads Controls before XAML, the runtime resolves
the actual `Application` activation factory to establish this lifetime before
returning the provider's factory; it does not create an Application or start
its message loop.

This is a process-lifetime module policy, not an object cache. Continue to
close windows, unsubscribe events, release projected/native objects, and
balance COM initialization on each thread as shown below. WinUI module-level
static state can remain after those application-owned resources are released.
The retained XAML mapping keeps its code valid when Controls static destruction
runs during COM cleanup, even when another MTA exits after the UI STA.

No new context manager or async executor is required. The Python runtime does
not release the published module reference from `RoApartment.close()`, Python
`atexit`, or wrapper finalizers: the operating system reclaims it at process
termination. Consequently, in-process unloading/hot replacement of that WinUI
implementation is not supported. This does not prohibit WinUI's documented
thread-level XAML/DispatcherQueue shutdown, guarantee repeated
`Application.start()` in one process, or imply that all SDK static resources
are gone when a window closes. The JavaScript and independent WinRT/COM
lifetime policies are unchanged.

Python subclasses of public composable controls preserve one COM identity for
inherited properties and methods. Metadata-supported
`measure_override`, `arrange_override`, and `on_apply_template` callbacks run
synchronously on the creating UI apartment with the `contextvars` context
captured during construction. Unsupported native override shapes fail during
construction instead of falling back to an unsafe ABI.

After creating the generated application, publicly composable controls can
register a Python subclass for activation by `XamlReader`:

```python
from generated.microsoft.ui.xaml.controls import StackPanel
from generated.microsoft.ui.xaml.markup import XamlReader

class PythonPanel(StackPanel):
    def measure_override(self, available_size):
        return available_size

registration = StackPanel.register_xaml_runtime_class(
    "MyApp.Controls.PythonPanel",
    PythonPanel,
)
raw_panel = XamlReader.load(
    '<local:PythonPanel '
    'xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" '
    'xmlns:local="using:MyApp.Controls" />'
)
if raw_panel is None:
    raise RuntimeError("XamlReader returned no value")
panel = StackPanel(raw_panel)

# First remove every instance from the XAML tree and release application owners.
panel = None
raw_panel = None
registration.unregister()
registration.release_instances()
```

Registrations are process-local, and duplicate names fail. Unregistering,
closing, or dropping the registration prevents new XAML metadata lookups.
XAML-created Python owners remain rooted until `release_instances()`; call it
only after every corresponding native control has left the XAML tree.
Registration does not make the class globally activatable through
`RoActivateInstance`.

Generated `Application.start()` and `DispatcherQueue.run_event_loop()` calls
stay on the caller's native thread but release the Python GIL while WinUI pumps
messages. WinRT callbacks reacquire the GIL, and worker threads can use
`DispatcherQueue.try_enqueue()` to return to the UI thread.

Use a projection lifetime scope for deterministic early cleanup inside the COM
apartment:

```python
from dynwinrt import RO_INIT_SINGLETHREADED, RoApartment, projected_lifetime_scope

with RoApartment(RO_INIT_SINGLETHREADED), projected_lifetime_scope():
    app = Application.create()
    # Create and use WinUI objects here.
```

Native factory and method outputs are tracked automatically, even when a
generated factory returns a bare `DynWinRTValue` (for example,
`PropertyValue.create_uint32(8080)`). Independently owned COM references made
inside `RoApartment` or after `ro_initialize()` are also observed weakly
without an explicit scope: any still-live owners release their own references
on the creating thread before the final *dynwinrt-managed* `RoUninitialize`.
The raw value or generated wrapper remains a Python object, but calls after
that boundary raise `RuntimeError` instead of releasing a native pointer in an
uninitialized apartment. Earlier `projected_lifetime_scope()` disposal remains
idempotent with apartment cleanup. It retains projected wrappers strongly,
but observes raw native outputs weakly, so ordinary temporary casts still drop
early. Pure scalar results do not own COM references and remain usable after
either boundary. An object created under an external COM initialization alone
is not automatically tied to a dynwinrt-managed apartment.

`DynWinRTArray` and `DynWinRTStruct` can independently own COM references,
including after `DynWinRTValue.as_array()` or `.as_struct()` clones an already
tracked value. COM-bearing containers created or extracted inside a managed
apartment are observed weakly and released before its final exit, even without
an explicit scope. Retained containers report `is_released()` and reject reads, writes and
`to_value()` with the released-object `RuntimeError`, rather than exposing
silently emptied storage. Scalar-only containers remain usable. Calling
`release()` early is repeat-safe and drops only the container's own references.
Cross-thread access and explicit release require the creating thread unless
every contained COM reference is agile. This also applies after mutating a
struct's object or nested-struct fields. An implicit foreign-thread Drop of a
non-agile container quarantines its own reference with a native diagnostic
rather than calling COM `Release` from the wrong apartment; agile containers
release normally on either thread.

`DynWinRTArray.from_values()` and `from_object_values()` validate every
element against its declared native type before retaining an independent
reference. Array, vector, map, delegate, and method inputs first validate
borrowed Python handles, then clone native COM references; a foreign-thread
non-agile input cannot cause `AddRef` before its thread error. Mismatched
scalar/object or struct identities and unsupported nested array elements
raise `OSError` instead of storing a value that cannot be marshaled safely;
nullable interface elements still accept native null.
For arrays produced by lower-level native paths, lifetime tracking also checks
the **actual owned elements**, not only the declared array element type.

Scopes nest in LIFO order. Wrappers and raw native results that survive a
closed scope or final managed apartment exit remain Python objects, but their
COM references are released. Using one afterwards as the object of a call, as
an argument, or inside a sequence, mapping, array, or struct input raises
`RuntimeError` explaining that it was released, as it does after
`release_projected(wrapper)` or `DynWinRTValue.release()`.
Returning one from an interface implementation handler fails the native call
like any other handler error. `DynWinRTValue.is_released()` tells a released
value apart from a WinRT null reference: both report `is_null()`, but only the
null can still be passed. Each scope is thread-affine: enter, use, and close it
inside that thread's `RoApartment`. Same-thread asyncio tasks inherit the
active scope; worker threads use their own `RoApartment` and optionally their
own earlier-cleanup scope. Native callbacks invoked on a foreign thread
preserve other captured context but do not inherit the subscriber's lifetime
scope. Independently cloned non-agile callback inputs created on a managed
thread stay usable after an explicit scope, but are released when that thread's
managed apartment finally closes. Agile callback inputs can be retained and
released normally; a non-agile progress input cannot cross callback threads
and reports an error instead.

An unfinished async future prevents the final apartment close without
silently cancelling work. Settle or explicitly cancel it, then call the
owner-thread guard's `close()` again. If an unnamed `RoApartment` context's
owner cleanup failed, use `retry_pending_apartment_close()` on the same thread;
if it was rejected during a native callback, use `RoApartment.recover_pending()`
after the outer native call returns. A
non-agile WinRT async operation awaited from asyncio checks completion on its
own apartment thread rather than passing its native reference to a worker.
Explicitly cancelling that asyncio task calls native `IAsyncInfo::Cancel` on
the owner thread; ordinary apartment cleanup never cancels an external task.
Calls to `RoApartment.close()` on another OS thread raise without changing the
apartment state. An implicit wrong-thread finalizer preserves its pending
native initialization for explicit recovery on the owner OS thread. If that
thread has exited, or interpreter shutdown makes the Python GIL unavailable,
native cleanup fails closed with a diagnostic instead of releasing COM after
teardown; normal close never treats a leak as success. A final close attempted
from a synchronous native callback is rejected before the owner drain and
`RoUninitialize`, then can be retried once the callback and native call return.

### Embedded host callback shutdown

If an embedded host retains native aliases to Python-backed delegates,
element factories, implementations, or progress handlers, it must close their
Python callback entry **while Python is still alive**, before `Py_FinalizeEx`:

1. Stop other native threads from invoking those aliases and settle callbacks
   already in flight (including queued progress delivery).
2. Call `dynwinrt.shutdown_python_callbacks()` on the live interpreter.
   This gate is idempotent; if a native callback is still active it raises
   `RuntimeError` without closing, so settle that callback and retry.
3. Finalize Python only after the gate succeeds. Release external COM aliases
   according to their own ownership contract; closing an apartment never
   disconnects them or cancels external work.

After the gate closes, late native delegate, element-factory, XAML, and
implementation entrypoints return `RO_E_CLOSED` **without attaching to
Python**. Python-backed callback creation also fails explicitly. A void
progress callback cannot return an HRESULT and instead issues a best-effort
native diagnostic. Hosts that skip the explicit gate have only PyO3's
best-effort shutdown detection: a foreign callback during the early,
unobservable part of `Py_FinalizeEx` cannot be guaranteed deadlock-free across
all supported Python versions. This external-callback limitation is separate
from the managed-apartment owner-release guarantee for #189.

Normal construction remains unavailable for protected-only composable classes
and system-returned classes without public activation metadata. Named Python
XAML registration does not support generic names, collection or dictionary
bases, markup-extension bases, or Python-defined XAML members.

## Develop

From `bindings\py`:

```powershell
python -m pip install "maturin>=1.11,<2" "pytest>=8.3.5" "mypy>=1.13,<2"
python -m maturin develop
python -m pytest
```

The focused WinUI lifetime regressions are in `tests\test_winui_lifetime.py`.
Without optional inputs they check ordinary WinRT imports and cleanup. Set
`DYNWINRT_WINUI_SAMPLE_ROOT` to a prepared Hello World directory (unchanged
`app.py`, `generated`, and `.runtime`) to exercise real STA, managed/external
MTA, exception, and default-async process teardown. Run each case in its own
process; process exit status is part of the assertion.

For native ownership inspection and the test-only shutdownable Tokio fixture,
build a separate test environment with `maturin develop --features test-hooks`.
This adds private test helpers only, not a production executor or public API.
`DYNWINRT_WINUI_REQUIRE_OWNER_HOOK=1` additionally checks the retained owner.
From a matching-architecture Visual Studio developer shell, build
`tests\e2e\fixtures\winui_activation.cpp` with
`cl /LD /EHsc /std:c++17 winui_activation.cpp /link runtimeobject.lib /OUT:Microsoft.UI.Xaml.dll`
in a private output directory and set `DYNWINRT_WINUI_FIXTURE_DIR` to it.
Copy that fixture DLL as `Microsoft.UI.Xaml.Controls.dll` in the same directory
for the dependency path-spelling regressions.
That SDK-typed factory tests actual loader-reference transfer, concurrent
fallback activation, failed/null factories, identity mismatch, and retry
without requiring WinUI installation. Do not use this fixture DLL in the real
sample directory.

## Release process

The release tag supplies one unified npm/Cargo version. For example,
`v0.1.0-preview.21` produces npm version `0.1.0-preview.21` and Python version
`0.1.0rc21` after PEP 440 normalization.

1. GitHub Actions builds and consumes eight CPython 3.11–3.14 runtime wheels
   and two standalone codegen wheels on Windows x64 and native ARM64.
2. The official 1ES ADO pipeline builds both npm packages and waits for the
   complete Python wheel matrix.
3. ADO creates one shared GitHub Release with the npm tarballs. GitHub Actions
   attaches the ten tested Python wheels, and ADO downloads and revalidates the
   complete set.
4. With `DoEsrp` enabled, ADO publishes both npm packages. `PublishPyPI`
   defaults to enabled and publishes the eight `dynwinrt` wheels before the two
   `dynwinrt-codegen` wheels. Disable it only for a non-PyPI rehearsal.

PyPI publication uses the Microsoft ESRP release identity and is not available
from GitHub Actions.
