Skip to content

The WinRT type system

PyWinRT is a "projection" that uses the Windows Runtime (WinRT) type system to automatically generate Python bindings for the Windows SDK. This page describes how the WinRT types look and feel in this Python projection.

Naming conventions

In the WinRT documentation, most names use PascalCasing in the style of .NET. In the Python projection, names are adapted to fit PEP8 naming conventions.

  • Type names remain as CapitalizedWords.
  • Constants, such as enum members are converted to UPPER_CASE_WITH_UNDERSCORES.
  • Namespaces are converted to lowercasewithoutunderscores.
  • All other identifiers are converted to lower_case_with_underscores (fields, properties, methods, events, etc.).
  • If an identifier is a Python keyword, an underscore is appended to the name.
  • A protected or overridable member, which WinRT means for a class derived from its class, starts with an underscore, as in _get_template_child() and _default_style_key.

Distribution packages

Each Windows SDK namespace is projected as a separate Python package. The packages are named like winrt-Windows.Foundation where winrt is the root namespace for a specific SDK or DLL and Windows.Foundation is a WinRT namespace within that SDK or DLL. Installing says how the other APIs are packaged.

There are also some PyWinRT-specific sub-packages distributed in the winrt-runtime package. As well as some extra interop packages that bridge between WinRT and Win32 types.

See also

API reference

Namespaces

Each Windows SDK namespace is projected as a Python package and follows the usual Python import conventions:

# import members of a namespace
from winrt.windows.foundation import Uri
# or import the namespace module itself
import winrt.windows.foundation as wf

As with the distribution package names, the WinRT namespace names have a root namespace that matches the part of the package name before the - followed by the namespace. The modules names are lowercasewithoutunderscores, so namespaces with multiple words in one segment, like Windows.AI.MachineLearning are projected as winrt.windows.ai.machinelearning.

Fundamental types

The WinRT type system defines the following fundamental types which are mapped to the corresponding Python builtin types.

WinRT type Python type Python format string Description
Boolean bool "?" an 8-bit Boolean value
Int8 int "b" an 8-bit signed integer
Int16 int "h" a 16-bit signed integer
Int32 int "i" a 32-bit signed integer
Int64 int "q" a 64-bit signed integer
UInt8 int "B" an 8-bit unsigned integer
UInt16 int "H" a 16-bit unsigned integer
UInt32 int "I" a 32-bit unsigned integer
UInt64 int "Q" a 64-bit unsigned integer
Single float "f" a 32-bit IEEE 754 floating point number
Double float "d" a 64-bit IEEE 754 floating point number
Char16 str 1 "u" 2 a 16-bit non-numeric value representing a UTF-16 code unit
String str "P" an immutable sequence of Char16 used to represent text
Guid uuid.UUID "T{I2H8B}" 3 a 128-bit standard globally unique identifier

The Python format strings use the syntax defined in the struct module and the PEP 3118 additions and are used as the format for the buffer protocol for arrays of these types. These format strings can be read at runtime by using memoryview.format.

An integer type takes an int or any object with an __index__() method, such as a NumPy integer, as Python's own functions do, and the type hints of a parameter say so with typing.SupportsIndex, as in winrt.system.UInt32 | typing.SupportsIndex. A number out of its range raises OverflowError, except that a negative number for an unsigned type raises ValueError.

Changed in version 4.0

A negative number for an unsigned type raises ValueError, as Python 3.14's PyLong_AsUInt32() and PyLong_AsUInt64() do. Previously it raised OverflowError.

A Char16 takes a str of one character. A character outside the Basic Multilingual Plane, such as an emoji, needs two UTF-16 code units and raises ValueError.

Changed in version 4.0

A character outside the Basic Multilingual Plane raises ValueError. Previously it raised TypeError, saying the string was of length 2.

The eleven types that have no Python type of their own are named in Python by the aliases in winrt.system, such as winrt.system.Int32. Each alias is annotated with what its type is called in each of the three languages involved - winrt.system.BufferFormat, winrt.system.StructFormat and winrt.system.WinrtSignature - so a program that needs one of them finds it with an isinstance() check over typing.get_args().

Enums

WinRT enums are projected using the Python standard library enum module. The WinRT Type system has a [Flags] attribute that indicates if an enum is treated as bit flags or not. Enum types without this attribute are projected as an enum.IntEnum type or if the [Flags] attribute is present, the type is projected as an enum.IntFlag type.

A newer version of Windows can return a value that the installed package was generated without. Such a value is still an instance of the enum: it equals its number and is named with it, as in "99", but it is not one of the enum's members, so it is not listed when the enum is iterated. Calling an enum with a number it has no member for gives the same kind of value rather than a ValueError. A flags enum keeps a bit it has no member for in the same way.

An array of enums is named with the enum type itself, as in Array(SomeEnum, 3). A buffer passed where such an array is expected uses the "i" format string for a regular enum and "I" for a flags enum.

Structs

Structs are simple data types that are passed by value in WinRT. In the Python projection, each struct is wrapped in a Python object that is similar to a typing.NamedTuple or frozen dataclasses. The names of fields are converted to use the lower_case_with_underscores naming convention.

Projected structs are @typing.final classes, so they cannot be subclassed. Projected structs are immutable. To modify a struct, use copy.replace() which creates a modified copy. Because a struct is immutable, copy.copy() and copy.deepcopy() return the struct itself, as they do for a tuple, so copying a list or a dataclass that holds structs works. A struct cannot be pickled.

As a convenience, plain tuple objects can be used in place of projected structs when calling methods that expect a projected struct or when setting properties that expect a projected struct.

Each projected struct also has an unpack() method that converts it to a tuple.

Example:

# window.position is a winrt.windows.graphics.PointInt32

# fails with AttributeError because Point is immutable
window.position.x = 200

# modifying
new = copy.replace(window.position, x=200)

# passing a tuple
window.move_and_resize((100, 100, 800, 600))

# unpacking
x, y = window.position.unpack()

Changed in version 2.2

Changed __repr__ implementation to give a representation that can be passed to eval().

Changed in version 3.0

  • Structs are now immutable. In previous versions, attributes could be set.
  • Added __replace__ method to allow for use with copy.replace().
  • Added unpack() method to convert to a tuple. Added support for using plain tuples in place of projected structs as method arguments.

Objects

A WinRT runtime class is projected as a Python class whose instances wrap WinRT objects. Every one derives from winrt.system.Object.

Calling the class constructs an object with the class's activation factory. A class with several constructors chooses one by the number of arguments, in the same way as methods, and the arguments are positional only. A class that has no constructor raises TypeError when it is called, and a type checker refuses the call. Its objects come from the methods and properties of other objects.

from winrt.windows.foundation import Uri

uri = Uri("https://example.com/")
page = Uri("https://example.com/", "docs/index.html")

Static members - methods, properties and events - are reached through the class rather than an instance.

A runtime class that WinRT declares composable can be subclassed in Python, and the subclass can override the class's overridable methods, which have a leading underscore like the other protected members. Any other class refuses to be a base class with a TypeError.

from winrt.windows.ui.xaml import Application


class App(Application):
    def _on_launched(self, args) -> None:
        ...

Besides the class it derives from, such a subclass can have only WinRT interfaces as bases, which it then implements, as an application that is a XAML metadata provider adds IXamlMetadataProvider. Any other base, such as a Python mixin or a second runtime class, raises TypeError.

Inheritance is linear, as in C#. A subclass can derive from another Python subclass of its class, but not from two of them, and not from a Python subclass of a class its own class derives from, such as one written for Control reused under ContentControl. Nor can it be generic, since a WinRT class has no type parameters, so typing.Generic is refused like any other Python base.

A class whose constructor is protected, such as Panel or Control, is created only through a class derived from it, as in C#, so calling the class itself raises TypeError. A type checker cannot tell the two calls apart and accepts both.

from typing_extensions import override

from winrt.windows.ui.xaml.controls import Panel


class StackingPanel(Panel):
    @override
    def _measure_override(self, available_size):
        ...


panel = StackingPanel()

Added in version 4.0

Deriving from a class whose constructor is protected.

A subclass with an __init__ of its own takes the arguments that __init__ declares, keywords included, as any Python class does. Its WinRT object is made when __init__ calls super().__init__(), whose arguments choose the constructor, and until then the object has no WinRT members to use: using one raises RuntimeError. A subclass without an __init__ passes the arguments of the call to the constructor, and so does one whose __init__ returns without calling super().__init__(), once it has returned: an __init__ that only keeps Python state of its own needs no call to the base class.

class TimelinePanel(Panel):
    def __init__(self, min_column_width=180, spacing=10):
        super().__init__()
        self.min_column_width = min_column_width
        self.spacing = spacing


panel = TimelinePanel(spacing=12)

Changed in version 4.0.2

A subclass's __init__ takes the arguments it declares, and its object has no WinRT members until it calls super().__init__(). Previously every argument of the call went to the constructor before __init__ ran.

WinRT asks such an object for its runtime class name, which by default is the name of the first overridable interface its class implements, or nothing when the class has none. A XAML metadata provider looks types up by that name, so a subclass can give its own with the runtime_class_name keyword, and a class derived from it inherits that name:

from winrt.windows.ui.xaml.controls import Page


class MainPage(Page, runtime_class_name="MyApp.MainPage"):
    ...

A wrapper is not the WinRT object itself, and reading the same object twice, for example from a property, gives two wrappers. They compare equal with == and have the same hash(), because those ask whether the two hold the same WinRT object, but is tells them apart. A mapping is the exception: it compares by content and has no hash, as a dict does. Two objects that only hold equal values, such as two Uri objects of the same address, are not equal. An object that implements IStringable converts to its string with str(), and repr() is Python's default.

A wrapper cannot be weakly referenced: the same WinRT object can come back as another wrapper, so a weak reference to one wrapper would die while the object lives on. A Python class derived from a projected class can be weakly referenced, and so can a Python implementation of an interface.

A null object comes back as None. The type hints annotate a method or property | None where WinRT's documentation says it can return null, but they do not yet cover every namespace, so a member without the annotation can still return None where WinRT returns null.

An object that has resources to release implements IClosable and is a context manager.

Query interface

A WinRT object implements several interfaces, and the type that a method or property hands it back as is only the one its signature declares. When the signature says Object, for example, the wrapper has none of the members of the object's class. as_() asks the object for another interface, which WinRT calls QueryInterface, and returns a wrapper of that type:

from winrt.windows.foundation import Uri
from winrt.windows.foundation.collections import PropertySet

properties = PropertySet()
properties.insert("home", Uri("https://example.com/"))

home = properties.lookup("home")  # a winrt.system.Object
print(home.as_(Uri).host)

as_() takes a runtime class or an interface, and raises OSError when the object does not implement it. Given anything else, a parameterized interface such as IVector[str] included, it raises TypeError. isinstance() asks the same question without raising; see Interfaces.

Changed in version 4.0

Type._from(obj), the spelling from before as_(), is deprecated and raises a DeprecationWarning. Write obj.as_(Type) instead.

See also

winrt.runtime.interop.as_interface() for an interface that the projection does not know.

Methods

Methods are named with the lower_case_with_underscores naming convention. Their arguments are positional only, and a keyword argument raises TypeError.

A method with several outputs returns them as a tuple: the return value first, then the output parameters in the order they are declared.

WinRT supports method overloading - several methods of one type with the same name. Overloads usually differ in the number of parameters, and a call chooses the overload that takes as many arguments as it was given. Too many or too few arguments raise TypeError, as do arguments of the wrong type.

folder.create_file_async("spam.txt")
folder.create_file_async("spam.txt", CreationCollisionOption.REPLACE_EXISTING)

Some types have several overloads that take the same number of parameters. Those cannot be told apart by the number of arguments, so each one is projected with the unique name it has in the WinRT metadata. One of them still keeps the shared name: an overload that has no unique name in the metadata, otherwise the one that the metadata marks as the default overload.

# FindAllAsync(DeviceClass), the default overload
DeviceInformation.find_all_async(DeviceClass.AUDIO_CAPTURE)
# FindAllAsync(String)
DeviceInformation.find_all_async_aqs_filter(aqs_filter)

Overridable methods - the methods that a Python subclass of a composable class implements - are the exception to the rule. Python methods cannot be overloaded, so each overload of an overridable method keeps its own unique name from the metadata.

Changed in version 4.0

PyWinRT 3.x used the unique name for every overload that had one, even when the overload could have been chosen by the number of arguments. For the methods that are not overridable, the Windows SDK packages keep those names as aliases that raise a DeprecationWarning, and they will be removed under the deprecation policy. The other packages do not have them. scripts/3to4/inspect_source.py in the repository finds them in your code.

# deprecated, use folder.create_file_async("spam.txt") instead
folder.create_file_async_overload_default_options("spam.txt")

Properties

Properties are projected as Python descriptor-like attributes. Properties with WinRT getter support getting and properties with a WinRT setter allow setting. Deleting properties is never allowed.

Names of properties are converted to use the lower_case_with_underscores naming convention.

Example:

from winrt.windows.foundation import Uri

uri = Uri("https://example.com")
print(uri.scheme_name)

Static properties are implemented as class attributes via a metaclass, so are accessed by using the type object rather than an instance object:

from winrt.windows.foundation import GuidHelper

empty_uuid = GuidHelper.empty

Changed in version v1.0.0b8

Previous beta releases implemented static properties as get_name() and put_name() static methods instead of class attributes.

Events

An event is projected as a pair of methods, add_<event>() and remove_<event>(), with the event name converted to the lower_case_with_underscores naming convention. add_<event>() takes a handler and returns an EventRegistrationToken; passing that token to remove_<event>() removes the handler again. An event can have any number of handlers, and each add_<event>() call registers one more. The token is a subclass of int, so it can be compared, hashed and used as a dictionary key.

The handler is a callable that takes two positional arguments, the object that raised the event and an event arguments object, and returns nothing. The types of both are in the stubs as the parameters of the event's delegate type.

from winrt.windows.devices.bluetooth.advertisement import (
    BluetoothLEAdvertisementReceivedEventArgs,
    BluetoothLEAdvertisementWatcher,
)


def on_received(
    sender: BluetoothLEAdvertisementWatcher,
    args: BluetoothLEAdvertisementReceivedEventArgs,
) -> None:
    print(args.bluetooth_address)


watcher = BluetoothLEAdvertisementWatcher()
token = watcher.add_received(on_received)
watcher.start()
...
watcher.stop()
watcher.remove_received(token)

Static events are added and removed on the type object rather than on an instance, in the same way as static properties:

from winrt.windows.applicationmodel.core import CoreApplication

token = CoreApplication.add_unhandled_error_detected(on_error)

Which thread calls the handler

WinRT calls the handler on whatever thread raises the event, and the handler runs as ordinary Python code on that thread. For most objects that is a WinRT thread pool thread, not one of the program's own. In a GUI app, an object that lives on the UI thread raises its events there, so a XAML event handler runs on the UI thread.

This means a handler must not touch anything that belongs to a particular thread. With asyncio, hand the work to the event loop with loop.call_soon_threadsafe() rather than doing it in the handler, and use an asyncio.Queue or an asyncio.Future to deliver the event arguments to a coroutine:

import asyncio

from winrt.windows.devices.bluetooth.advertisement import BluetoothLEAdvertisementWatcher


async def scan() -> None:
    loop = asyncio.get_running_loop()
    received = asyncio.Queue()

    watcher = BluetoothLEAdvertisementWatcher()
    token = watcher.add_received(
        lambda sender, args: loop.call_soon_threadsafe(received.put_nowait, args)
    )

    try:
        watcher.start()

        while True:
            args = await received.get()
            print(args.bluetooth_address)
    finally:
        watcher.stop()
        watcher.remove_received(token)

A handler is a plain function, not an async def one: WinRT does not await what a handler returns, so the body of an async def handler would never run, and passing one raises TypeError. None raises TypeError too, since it is no handler. A handler is taken off with remove_<event>().

The sender carries on after the handler returns, so a property of the sender read later from a task on the event loop describes the object as it is then, not as it was when the event was raised. Where that matters, read the property in the handler and hand the value to the event loop instead of the object.

Some events are designed for the handler to finish its work later: their event arguments have a get_deferral() method. The object that raised the event waits until the deferral is completed, so a handler can take the deferral, schedule a coroutine on the event loop, and have that coroutine call complete() on the deferral when it is done. Everything else the handler needs must still be read before the handler returns.

def on_suspending(sender, args):
    deferral = args.suspending_operation.get_deferral()

    async def save() -> None:
        try:
            await save_state()
        finally:
            deferral.complete()

    asyncio.run_coroutine_threadsafe(save(), loop)

Removing handlers

The WinRT object keeps a reference to the handler until it is removed. A handler that is a closure or a bound method keeps its own object alive too, so an object that subscribes to an event on a WinRT object it owns will not be garbage collected until the handler is removed, however unreachable the pair become. Keep the token and remove the handler when the object is done with the event. contextlib.ExitStack pairs an add_<event>() with its remove_<event>() at the point of subscribing:

import contextlib

with contextlib.ExitStack() as stack:
    token = frame_pool.add_frame_arrived(on_frame)
    stack.callback(frame_pool.remove_frame_arrived, token)
    ...

remove_<event>() stops the handler from being called again, but it does not wait for a call that is already in progress on another thread. A handler can still be running when remove_<event>() returns.

Removing a handler is not required before the interpreter exits. An event that is raised while Python is shutting down, or after it has shut down, is dropped rather than delivered.

Exceptions in handlers

An exception that escapes a handler goes to sys.unraisablehook(), as for any callback WinRT calls, so handle exceptions inside the handler. See Exceptions.

Interfaces

A WinRT interface is projected as an abstract Python class. It cannot be instantiated, since an interface is something an object implements and not an object of its own.

A projected class does not derive from the interfaces it implements in Python, but isinstance() and issubclass() answer for them anyway. isinstance(obj, IStringable) asks the object, in the same way as as_(), and issubclass(Uri, IStringable) is answered from what the projection says the class implements, including the interfaces it has through a base class and the interfaces those interfaces require.

from winrt.windows.foundation import IStringable, Uri

uri = Uri("https://example.com/")
assert isinstance(uri, IStringable)
assert issubclass(Uri, IStringable)

A parameterized interface, such as IVector[str], is for type hints. isinstance() and issubclass() raise TypeError for it, with or without type arguments, since without them there is no interface to ask for and with them Python refuses any parameterized generic type.

An object whose declared type is an interface is an instance of that interface. Its exact type, which type() and repr() show with a leading underscore, such as _IStringable, is an implementation detail that may change, so check for the interface with isinstance(obj, IStringable) rather than comparing types.

Implementing an interface

Passing a WinRT API an object of your own is rarely needed. Where a parameter is a collection interface, such as IIterable or IMap, pass a Python list, dict or other collection instead. For any other interface, use one of the runtime classes that implement it. A parameterized interface, such as IVector[str], cannot be implemented in Python at all: deriving a class from one raises TypeError.

Some APIs call back into an object that the program provides, as XAML does with a value converter. For those, a Python class that derives from an interface implements it, and its instances can be passed wherever WinRT wants that interface. The class defines the interface's methods and properties, and those of every interface the interface requires, and WinRT sees one object that answers to all of them. The type hints mark the members abstract, so a type checker reports one that is missing; at run time a missing member fails only when WinRT calls it.

The class cannot also derive from an abstract base class, such as one from collections.abc: Python reports a metaclass conflict, because the metaclass of a WinRT type is not ABCMeta, although the type hints declare it one so that a type checker enforces the abstract members.

The type hints of a member say what a caller may pass, which is more than the WinRT type: a tuple for a struct, a Python collection for a collection interface, any buffer for an IBuffer, any object with __index__() for an integer. A method of an implementation can be called from Python as well as by WinRT, and only a call from WinRT is converted, so it takes the same types, as target_type does below. operator.index() turns such an integer into an int, and a type checker takes the parameter as an int after count = operator.index(count).

from typing_extensions import override

from winrt.system import Object, box_string, unbox_string
from winrt.windows.ui.xaml.data import IValueConverter
from winrt.windows.ui.xaml.interop import TypeKind, TypeName


class UpperCase(IValueConverter):
    @override
    def convert(
        self,
        value: Object,
        target_type: TypeName | tuple[str, TypeKind],
        parameter: Object,
        language: str,
    ) -> Object:
        return box_string(unbox_string(value).upper())

    @override
    def convert_back(
        self,
        value: Object,
        target_type: TypeName | tuple[str, TypeKind],
        parameter: Object,
        language: str,
    ) -> Object:
        return value

Passed to WinRT and handed back again, for example by storing it in a collection and reading it back, the object comes back as the same Python object rather than as a wrapper. It has the members of Object as a wrapper does, so as_() of an interface its class implements is the object itself.

WinRT calls the methods on whatever thread it calls them from, as it does event handlers. An exception that escapes one goes to sys.unraisablehook(), as for any callback WinRT calls. See Exceptions.

Changed in version 4.0

An interface refuses to be instantiated, and issubclass() answers for the interfaces a projected class implements, where it used to answer False. isinstance() and issubclass() raise TypeError for a parameterized interface without type arguments.

Delegates

A WinRT delegate is a callback. Where a method, a property or an event wants one, pass any Python callable that takes the delegate's parameters positionally. The type hints name the delegate type as an alias of Callable that spells out those parameters and the result, so there is no class to construct: pass the function itself.

from winrt.windows.foundation import Deferral

deferral = Deferral(lambda: print("completed"))

WinRT calls the callable and does not await what it returns, so an async def function raises TypeError wherever a delegate is passed, since its body would never run. None passes no delegate, where WinRT accepts that, except as an event handler or as an async operation's completed or progress handler, which raise TypeError for it.

Changed in version 4.0

An async def function, and None where it is refused, raise TypeError. Previously the body of an async def function never ran, and None could crash the process.

A delegate that has outputs returns them in the same way as a method: its return value alone, or, with output parameters, a tuple of the return value followed by the output parameters in the order they are declared. A result of the wrong shape is an error, as an exception would be, and the caller gets the error code described below.

WinRT keeps the callable alive for as long as it holds the delegate. It calls the callable on whatever thread it calls the delegate from, which is often not one of the program's own threads; see Which thread calls the handler.

A delegate that WinRT hands to Python, such as the value of an async operation's completed property, is callable and calls the delegate. It is a wrapper, not the Python callable that may have been set there.

Arrays in delegates

A delegate's array parameters arrive as winrt.system.Array objects, which WinRT has three uses for:

  • an array the caller passes arrives as an Array of its own, which the callable may keep;
  • an array the caller lends to be filled arrives as an Array whose elements are the caller's. The callable assigns them by index, lent[i] = value, and cannot export the array as a buffer, so memoryview(lent), bytes(lent) and passing it on to another WinRT call raise BufferError. Once the call returns, the array is empty, since its elements went back to the caller;
  • an array the callable returns, as its result or as an output parameter, is an Array of the declared element type, or for a fundamental type or a struct of fundamental types, any buffer of the same layout.

Exceptions in delegates

An exception that escapes the callable goes to sys.unraisablehook(), as for any callback WinRT calls, and a delegate that returns a value then returns its caller only the error. Handle exceptions inside the callable. See Exceptions.

Arrays

WinRT arrays are projected as winrt.system.Array, a fixed-size Sequence whose items can be assigned. It is not a MutableSequence: its length never changes, so it has no append(), insert() or del.

from winrt.system import Array, Int32
from winrt.windows.foundation import Uri

numbers = Array(Int32, 3)            # three zeros
numbers = Array(Int32, [1, 2, 3])    # from an iterable
uris = Array(Uri, [Uri("https://example.com/")])

numbers[0] = 4
numbers[-1] = 6

The first argument is the element type: a projected type, the Python type that a WinRT type is projected as, such as str or bool, or for a fundamental type that has no Python type of its own, one of the winrt.system aliases such as Int32. The second is a length, an iterable of elements, or, for an element type that is a fundamental type or a struct of fundamental types, a buffer of the same layout to copy.

Indexing counts from the end for a negative index and raises IndexError out of range. A slice is a new Array with copies of the elements; a slice with a step is not supported and raises NotImplementedError. Two arrays are equal when their elements are, whatever their element types, and an Array equals only another Array, as a list equals only a list. An array is not hashable, and its repr() names the element type and the elements:

>>> Array(Int32, [1, 2, 3])
Array(Int32, [1, 2, 3])

Arrays as buffers

An Array supports the buffer protocol, in the format given for its element type in the table above, or for one of the numerics structs as a block of floats. An array of fundamental types or of structs of fundamental types exports a writable buffer, so memoryview, struct and similar can read and write its elements in place. An array whose elements hold references - strings, objects, or structs with a string in them - exports a read-only buffer of the references themselves, and while such a buffer is exported, assigning an element raises BufferError.

Arrays as parameters

A method's array parameters take three forms:

  • an array passed to WinRT to read takes an Array of the declared element type, or for a fundamental type or a struct of fundamental types, any buffer of the same layout, such as an array.array or a bytes object;
  • an array that WinRT fills takes an Array of the right length, or a writable buffer, and WinRT writes its elements;
  • an array that WinRT returns comes back as an Array.
from winrt.windows.security.cryptography import CryptographicBuffer

buffer = CryptographicBuffer.create_from_byte_array(b"\x01\x02\x03")

An array of references, such as strings, has no buffer that says what it points at, so only an Array is accepted for one.

Arrays that WinRT passes to a delegate are described under Arrays in delegates.

See also

winrt.system.Array for its signature and what changed in version 4.0.

Exceptions

A WinRT call that fails raises OSError with OSError.winerror set to the HRESULT error code it failed with.

Python picks the class from the HRESULT, as it does for a failed Win32 call. One that carries a Win32 error code, of the form 0x8007xxxx, may raise a subclass of OSError: a path that does not exist raises FileNotFoundError, and E_ACCESSDENIED raises PermissionError. Catching OSError catches them all, and winerror is the HRESULT either way.

See also

winrt.system.hresult for some common error codes.

An error code that WinRT hands back as a value rather than raising, such as an async operation's error_code, is an HResult.

On the other hand, Python exceptions cannot be propagated to C++ code. WinRT requires that errors are serializable, but Python exceptions are not. So if a Python callback from C++ code (i.e. an event handler or other delegate or a method of a Python subclass of a WinRT interface) raises an exception that isn't handled before the method returns, it will trigger the sys.unraisablehook() handler in Python and cause the C++ code to receive an HRESULT error code of PYWINRT_E_UNRAISABLE_PYTHON_EXCEPTION. This can cause undefined behavior in the C++ code in some cases, so it should be avoided.

A Python list, dict or other collection passed where WinRT takes a collection is read through such callbacks too, so an item that fails to convert, such as an int in a list passed for an IVector<String>, is reported to sys.unraisablehook() in the same way, and the call it was passed to fails with that error code. An index or key the collection does not have is the one failure WinRT has a name for, and gets E_BOUNDS instead.

Specialized types

Some types have special handling and don't strictly follow the patterns described above.

Awaitables

WinRT has many _async methods that perform background operations and return a type that derives from Windows.Foundation.IAsyncInfo to be able to wait for the result. There are four fundamental types that derive from this:

Synchronous usage

Without asyncio, a thread can block until an operation has finished with get() or wait().

get
IAsyncAction.get(self) -> None
IAsyncOperation.get(self) -> TResult
IAsyncActionWithProgress.get(self) -> None
IAsyncOperationWithProgress.get(self) -> TResult

These methods block until the operation is complete.

Returns: The result of the operation.

Raises:

  • OSError if the operation failed or was canceled.
  • RuntimeError if called from a single-threaded apartment (i.e. a GUI thread).

Warning

This method can't be interrupted by Ctrl+C which means that a KeyboardInterrupt will not be raised until the operation is complete.

Added in version 3.2

wait
IAsyncAction.wait(self, timeout: float, /) -> AsyncStatus
IAsyncOperation.wait(self, timeout: float, /) -> AsyncStatus
IAsyncActionWithProgress.wait(self, timeout: float, /) -> AsyncStatus
IAsyncOperationWithProgress.wait(self, timeout: float, /) -> AsyncStatus

These methods block until the operation is complete or the timeout is reached, whichever comes first.

Parameter Type Description
timeout float The timeout in seconds. A timeout that is not a positive number of seconds returns the status as it stands, without waiting. If the operation is still running when the call returns, whether the timeout ran out or was not positive, the wait keeps the one completed handler the operation allows, so awaiting it or calling get() or wait() again raises OSError until it has finished (see Asynchronous usage). To look without waiting, read the operation's status instead.

Returns: The status of the operation. In case of a timeout, the status will be AsyncStatus.STARTED.

Raises:

  • RuntimeError if called from a single-threaded apartment (i.e. a GUI thread).

Warning

This method can't be interrupted by Ctrl+C, which means a KeyboardInterrupt will not be raised until the operation is complete or the timeout is reached.

Added in version 3.2

Asynchronous usage

Note

WinRT async methods look like Python coroutines (methods defined with async def) but they are not. This means they do not return a Coroutine object and therefore cannot be passed to asyncio.create_task() or TaskGroup.create_task(). They are only Awaitable objects, so await one in a coroutine of your own and make the task from that.

If you are using asyncio, then you can use the await keyword to wait for the result of async WinRT methods:

thing = await winrt_obj.get_thing_async()

To put a time limit on operations, use asyncio.timeout(), and to run several at once, an asyncio.TaskGroup with a coroutine for each:

from winrt.windows.storage import FileIO

async def read(file) -> str:
    return await FileIO.read_text_async(file)

async with asyncio.timeout(10):
    async with asyncio.TaskGroup() as group:
        first = group.create_task(read(file1))
        second = group.create_task(read(file2))

print(first.result(), second.result())

Where asyncio needs a future, such as in asyncio.wait(), it makes a task around the operation. Make the tasks yourself with asyncio.ensure_future() to match what it hands back with the operations.

An operation that has finished can be awaited again, and gives the same result or fails in the same way each time. Awaiting, the blocking get() and wait(), and the completed property are three ways of being told that an operation has finished, and WinRT accepts only one completed handler per operation, so use one of them for each operation that is still running. To wait for one in several places, await a task made with ensure_future().

Cancellation

A task that is canceled while it awaits an operation, by asyncio.timeout() or by a TaskGroup whose other task failed, for example, asks WinRT to cancel the operation and stays suspended until the operation has finished, so no canceled operation is left running behind a task that has moved on. Then it raises asyncio.CancelledError, which asyncio.timeout() turns into TimeoutError, even if the operation was too far along to stop and finished anyway.

Canceling the task again while it waits for that gives up waiting: the task ends at once and the operation is left to finish by itself. That is the way out of an operation that never answers the request. If the operation fails after that, its error goes to the event loop's exception handler, as an error that nothing retrieved from a future does.

asyncio.run() cancels the tasks its coroutine leaves running and waits for them, so it does not return until every operation they await has finished. An operation that keeps running after the request holds it up for as long as it runs. Cancel such a task a second time before the coroutine returns to leave the operation behind.

A task that is canceled before it has started never awaits its operation, so the operation is not asked to stop.

The operation's own cancel() is IAsyncInfo.Cancel(), which asks WinRT to cancel it and returns nothing. An await of an operation canceled that way raises OSError with winerror set to WIN32_ERROR_CANCELLED, as it does for one that Windows canceled, because a device went away, for example.

Changed in version 4.0

An operation that has finished can be awaited again. Previously a second await failed. An error that an operation reports after the task awaiting it gave up waiting goes to the event loop's exception handler.

Changed in version 3.2

If the Awaitable that wraps the operation is canceled, it will now propagate the cancellation to the WinRT async action/operation. To restore the previous behavior, you can wrap the operation in asyncio.shield().

Other usage

Without asyncio, and on a thread that refuses get() and wait(), such as a GUI thread, set the operation's completed property to a delegate. WinRT calls it with the operation and its AsyncStatus once the operation has finished, and in it get_results() gives the result without blocking. It raises the error of an operation that failed, and OSError for one that was canceled:

from winrt.windows.foundation import AsyncStatus
from winrt.windows.storage import StorageFolder

def on_completed(operation, status: AsyncStatus) -> None:
    if status == AsyncStatus.CANCELED:
        return

    try:
        folder = operation.get_results()
    except OSError as e:
        print("failed:", e)
    else:
        print("opened", folder.name)

operation = StorageFolder.get_folder_from_path_async(r"C:\Windows")
operation.completed = on_completed

WinRT calls the handler on whichever thread finished the operation, even when a GUI thread set it, so hand the result to the thread that uses it as for an event handler. If the operation had already finished, the handler is called at once, on the thread that sets the property.

You must also be careful about not creating a reference cycle to the operation, otherwise it will cause a memory leak. This can happen if the callback is a closure and references an object that references the operation itself.

Progress

An operation that reports progress has a progress property. Setting it gives the operation its progress handler, a delegate that WinRT calls with the operation and the progress value each time there is progress to report. An operation has one progress handler, so setting the property again replaces it. Set the handler before awaiting the operation, so that no report is missed.

WinRT calls the handler on a thread of its own, as it does an event handler, so with asyncio hand the value to the event loop with loop.call_soon_threadsafe():

async def download(operation) -> None:
    loop = asyncio.get_running_loop()

    def on_progress(value) -> None:
        print("progress:", value)

    operation.progress = lambda op, value: loop.call_soon_threadsafe(
        on_progress, value
    )

    await operation

Buffers

A method parameter of type Windows.Storage.Streams.IBuffer takes any Python object that implements the buffer protocol, for example, a bytearray, bytes, or a memoryview, and WinRT reads or writes its memory directly, without a copy. The type hints name such a parameter winrt.system.ReadableBuffer, or winrt.system.WriteableBuffer where WinRT writes into it, both aliases for collections.abc.Buffer. An IBuffer that WinRT hands back is typed as IBuffer, which supports the buffer protocol and len() too and has length and capacity.

Where WinRT writes into the buffer, as read_async() of a stream does, the object has to be writable, so a bytearray rather than bytes, and a read-only one raises TypeError. Use the buffer the method hands back: its len() is how many bytes WinRT wrote, which can be fewer than the object holds.

data = bytearray(1024)
read = await stream.read_async(data, len(data), InputStreamOptions.NONE)
received = bytes(read)  # the len(read) bytes the read filled

For a small read, copying the bytes out with bytes() is quickest. For a large one, from tens of kilobytes, a memoryview of read is quicker: it reaches the same bytes without copying them, for as long as the view is open.

WinRT's metadata does not say which methods write into a buffer, so PyWinRT knows those of the Windows SDK by name and takes any other method to only read its buffer. If such a method sets the length of a read-only one after all, it fails with PermissionError rather than passing silently. For the pixels of a bitmap, SoftwareBitmap.lock_buffer(), below, reaches the bitmap's own memory, where copy_to_buffer() copies all of it.

Buffers received as a return value can likewise be used with anything that supports the buffer protocol. For example, memoryview can be used to access the buffer memory directly. Or, the struct module can be used to unpack formatted binary data.

Using native Python classes to access the memory is significantly more efficient than using the WinRT Windows.Storage.Streams.IDataReader or Windows.Storage.Streams.IDataWriter classes.

A Windows.Foundation.IMemoryBuffer - a MemoryBuffer, the BitmapBuffer of SoftwareBitmap.lock_buffer(), the AudioBuffer of AudioFrame.lock_buffer(), a tensor - supports the buffer protocol too, so memoryview or numpy.frombuffer() reads and writes its memory directly. A view keeps the memory for as long as it exists, whatever is done with the buffer meanwhile, so it may outlive the with block that closes the buffer. A SoftwareBitmap stays locked while a view of its BitmapBuffer exists. A view needs no reference, so the type hints mark create_reference() deprecated and point at the buffer protocol instead. A reference from it holds the buffer until it is closed, and closing it while a view of it exists raises BufferError.

Changed in version 4.0

An IMemoryBuffer supports the buffer protocol itself, and the type hints mark create_reference() deprecated in its favor.

A Windows.Foundation.IMemoryBufferReference supports the buffer protocol as well, and is what WebView2's CoreWebView2SharedBuffer.buffer returns. Closing the reference frees the memory once the memory buffer is closed or released too, so while a memoryview or a NumPy array of the reference exists, closing it raises BufferError, as closing an mmap does, and the reference stays open. Release the view, or copy what it holds, before the end of a with block that closes the reference. A close made by WinRT itself cannot be refused.

len() of a buffer is its length in bytes, and of a memory buffer reference its capacity, which is the length of a memoryview of either. So an empty one is false, as an empty bytes is: a new Buffer(16) is false until its length is set, and so is a closed memory buffer reference. Compare with None to ask whether there is a buffer at all.

Changed in version 4.0

A buffer and a memory buffer reference have a len(), and are false when they are empty. They had no len() and were always true.

Collections

Most of the generic types in Windows.Foundation.Collections are projected as collections.abc types. The WinRT runtime types are still present behind the scenes, but the type hints use only the Python standard library types to encourage users to use the Pythonic APIs. Furthermore, types that inherit from these interfaces are also extended to support the Pythonic APIs.

Where WinRT takes a collection of strings, such as an IIterable<String> or an IVector<String>, a str raises TypeError, although it is an iterable of strings: it would be read as one string for each of its characters. Pass a list of one string instead, such as DeviceInformation.create_watcher(aqs, ["System.ItemNameDisplay"]).

Changed in version 4.0

A str given for a collection of strings raises TypeError. Before, it was read as one string for each of its characters.

Sequences

IVector<T> is projected as MutableSequence[T] and IVectorView<T> is projected as Sequence[T]. These types behave very much like Python lists, so you can iterate them with a for loop, index them seq[0], slice them seq[:3] use them with len(seq) and search with value in seq. A slice is a winrt.system.Array, which is a Sequence as well, so it can be indexed, sliced and searched the same way.

For mutable sequences, items can be modified with seq[0] = value, deleted with del seq[0], appended with seq.append(value) or seq.extend(seq2), inserted with seq.insert(0, value), removed with seq.remove(value), and cleared with seq.clear(). You can also reverse the sequence in place with seq.reverse(), pop an item at a specific index using seq.pop(index) (or the last item with seq.pop()), or add items using the += operator like seq += [value1, value2].

A slice of a mutable sequence can be replaced with seq[1:3] = [value], by as many items as it holds or by more or fewer, and deleted with del seq[1:3]. A slice with a step is not supported, here as when reading one, and raises NotImplementedError. The sequence is changed one item at a time, so an item that cannot be converted leaves the items before it written.

Added in version 4.0

Assigning and deleting a slice.

Mappings

IMap<K, V> is projected as MutableMapping[K, V] and IMapView<K, V> is projected as Mapping[K, V]. These types behave like Python dictionaries, so you can access values with map[key] or map.get(key), check for keys with key in map, and iterate over keys with a for loop. You can also retrieve all keys, values, or key/value pairs using map.keys(), map.values(), and map.items() respectively. Equality operators (== and !=) can be used to compare mappings for equality based on their key-value pairs. You can also use len(map) to get the number of keys in the mapping. Like a dict, a mapping has no hash, so it cannot be a key of a dict or be put in a set.

Changed in version 4.0

A mapping has no hash. Before, it hashed by identity, so two equal mappings had different hashes.

Note

Python iterates over the keys only which might not be what you expect if you are used to using the same types in .NET. In Python, you will write:

for key in map:
    print(key)

To get both the keys and values, use the items() method:

for key, value in map.items():
    print(key, value)

For mutable mappings, you can add or update items with map[key] = value, use value = map.setdefault(key, default) to insert a key with a default value if it doesn't exist, delete items with del map[key], and clear all items with map.clear(). You can also remove and return a value while removing the key using map.pop(key) or map.popitem(). Multiple values can be set at the same time using map.update(other).

A key missing from a mapping raises KeyError, as a dictionary does, but a key that is not of the mapping's key type raises TypeError, even in key in map and map.get(key), as it does for os.environ: 5 in map for a map with str keys is a mistake to report, not a key to look for.

There is also a special handling for IIterable<IKeyValuePair<K, V>> when used as an argument to methods where a Python mapping is allowed. This means you can pass a Python dictionary as the argument.

Example:

data = NotificationData({"my_key": "my_value"})

Iterators

IIterable<T> becomes Iterable[T]. Any iterable Python type but a str for an IIterable<String>, as above, can be used as an argument and return values can be used as any other iterable in Python. Don't use the WinRT first() method to get an iterator, instead use the builtin iter() function or other Python features like for loops.

IIterator<T> is projected as Iterator[T] however these objects are rarely used directly in Python. Instead, use for loops or generator expressions to do the iterating for you. In rare cases, iterators might be used with next(). The WinRT methods on this object should be avoided.

Context managers

Any type that implements IClosable can (and probably should) be used as a context manager in Python:

from winrt.windows.foundation import MemoryBuffer

with MemoryBuffer(256) as buf:
    ...

Generally, when an object it closable, it means that there are unmanaged resources that may need to be released in a deterministic manner as opposed waiting for the garbage collector to run to clean them up.

Note

.NET programmers may recognize this as similar to using statements in C# with IDisposable types.

Date and time

There are a few foundational time-related types in WinRT that are projected as the analogous Python types.

Windows.Foundation.DateTime

This type is converted to a datetime.datetime object from the standard Python library.

WinRT has a resolution of 100 nanoseconds while the Python type has a resolution of 1 microsecond, so there is a small loss of precision in the conversion. Python also has a much smaller allowed range of dates (years from 1 to 9999), so a value from WinRT outside those years raises OverflowError.

WinRT serializes values of this type as 100s of nanoseconds since January 1, 1601 (UTC). It uses a signed 64-bit integer for this, so the "q" format string is used in Python.

WinRT uses UTC for all values, so any datetime object returned from a Windows API will use that timezone. It is recommended to use the UTC timezone when creating datetime objects to pass to Windows APIs as well. "Naive" datetime objects (without a timezone) are assumed to use the local timezone and will be converted to UTC. On Windows, Python can find the local time only for a limited range of years, so a naive datetime far in the past or future raises an error from Python itself. An aware datetime converts for every year from 1 to 9999.

Example:

# set notification to expire 10 seconds from now
toaster.expiration_time = datetime.now(timezone.utc) + timedelta(seconds=10)

Windows.Foundation.TimeSpan

This type is converted to a datetime.timedelta object from the standard Python library.

WinRT has a resolution of 100 nanoseconds while the Python type has a resolution of 1 microsecond, so there is a small loss of precision in the conversion. WinRT also has a much smaller range, about 29,000 years either way (10,675,199 days), so a longer timedelta, such as timedelta.max, raises OverflowError.

WinRT serializes values of this type as 100s of nanoseconds. It uses a signed 64-bit integer for this, so the "q" format string is used in Python.

Error codes and event tokens

Two structs in WinRT hold a single integer, and are projected as subclasses of int, so they compare, hash and format as the integer they hold.

Windows.Foundation.HResult

An error code that WinRT hands back as a value rather than raising, such as an async operation's error_code. It compares with OSError.winerror and with the codes in winrt.system.hresult directly.

A code is a signed 32-bit integer, which is how WinRT hands one back, so E_FAIL is -2147467259. HResult() takes one integer, in that form or in the unsigned form a code is usually written in, so HResult(0x80004005) == E_FAIL. Anything that is not a 32-bit code in either form raises OverflowError.

Windows.Foundation.EventRegistrationToken

What add_<event>() returns and remove_<event>() takes. See Events. Only WinRT issues one, so creating one from Python raises TypeError.

Changed in version 4.0

HResult and EventRegistrationToken are subclasses of int. Previously each was a struct with a single value field, which is deprecated and raises a DeprecationWarning.

Numerics

The structs of Windows.Foundation.Numerics - Vector2, Vector3, Vector4, Quaternion, Plane, Matrix3x2 and Matrix4x4 - have the arithmetic, the methods and the constants that System.Numerics gives them in .NET, such as v.length(), v.dot(w), Vector3.unit_x and Matrix4x4.make_rotation_z(). Their fields are 32-bit floats, so a value read back from one is rounded to float32 precision.

The operators:

Expression Meaning
v + w, v - w, -v Elementwise, for the vectors, the quaternion and the matrices.
v * w, v / w Elementwise, for two vectors of the same type.
v * 2, 2 * v, v / 2 Scaling by a number, on either side; a matrix or a quaternion is multiplied by a number but not divided.
a @ b The matrix product of two matrices of the same type.
q * r, q @ r The Hamilton product of two quaternions, which composes their rotations; q.concatenate(r) is r * q.
v @ m v.transform(m): a vector by a Matrix4x4 or a Quaternion, and a Vector2 by a Matrix3x2 too.
abs(v) v.length(), for the vectors and the quaternion.

* between two matrices raises TypeError, so that it is never taken for either the matrix product or an elementwise product. Otherwise an operator takes a value of the struct, or a number where it scales, and with anything else it leaves the answer to the other operand. So a struct and a NumPy array give NumPy's answer either way round, and a tuple raises TypeError, although a method takes a tuple of a struct's fields in place of the struct.

The library uses row vectors: a vector is transformed by putting it on the left of a matrix, and a translation is in the last row of the matrix, in m41, m42 and m43. A matrix on the left of a vector is not supported. Since transforms compose from left to right, v @ (a @ b) applies a and then b.

from math import pi
from winrt.windows.foundation.numerics import Matrix4x4, Vector3

move = Matrix4x4.make_translation(10, 0, 0)
turn = Matrix4x4.make_rotation_z(pi / 2)

point = Vector3(1, 0, 0) @ (turn @ move)   # turned, then moved: (10, 1, 0)

A vector or a quaternion is a sequence of its components, so it unpacks and works with len(), sum(), max() and the like. A matrix is indexed by a row and a column, counted from 0: m[3, 0] is m.m41. unpack() gives the fields of any of them as a tuple.

x, y, z = Vector3(1, 2, 3)

NumPy

Each of these structs supports the buffer protocol: a read-only buffer of its floats, in the shape (2,), (3,) or (4,) for the vectors and the quaternion, (4,) for a Plane (the normal and then the distance), and (3, 2) or (4, 4) for the matrices, a row after another. An Array of them is a writable block of floats with one more dimension, (n, 3) for an array of Vector3 and (n, 4, 4) for an array of Matrix4x4. NumPy and anything else that reads buffers sees them without a copy:

import numpy as np
from winrt.system import Array
from winrt.windows.foundation.numerics import Matrix4x4, Vector3

np.asarray(Matrix4x4.identity)             # a (4, 4) float32 view
points = Array(Vector3, 1000)
np.asarray(points)[:, 2] = 1               # writes z of every point

The other way around, a buffer of floats or doubles in exactly the shape of a struct is taken wherever a method takes the struct, as a tuple of its fields is: a NumPy array of shape (3,) stands for a Vector3 and one of shape (4, 4) for a Matrix4x4. The exception is a method whose overloads take different structs, such as transform(), which picks what to do by the type of the struct it is given, so it takes the struct itself and not a tuple or a buffer. An operator takes neither, as above. An Array of one of the structs is made from, and an array parameter of one takes, a block of float32 values of shape (n, 3), (n, 4, 4) and so on:

vectors = Array(Vector3, np.zeros((1000, 3), np.float32))

Flat floats are not taken for a shape, as NumPy does not take them for one either: sixteen floats in a row could be the rows of a matrix or its columns. Give them their shape with reshape(), or with memoryview(data).cast("B").cast("f", (4, 4)) for an array.array. A tuple is another thing: it is the fields of the struct, in the order its constructor takes them, so a Matrix4x4 is sixteen numbers in a tuple as it is in Matrix4x4(...).

A value of one of the structs, or an Array of them, is not taken as another of the structs, although its floats would fit, because a Quaternion is not a Vector4 and three Vector2 are not a Matrix3x2. To read its floats as another one on purpose, pass np.asarray(value) or memoryview(value), which are plain floats:

from winrt.windows.foundation.numerics import Quaternion, Vector4

rotations = Array(Quaternion, 2)
Array(Vector4, rotations)                  # TypeError
Array(Vector4, memoryview(rotations))      # the same floats, as two Vector4

Changed in version 4.0

* between two matrices was the matrix product, and is a TypeError; the product is @. The structs export a buffer, the vectors and the quaternion are sequences, a matrix takes m[row, column], a number multiplies a matrix or a quaternion from either side, and a buffer is taken in place of a struct where a method takes one. An operator took a tuple of a struct's fields in place of the struct, and takes only the struct. An Array of these structs exported a buffer of one item per element with a named field per float, and exports a block of floats.


  1. Strings that are converted to Char16 can only contain one character, similar to how ord() works. ↩

  2. "u" is deprecated in the array module and is not compatible with the struct module. Use "H" instead if needed, which is what winrt.system.StructFormat carries for Char16. ↩

  3. Use "I2H8B" with the struct module since it does not support the PEP 3118 T{} syntax. ↩