WinRT Is COM — IInspectable, .winmd, Language Projections, and Why WinUI Still Rests on a Binary Contract
· Updated: · Go Komura · Windows, WinRT, COM, WinUI, Windows App SDK, Windows Development
Revision history (first version, published Aug 29, 2026)
- First published
Cite this article(DOI: 10.5281/zenodo.22640267)
This article is archived on Zenodo. Below are both the DOI that always resolves to the latest version and the DOI pinned to the version you are reading.
Go Komura (2026). WinRT Is COM — IInspectable, .winmd, Language Projections, and Why WinUI Still Rests on a Binary Contract. KomuraSoft LLC. https://doi.org/10.5281/zenodo.22640267 https://comcomponent.com/en/blog/winrt-is-com/
- DOI (latest version)
- 10.5281/zenodo.22640267
- DOI (this version)
- 10.5281/zenodo.22640268
You want to add a new Windows capability to a WPF or WinForms app. But calling a WinRT picker throws an exception. Then you read about WinUI, and it looks as if you would have to rebuild the UI. This confusion clears up once you separate how WinRT works from the choice of UI framework.
The starting point of this article is that WinRT, too, rests on the COM binary contract. Microsoft itself states plainly that “The Windows Runtime is based on COM”.1 The Excel table embedded in Word that we saw in the previous article on OLE objects, and today’s WinRT and WinUI, have the same IUnknown at their root.
Here we first pin down the three elements that make up WinRT, then look at what to watch for when using it from a desktop app, and finally consider how to treat existing assets. The intended readers are developers with experience in COM or Windows desktop development, the prerequisites are Windows 10/11 and .NET 6 or later (C#) or C++17 (C++/WinRT), and the difficulty is intermediate.
1. The Bottom Line First
There are three conclusions to hold on to.
- WinRT is not a managed runtime; it is an ABI (a binary contract) built on COM. Added to that contract are
.winmd, which carries type information, and language projections, which let each language call it naturally.1234 - Most WinRT APIs can be used from existing WPF, WinForms, and Win32 apps. But three prerequisites have to be checked: passing an HWND, package identity, and thread initialization.5678
- A full UI migration to WinUI and selective use of WinRT APIs are separate decisions. WinUI also sits on the WinRT ABI, and existing COM/ActiveX assets and WinRT can coexist on the same foundation.921
From here on, reading in the following order makes the connections easiest to follow.
| What you want to know | Where to read |
|---|---|
| Why can we say “WinRT is COM”? | Sections 2–3: what it shares with COM, and IInspectable |
| Why can C# and C++ call it so naturally? | Sections 4–5: .winmd and language projections |
| What must be checked to use it from an existing app? | Section 6: HWND, package identity, apartments |
| Should we migrate to WinUI? | Sections 7–9: what sits under WinUI, the migration decision, the registration conditions for notifications |
The knowledge map below is for reviewing how the elements relate. If you would rather read the explanation first, proceed from Section 2 in order.
In the diagram a solid line marks a relation that always holds and a dashed line marks a conditional one (the conditions are given per relation on the detail page). The full list of relations (20 in total, with evidence and certainty) and the definitions of the main concepts are collected on the knowledge map detail page (in Japanese). Data: JSON-LD / Turtle
2. OLE and WinRT Share the Same Binary Contract at the Root
On this blog we have so far followed the world of classic COM: the design philosophy of COM, the STA/MTA threading model, handling ActiveX/OCX, and OLE compound documents. All of these are technologies dating from the 1990s.
WinRT, on the other hand, is the API foundation introduced with Windows 8 (2012). Today it provides toast notifications, Share, Bluetooth, OCR, and more as the APIs in the Windows.* namespaces, and it is also what WinUI and the Windows App SDK stand on.10 Old and new look like entirely separate worlds, but in substance they are continuous.
What They Share Is the Promise to Call Through Interfaces
COM components and WinRT classes both expose their functionality through interfaces. Comparing the base interfaces, the relationship looks like this.1
| Classic COM | WinRT |
|---|---|
The base of every interface is IUnknown |
The base of every interface is IInspectable, whose base is IUnknown |
In other words, WinRT did not replace COM with an unrelated mechanism; it is one more layer stacked on top of IUnknown. Reference counting, QueryInterface, and HRESULT all live on as they were.
The C++/WinRT documentation likewise calls the WinRT APIs “an evolution of COM” and explains that they are designed to be consumed through language projections as COM-based APIs.211
flowchart TB
accTitle: The lineage of classic COM and WinRT
accDescr: On the shared foundation of the COM binary contract made of IUnknown and vtables stand, side by side, the classic COM world of OLE and ActiveX from the 1990s and the world of WinRT from 2012 onward with WinUI and the Windows App SDK on top of it; the two are not mutually exclusive but continuous
base["COM binary contract (IUnknown, vtable)"]
base --> classic["Classic COM (OLE, ActiveX, custom COM)"]
base --> winrt["WinRT (IInspectable, .winmd)"]
winrt --> winui["WinUI / Windows App SDK"]
classic -.-> coexist["Can coexist on the same foundation"]
winrt -.-> coexist
Figure 1: Classic COM and WinRT are not separate worlds but two generations, old and new, on the same binary contract.
That is why this article is not merely “an introduction to a new API”. It is an article that confirms where the COM knowledge you already have applies in Windows development in 2026.
3. IInspectable on Top of IUnknown
The Unchanged Foundation and the Three Added Methods
The official specification of the WinRT type system stipulates that every WinRT interface implicitly requires IInspectable, and IInspectable requires IUnknown. What IUnknown defines is, as always, the three methods QueryInterface, AddRef, and Release.12
On top of that, IInspectable adds the following three methods.13
| Method | Role |
|---|---|
GetIids |
Returns the list of IIDs of the interfaces this object implements |
GetRuntimeClassName |
Returns the fully qualified WinRT type name (such as Windows.Storage.StorageFile) as an HSTRING |
GetTrustLevel |
Returns the object’s trust level |
flowchart TB
accTitle: IInspectable resting on IUnknown
accDescr: Every WinRT interface requires IInspectable, and IInspectable requires IUnknown. IUnknown provides QueryInterface, AddRef, and Release; IInspectable provides GetIids, GetRuntimeClassName, and GetTrustLevel; and the methods of each WinRT interface sit on top of those
unk["IUnknown (QI, AddRef, Release)"]
insp["IInspectable (GetIids, type name, trust level)"]
api["Methods of each WinRT interface"]
unk --> insp
insp --> api
Figure 2: A WinRT object stacks the three methods of IInspectable on the three methods of IUnknown, and the individual APIs sit on top of that.
From a Type Name You Can Look Up the Definitions of Methods, Properties, and Events
What matters is not the number of added methods but the ability to connect a type name to metadata.
In classic COM, the standard way to learn an object’s identity at run time was “know the IID and ask through QueryInterface”. For scripting languages there was a separate path, IDispatch.
In WinRT, the type name obtained from GetRuntimeClassName can be resolved against the .winmd metadata covered in the next section. From there you get the complete definition of its methods, properties, and events. The specification itself says that being able to obtain a WinRT type name resolvable through metadata “enables language projection”.12
flowchart TB
accTitle: From GetRuntimeClassName to language projection
accDescr: When the caller invokes GetRuntimeClassName on an object, the fully qualified WinRT type name comes back; resolving that type name against Windows Metadata yields the complete definition of the type, and that is what makes projection into each language possible
obj["WinRT object"] --> name["Type name (GetRuntimeClassName)"]
name --> md["Resolve the type definition in .winmd"]
md --> proj["Language projection becomes possible"]
Figure 3: “The type name is available at run time, and the type name leads to the metadata” is the heart of WinRT’s mechanism.
Separate “Inheritance” from “Requires” for User-Defined Interfaces
There is also a difference that people used to COM should note. The WinRT type system has no inheritance between user-defined interfaces. Derivation such as classic COM’s IFileSystemBindData2 : IFileSystemBindData is deliberately absent; instead it is expressed by the declaration “interface A requires interface B”.121
This is a separate matter from the base ABI chain IUnknown → IInspectable we have seen so far. That base remains as the foundation of every WinRT interface.
User-defined contracts were moved toward a looser form that does not depend on the vtable inheritance layout. At the same time, the calls themselves still go through the vtable. It is important not to confuse the way contracts are written with the mechanism of the call.
4. What .winmd Solved — The Type Information Binding Hell
The Difficulty of Classic COM Lay in “How to Distribute Type Information”
In classic COM practice, more effort went into how to distribute type information than into implementing the interfaces themselves.
| Consumer | Path for delivering type information |
|---|---|
| C++ | Write the contract in IDL, generate headers and proxy/stub with MIDL |
| VB6, scripting | Distribute a type library (TLB) |
| .NET | Build a separate interop assembly |
A TLB has automation-oriented type restrictions: some information that can be written in IDL does not fit in a TLB. And because each language has its own path, when any one of them goes stale you end up with type mismatches. We covered these troubles in the article on type libraries and dscom and the article on DLL and COM interface backward compatibility.
.winmd Is the Common Contract That Every Language Reads
WinRT’s answer is Windows Metadata (.winmd). The APIs are described as machine-readable metadata, and tools and language projections read it to generate a projection for each language.3
Windows ships the metadata for every system-provided WinRT API and also provides APIs that resolve namespaces and types at run time. The Windows SDK contains a copy for compile time. Third parties, too, can take part in language projection through the same mechanism as the system APIs by attaching a .winmd to their own WinRT component.3
What changed here is the form in which type information is distributed. In place of TLBs, headers, and interop assemblies scattered per language, a single .winmd is now read by the projections of every language.
That said, IDL has not become unnecessary. When you author a WinRT component, you still describe the contract in IDL (MIDL 3.0, modernized for WinRT), and the MIDL compiler generates the .winmd.14
flowchart TB
accTitle: The production pipeline of a WinRT component
accDescr: The contract of a WinRT component is still written in IDL, that is, MIDL 3.0, and the MIDL compiler compiles it into a .winmd. What is distributed is this .winmd, and each language's projection tool, such as cppwinrt.exe or cswinrt.exe, reads it to generate the projection. What was replaced is not IDL but the form in which type information is distributed
idl2["Write the contract (IDL, MIDL 3.0)"]
midl2["MIDL compiler"]
winmd4[".winmd (distributed type information)"]
proj3["Generate each language's projection"]
idl2 --> midl2
midl2 --> winmd4
winmd4 --> proj3
Figure 4: The entry point of the contract (IDL) remains in service; the exit point, the distributed type information, was unified into .winmd.
The Same File Format as .NET, but Not a Managed Runtime
The physical format of a .winmd uses the ECMA-335 specification, the same as a CLR assembly. The rules for which combinations of data are valid differ from those for CLR assemblies, however. Borrowing the format and needing the CLR to run are two different things.3
Here you have to read system APIs and third-party components separately.
| Subject | Relationship between the .winmd and the implementation |
|---|---|
| System-provided WinRT APIs | The .winmd is pure metadata with no executable code. The implementation lives in native OS DLLs, and the CLR is not needed to run it |
| Third-party WinRT components | The .winmd may also contain implementation code. For a managed component (written in C#) that contains MSIL, the corresponding .NET runtime is required to run it |
Because a .winmd looks like a .NET assembly when opened in a tool, it is easy to fall into the misconception “WinRT = managed”. But the content of a system-provided .winmd is a contract for COM interfaces.3
flowchart TB
accTitle: Separation of .winmd and implementation
accDescr: A .winmd is metadata that borrows the ECMA-335 physical format; the system-provided ones are contracts with no executable code, and the implementation of the system-provided WinRT APIs lives in native OS DLLs. Because of this separation, even though a .winmd looks like a .NET assembly, the CLR is not needed to run the system WinRT APIs (the .winmd of a third-party managed component contains MSIL and requires the .NET runtime)
winmd3[".winmd (contract, ECMA-335 format)"]
impl["Native OS DLL (implementation)"]
winmd3 -.->|"System-provided: no code"| note3["System APIs need no CLR"]
impl --> note3
winmd3 ---|"Type definitions map to implementation"| impl
Figure 5: For system-provided WinRT APIs the .winmd is the contract and the implementation is a native OS DLL. The format looks like .NET, but execution is native COM.
flowchart TB
accTitle: Classic COM type information versus .winmd
accDescr: In classic COM the type information path was split per language, from IDL to C++ headers, from type libraries to VB6 and scripting, from interop assemblies to .NET, which caused inconsistencies, whereas in WinRT a single .winmd is read in common by the projections of every language
subgraph old["Classic COM: one path per language"]
idl["IDL to C++ headers"]
tlb["TLB to VB6 and scripting"]
ia["Interop assembly to .NET"]
end
winmd[".winmd (single metadata)"]
winmd --> all["Read in common by every language's projection"]
Figure 6: .winmd folded the per-language scattering of type information into “one metadata file that everyone reads”.
The Constraints Did Not Vanish; They Shifted to an Axis of Projectability
For those who know classic COM, in a phrase: .winmd is “the type library, done over”. Think of it as the role a TLB tried to play, redesigned from the outset as a single source of truth shared by all languages, on top of the proven ECMA-335 format, and its position becomes clear.
The constraints have not gone away, however. The automation-oriented constraints of TLBs were replaced by the constraints of WinRT’s own type system, whose axis is “can be projected safely into every language”. The absence of inheritance between user-defined interfaces, seen in Section 3, is one example.12
Consequently, an existing COM/IDL contract cannot necessarily be carried into WinRT as is. The API may need to be redesigned.
flowchart TB
accTitle: Replacement of type library constraints by WinRT type system constraints
accDescr: The automation-oriented expressiveness constraints of the TLB (type library) were not removed by .winmd but replaced by the constraints of WinRT's own type system, whose axis is safe projection into every language. One example is the absence of user-defined interface inheritance; an existing COM/IDL contract cannot necessarily be carried over as is, and the API may need to be redesigned
tlb["TLB constraints (automation-oriented)"] -->|"Replaced by"| wrt["WinRT type system constraints (projectability axis)"]
wrt -.-> ex["Example: no user-defined inheritance"]
wrt -.-> re["Existing COM contracts may need redesign"]
Figure 7: The TLB constraints did not “disappear”; they were replaced by different constraints whose axis is projectability into every language.
5. C++/WinRT and C#/WinRT Are Projections, Not “Wrappers”
Showing the Common Contract to Each Language in Its Natural Form
Once there is a common contract in the form of .winmd, the view for each language can be generated automatically by tools. This is a language projection. It exposes WinRT APIs in the idiom of each language and hides the details of COM, providing a programming experience that feels natural to that language.411
The projections Microsoft currently supports are the following two.4
| Projection | What it generates | Characteristics |
|---|---|---|
| C++/WinRT | cppwinrt.exe generates C++ projection headers from .winmd |
A header-file-based projection in standard C++17. No language extensions like those of C++/CX are needed; the successor to C++/CX and WRL11 |
| C#/WinRT (CsWinRT) | cswinrt.exe generates C# code from .winmd and turns it into an interop assembly |
The projection for .NET. A toolchain independent of the runtime1516 |
The history on the C# side is a little confusing, so let us untangle it. Up to .NET Core 3.x, the .NET runtime had built-in support for consuming WinRT/winmd. In .NET 5 that built-in support was removed, and the role moved to C#/WinRT.16 WinRT APIs did not become unusable; where the projection is handled changed.
Today, when you specify a TFM such as net8.0-windows10.0.19041.0 in C#, the Windows SDK projection assemblies are referenced automatically.10
flowchart TB
accTitle: Generating projections for each language from .winmd
accDescr: When cppwinrt.exe reads the single .winmd, it generates C++17 projection headers; when cswinrt.exe reads it, it generates a C# interop assembly; and the WinRT APIs are exposed in a form that follows the idiom of each language
winmd2[".winmd (the API contract)"]
winmd2 --> cpp["cppwinrt.exe to C++17 headers"]
winmd2 --> cs["cswinrt.exe to C# interop assembly"]
cpp --> cppcode["Callable in C++ idiom"]
cs --> cscode["Callable in C# idiom"]
Figure 8: A projection is not a hand-written wrapper; tools generate it mechanically from the contract (.winmd).
Even Though It Is Generated, the Call Itself Is Still COM
This article says “projection” rather than “wrapper” to emphasize that it is a mechanism derived mechanically from metadata, not a translation layer that people maintain API by API. Any API present in the .winmd can be made usable in every supported language from the start.
And whichever language you call from, what happens beneath the projection is the same COM call. For example, when you write await picker.PickSingleFolderAsync() in C#, the projection bridges WinRT’s IAsyncOperation into the world of .NET’s Task. Even so, on the ABI side, vtable method calls and HRESULT are what is used. That is why errors show up in the form of COM exceptions (an HRESULT such as 0x80070005).
flowchart TB
accTitle: The layers from C# code to the OS WinRT API
accDescr: The app's C# or C++ code is converted through the language projection into WinRT ABI calls, that is, vtable calls on IInspectable, and reaches the WinRT API implemented by the OS. The projection only hides the details of COM; the call itself is COM
code["App code (C#, C++)"]
proj2["Language projection"]
abi["WinRT ABI (IInspectable vtable)"]
os["OS implementation of the WinRT API"]
code --> proj2
proj2 --> abi
abi --> os
Figure 9: What the projection hides is the “details” of COM, not COM itself.
Knowing this structure lets you split trouble into two layers. A version mismatch in the generated code or a missing TFM setting is the projection layer; HRESULTs, apartments, and reference counting are the ABI layer. In the latter, COM development experience serves you directly.
6. Where Desktop Apps Get Stuck — HWND, Identity, Apartments
First Separate “Being Able to Reference the API” from “The Conditions for It to Work”
Most WinRT APIs can be called from WPF, WinForms, and Win32 desktop apps.5 The entry point for calling them differs between C# and C++ as follows.10
| Environment | Initial setup |
|---|---|
| C#/.NET 6 or later | Set TargetFramework to a TFM with a Windows OS version, such as net8.0-windows10.0.19041.0 |
| C++ | Add the Microsoft.Windows.CppWinRT NuGet package and use C++/WinRT with C++17 or later |
Being able to reference the APIs, however, does not mean every API works as is. Check the following three points. The registration conditions for toast notifications are covered separately in Section 9.
| What to check | Main remedy |
|---|---|
| Does it need a window to display on? | Pass an HWND through the COM interop that matches the UI |
| Does it need package identity? | Grant identity with MSIX or a package with external location |
| Is the thread initialized for WinRT? | In native code, initialize with STA/MTA specified. In C# the runtime normally handles it |
Sticking Point 1: Pass an HWND to UI Such as Pickers
Some pickers, dialogs, and the Share UI expect a UWP CoreWindow as the surface to display on. A desktop app has no CoreWindow, so the owner window’s HWND must be passed explicitly before the object is shown.6
The entry point used for pickers and the like is a COM interface called IInitializeWithWindow. It inherits from IUnknown and provides an owner window to WinRT objects used in desktop apps.17
In C#, first obtain the HWND according to the UI framework in use.186
| Owner window | How to get the HWND |
|---|---|
A WinUI Window |
WinRT.Interop.WindowNative.GetWindowHandle |
| A WPF window | WindowInteropHelper |
| A WinForms form | The form’s Handle property |
Next, hand it to the picker with WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd), and only then show it. In C++/WinRT, obtain the object as as<IInitializeWithWindow>() and then call Initialize(hwnd). If you skip the initialization, it throws or fails silently.1819
Initializing a modern WinRT object by QueryInterface for a classic COM interface: the fact that this bridge is the official practice is another place where WinRT shows that it is COM.
The Share UI uses a different interface. For DataTransferManager you do not use IInitializeWithWindow. You use the dedicated IDataTransferManagerInterop and pass the HWND to ShowShareUIForWindow.6
The newer Windows App SDK pickers are another separate path. Microsoft.Windows.Storage.Pickers takes a WindowId in the constructor, so the InitializeWithWindow pattern is unnecessary. They are not APIs you can use with a TFM setting alone, however. In addition to adding the Windows App SDK, an unpackaged app needs the runtime deployed and initialized on the target machines.19
sequenceDiagram
accTitle: Steps to show a picker from a desktop app
accDescr: After a desktop app creates a picker, calling PickSingleFolderAsync as is results in an exception or a silent failure, so the owner window's HWND has to be obtained first and passed through IInitializeWithWindow's Initialize before the picker is shown
participant A as Desktop app
participant P as Picker (WinRT)
A->>P: Create
alt Show without passing an HWND
A->>P: PickSingleFolderAsync
P-->>A: Exception or silent failure
else Pass the HWND first
A->>P: Set the HWND through IInitializeWithWindow
A->>P: PickSingleFolderAsync
P-->>A: The picker is shown
end
Figure 10: Filling the gap of “there is no CoreWindow on the desktop” by passing an HWND explicitly is the official practice.
Sticking Point 2: Some APIs Require Package Identity
Some WinRT APIs, such as the toast notification history (ToastNotificationHistory), jump lists, and share targets, work only in apps that have package identity (packaged apps). Calling them from an unpackaged app distributed with a traditional installer fails.7
There are two remedies. Package the app with MSIX, or use a “package with external location” (a so-called sparse package), which grants an identity while keeping the existing installer.20 Which APIs require identity can be checked in the official list.7
flowchart TB
accTitle: Two paths to APIs that require package identity
accDescr: Calling a WinRT API that requires package identity from an unpackaged app fails, so give the app package identity either by packaging it with MSIX or with a package with external location, which grants only the identifier while keeping the existing installer
need["Want to use an identity-required API"]
need --> m1["Package with MSIX"]
need --> m2["Package with external location"]
m1 --> id2["Obtain package identity"]
m2 --> id2
id2 -.-> okid["Notification history etc. work"]
Figure 11: The remedy for identity-required APIs is a choice of two: “MSIX” or “existing installer + granting an identifier”.
Sticking Point 3: Check Thread Initialization and STA/MTA
A thread that handles WinRT objects must be initialized for WinRT beforehand. In native code, use RoInitialize or winrt::init_apartment and specify the concurrency model, STA or MTA. In C# WPF/WinForms apps, the runtime normally handles the initialization.8
RoInitialize is the WinRT-generation entry point within the same framework as COM’s CoInitializeEx. The CoInitialize documentation itself directs you to call RoInitialize or Windows::Foundation::Initialize instead when using the Windows Runtime.21
The WPF/WinForms UI thread is an STA; UI objects must be touched on the UI thread; blocking waits on an STA invite deadlocks. The thinking covered in the STA/MTA article carries over unchanged to WinRT APIs.
Some APIs Cannot Be Used Even With an HWND and Identity in Place
The remedies so far concern APIs that have an entry point for desktop use. APIs that depend on CoreWindow or ApplicationView themselves cannot be used from desktop apps. In that case, rather than adding initialization, look for an alternative API.57
flowchart TB
accTitle: Branching of the sticking points when calling WinRT APIs from the desktop
accDescr: First confirm that the thread is initialized for WinRT (in native code specify STA/MTA explicitly with RoInitialize or similar; in C# the runtime normally handles it). Then, if the WinRT API you want to call is UI that assumes a CoreWindow, pass an HWND through IInitializeWithWindow or use the new pickers; if it requires package identity, grant an identifier with MSIX or a package with external location; APIs that depend on CoreWindow or ApplicationView themselves cannot be used on the desktop, so look for an alternative API; and the large remainder of APIs can be called as is with only the TFM or C++/WinRT setup
pre["Thread initialization"] --> q{"What kind of API is it?"}
pre -.-> auto["Native: explicit; C#: usually automatic"]
q -->|"UI"| h["HWND or the new pickers"]
q -->|"Identity required"| p2["MSIX or grant an identifier"]
q -->|"Depends on CoreWindow itself"| x["Look for an alternative API"]
q -->|"Everything else"| ok3["Callable as is"]
Figure 12: With thread initialization as the premise (explicit in native code, normally left to the runtime in C#), the sticking points fall into three families, each with a standard remedy.
flowchart TB
accTitle: Correspondence of thread initialization between COM and WinRT
accDescr: In classic COM a thread is initialized with CoInitializeEx specifying STA or MTA, whereas in WinRT it is initialized with RoInitialize specifying the same STA or MTA concurrency model. The CoInitialize documentation also directs you to call RoInitialize when using WinRT, and the apartment concept is shared
com3["Classic COM: CoInitializeEx"] --> apt["Apartment (STA / MTA)"]
wrt["WinRT: RoInitialize"] --> apt
apt -.-> rule["UI thread is STA; beware of waits"]
Figure 13: The name of the initialization API changed, but the same apartment concept continues to be used.
7. What Sits Under WinUI — Development in the Open Does Not Change the Contract
Moving to Development in the Open Is Not a Change to the Binary Contract
In the summer of 2025, Microsoft officially announced, as a phased approach, its policy of moving mainline WinUI development into the open on GitHub. There are four stages: increase the update frequency of the mirror, make local builds possible, accept community contributions once tests are in place, and finally make GitHub the primary home of development.22
As of this article’s publication (late August 2026), the official documentation also states plainly that WinUI is “built in the open”. Day-to-day engineering progress can now be followed in the public repository.9
Developers who have followed the generational shifts from WinForms to WPF to UWP to WinUI naturally feel “the framework is changing again?”. But the UI frameworks that kept changing and the foundation beneath them have to be viewed separately. Win32 and COM, and since 2012 the WinRT ABI, have stayed in the same place.
WinUI Windows, Too, Are Backed by an HWND
WinUI is a set of WinRT APIs provided as part of the Windows App SDK.923 Its Microsoft.UI.Xaml.Window is a window backed by an HWND, replacing the CoreWindow-based window model of the UWP generation. The official interop walkthrough, too, begins by obtaining the window handle.24
In other words, a WinUI app is a Win32 app in which an object tree that follows the IInspectable contract runs on top of an HWND window. The knowledge of QueryInterface, reference counting, and apartments acquired through COM, and the knowledge of HWNDs and message loops acquired through Win32, both apply directly to troubleshooting WinUI.
flowchart TB
accTitle: Generational shifts of UI frameworks and the unchanged foundation
accDescr: The UI frameworks WinForms, WPF, UWP XAML, and WinUI have gone through generation after generation, but WinForms and WPF sit directly on the Win32 and COM foundation, while UWP XAML and WinUI sit on the same Win32 and COM foundation through the WinRT ABI. What changed generations is the upper layer; the contract underneath has not changed
gen["Generational shifts of UI frameworks"]
gen --> f1["WinForms, WPF"]
gen --> f2["UWP XAML"]
gen --> f3["WinUI (current)"]
f2 --> abi3["WinRT ABI (since 2012)"]
f3 --> abi3
f1 --> stable["Unchanged foundation (Win32 + COM)"]
abi3 --> stable
Figure 14: What rose and fell was the framework layer. WinForms and WPF sit directly on Win32 + COM; UWP and WinUI sit on the same foundation through the WinRT ABI.
flowchart TB
accTitle: The layers supporting a WinUI app
accDescr: WinUI's XAML and controls are provided as part of the Windows App SDK, run on the WinRT ABI, that is, the IInspectable contract, and beneath that lies the foundation of COM and Win32's HWND. Under the generational shifts of UI frameworks, this underlying contract has not changed
ui["WinUI (XAML, controls)"]
sdk["Windows App SDK"]
abi2["WinRT ABI (IInspectable)"]
base2["COM + Win32 (HWND)"]
ui --> sdk
sdk --> abi2
abi2 --> base2
Figure 15: Beneath WinUI is the WinRT ABI, and beneath that are classic COM and Win32. Only the stacking changed; the foundation is the same.
Mixing With XAML Islands: Check the Constraints of Each Generation
The strategy of “mixing only WinUI controls into existing WPF/WinForms screens” has to be evaluated separately for each generation of XAML Islands.
| Generation | Situation when used from WPF/WinForms |
|---|---|
| UWP-generation XAML Islands | Wrapper controls from the Windows Community Toolkit exist. But the WPF/WinForms versions stopped at the .NET Core 3.x generation and are not supported on current .NET25 |
| WinUI 3 generation | Can be hosted from WPF, WinForms, and Win32 with the Windows App SDK’s DesktopWindowXamlSource. But there are no convenient wrapper controls like those of the UWP generation, and the implementation and validation burden of handling the hosting API directly falls on you26 |
If you plan incremental mixing, it is safer not to assume control-level mixing alone. Organizing the plan around feature-level use of WinRT APIs (Section 6) or separation at the screen or process level is the sounder approach as of 2026.
8. Implications for Business Apps — Full Migration and Selective Use Are Separate Problems
“WinRT is a new, separate world, so using it means throwing away existing assets and rebuilding.” This misconception, encountered in Custom Software Development projects, dissolves once you split it in two.
Existing COM Assets and WinRT Can Coexist
A WPF app that uses Excel COM automation, hosts ActiveX controls, and calls in-house COM components can have WinRT APIs added to it. It is no special acrobatics, because you are combining features on the same COM foundation.
For toast notifications, for example, set the TFM to reference the API and satisfy the registration conditions for showing notifications. So that the latter is not forgotten, Section 9 lays it out path by path. In C++/WinRT, both WinRT and classic COM interfaces can be handled with the same mechanisms, winrt::com_ptr and winrt::implements.21
Estimate UI Renewal and Feature Addition Separately
“Migrate the UI fully to WinUI?” and “Use WinRT APIs only where needed?” are decisions that differ in scale, duration, and risk.
A full UI migration is a framework selection problem that depends on screen assets, third-party controls, and the development organization. We covered that decision in Choosing Between WinForms, WPF, and WinUI. Selective use of WinRT APIs, on the other hand, is a small improvement you can start on in an existing app today.
For a project that just wants toast notifications, there is no need to estimate a full UI migration. Conversely, there is no need to hold off on using WinRT APIs “because we are not migrating to WinUI”.
flowchart TB
accTitle: Full migration and selective use are separate decisions
accDescr: A full UI migration to WinUI is a large framework-selection decision that depends on screen assets and the organization, whereas selective use of WinRT APIs is a small decision that can be added to an existing WPF or WinForms app today with a TFM setting or similar; consider the two separately without confusing them
goal["Want to use new Windows features"]
goal --> big["Full UI migration (large decision)"]
goal --> small["Selective WinRT API use (small decision)"]
big -.-> dep["Depends on screen assets and organization"]
small -.-> today["Can be added to the existing app today"]
Figure 16: There are two roads to “new features”, and confusing them throws off both the estimate and the decision.
9. Decision Table — The WinRT Version of Keep, Wrap, Replace
Choose Only the Changes Each Situation Needs
As an extension of the ActiveX decision table, here is a situation-by-situation decision guide on the WinRT side.
| Situation | Recommendation | Reason |
|---|---|---|
| Want to use WinRT APIs such as toast, Share, or Bluetooth from WPF/WinForms | Selective use through a TFM setting (or adding C++/WinRT) | Callable today without a UI migration. But the Share UI goes through IDataTransferManagerInterop (Section 6), and for toast see the supplement below the table106 |
| Exception or silent failure in a picker or dialog | Pass an HWND through IInitializeWithWindow. For new code, the new WindowId-based pickers (requires adding the Windows App SDK) |
The official practice of filling the CoreWindow-based design with an HWND619 |
| Notification history, jump lists, and the like do not work | Grant package identity with MSIX or a package with external location | Identity-required APIs assume packaging720 |
| Existing COM/ActiveX/OLE assets | Do not discard. Decide keep, wrap, or replace per asset | Not mutually exclusive with WinRT; they coexist on the same foundation (Section 8) |
| UI for a new desktop app | Evaluate WinUI as the first candidate (WPF is still current) | Development in the open made the investment direction clear. Underneath is the WinRT ABI229 |
| Mix WinUI controls into existing WPF/WinForms screens | Evaluate cautiously, allowing for the implementation burden of missing wrappers | UWP-generation Islands stopped at .NET Core 3.x; the WinUI 3 generation offers only the hosting API2526 |
| A plan premised on “WinRT ended together with UWP” | Correct the premise | WinRT is the current API foundation, callable from the desktop5 |
Toast Notifications Do Not Appear With a TFM Setting Alone
The setting that makes the API callable and the registration that makes a notification appear are two different things. When “the build succeeds but no notification appears”, check not just the calling code but the path in use, the packaging form, and whether the process is elevated.
When Using the Classic ToastNotificationManager
For an unpackaged app without package identity, the prerequisite is registering a Start menu shortcut with an AppUserModelID (AUMID) assigned. Without it, toasts cannot be shown.27
An app packaged with MSIX or similar does not need this manual registration, because the package identity supplies the AUMID.
When Using the Windows App SDK’s AppNotificationManager
On this currently recommended path, adding the Windows App SDK and calling Register() at startup are required. Beyond that, the conditions branch by packaging form.28
| Packaging form | What to check additionally |
|---|---|
| Unpackaged | The Windows App SDK runtime must be deployed on each target PC. Register() performs the COM server registration |
| Packaged with MSIX | The automatic registration by Register() does not work. Declare a COM activator in Package.appxmanifest |
Furthermore, on the Windows App SDK path, notifications from a process elevated to administrator are unsupported. Show fails quietly without throwing. In an app that needs elevation, consider separating notifications into a non-elevated process.28
The implementation details around registration are covered in the system tray and notification implementation guide.
flowchart TB
accTitle: The two paths for toast notifications and the registration each needs
accDescr: To show a toast notification from a desktop app, the classic ToastNotificationManager path branches on whether the app is packaged: an unpackaged app must go through registering a Start menu shortcut with an AppUserModelID assigned, while for a packaged app the package identity supplies the AppUserModelID. On the Windows App SDK AppNotificationManager path, in addition to adding the SDK and calling Register at startup, an unpackaged app must deploy the runtime on each target PC and an app packaged with MSIX must declare a COM activator in the manifest before it can proceed. The Windows App SDK path further branches on whether the process is elevated: notifications from a process elevated to administrator are unsupported and Show fails quietly without throwing, so on this path a notification is shown only for a non-elevated process, and an app that needs elevation separates notifications into a non-elevated process
want["Want to show a toast"]
want --> c1["Classic: ToastNotificationManager"]
want --> c2["WASDK: AppNotificationManager"]
c1 --> q1{"Packaged?"}
q1 -->|"No"| s1["Register an AUMID shortcut"]
q1 -->|"Yes"| s2["Identity supplies the AUMID"]
c2 --> r2["Add the SDK + Register()"]
r2 --> q2{"Packaged?"}
q2 -->|"No"| s3["Deploy the runtime"]
q2 -->|"Yes"| s4["Declare COM in the manifest"]
s1 --> shown["Notification is shown"]
s2 --> shown
s3 --> elev{"Elevated process?"}
s4 --> elev
elev -->|"No"| shown
elev -->|"Yes"| fail["Unsupported: Show fails quietly"]
fail -.-> comp["Separate notifications into a non-elevated process"]
Figure 17: On either path, the notification appears only after the prerequisites for the packaging form are satisfied. On the Windows App SDK path, even with every prerequisite in place, nothing is shown from an elevated process.
Finally, Back to Keep, Wrap, Replace
Do you need a new feature, or do you want to renew the UI itself? Returning to this question lets you decide about using WinRT and about replacing existing assets separately.
flowchart TB
accTitle: Decision flow for how existing assets and WinRT fit together
accDescr: Starting from an existing desktop app, a stepwise decision flow: if no new Windows feature is needed, keep it; if a feature is needed, wrap it with selective use of WinRT APIs (watching for the Section 6 sticking points of HWND, package identity, and thread initialization); and only when the UI itself needs renewal, consider replacing it with WinUI
start["Existing desktop app"] --> q2{"What is needed?"}
q2 -->|"Current state suffices"| keep2["Keep (maintain as is)"]
q2 -->|"A new feature"| wrap2["Wrap (selective WinRT API use)"]
q2 -->|"UI renewal"| rep["Replace (evaluate WinUI)"]
wrap2 -.-> note2["Watch HWND, identity, initialization (Section 6)"]
Figure 18: The same “keep, wrap, replace” structure as the ActiveX decision table applies directly on the WinRT side.
10. Summary
WinRT is not a new execution environment cut off from COM. It is an API foundation that combines the COM binary contract with metadata and projections into each language.
| Element | Its role as covered in this article |
|---|---|
IUnknown and IInspectable |
Built on QueryInterface and reference counting, adding three methods that return the type name and more |
.winmd |
The contract shared by every language, from which a type name leads to a definition. Borrows the ECMA-335 format, but the system-provided ones contain no executable code |
| C++/WinRT and C#/WinRT | Generate an API for each language from the contract. Since .NET 5 the C# projection has been a toolchain independent of the runtime |
Even when the surface is a natural C# or C++ API, underneath it are COM calls through vtables and HRESULTs. That is why understanding HWND passing on the desktop, package identity, and STA/MTA with thread initialization makes WinRT trouble easier to isolate.
The effort to move mainline WinUI development into the open was announced in the summer of 2025 as a phased approach. As of this article’s publication the official documentation also says “built in the open”, but the contract beneath it, IUnknown → IInspectable, has not changed.229
The conclusion for business apps is the same. Existing COM/ActiveX assets and WinRT can coexist. Not confusing a full UI migration with selective use of WinRT APIs is what protects the accuracy of estimates and decisions.
The compound documents of the 1990s seen in the OLE article and today’s WinUI stand on the same IUnknown. Knowing COM is not merely “being well versed in legacy”. It is being able to read the foundation of today’s Windows.
Related Articles
- What Is COM? - Why the Design of Windows COM Is Still Beautiful Today
- What Are COM / ActiveX / OCX? - The Differences and Relationships Explained
- COM STA/MTA Fundamentals - Threading Models and How to Avoid Hangs
- How to Handle ActiveX / OCX Today - A Keep / Wrap / Replace Decision Table
- Choosing Between WinForms, WPF, and WinUI - A Practical Decision Table
- What Is an OLE Object? — How Embedding and Linking Work, and the Pitfalls in Business Documents
- Using a .NET 8 DLL from VBA with Full Typing - COM Exposure and dscom TLB
Related Consulting Areas
KomuraSoft LLC handles COM component development and the maintenance, modification, and replacement of Windows business apps that include COM assets. You can consult us from the stage where the content of this article is exactly the issue at hand: “we want toast notifications and pickers while staying on WPF”, “calling a WinRT API stopped with an exception”, “we want to decide whether to migrate to WinUI or make the most of existing assets”.
- Windows Application Development
- COM Component Development
- Technical Consulting and Design Review
- Contact Us
References
-
Microsoft Learn, Consume COM components with C++/WinRT. On programming in COM through interfaces rather than objects; this also holding behind the scenes of the WinRT APIs, which are “an evolution of COM”; and handling WinRT and classic COM in the same style with the winrt::com_ptr COM smart pointer. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Windows Metadata (WinMD) files. On WinRT APIs being described in machine-readable metadata called .winmd, used by tools and language projections; Windows shipping the metadata for every system-provided WinRT API and providing resolution APIs; third parties being able to take part in language projection with the same format; and the physical format being the ECMA-335 specification (the same as CLR assemblies), with the system-provided WinMD files being pure metadata. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Windows Runtime (WinRT) language projections. On language projections exposing WinRT APIs in the idiom of each language; .winmd defining the WinRT APIs and projections reading it; and the two projections Microsoft supports being C++/WinRT (C++17 or later) and C#/WinRT (.NET). ↩ ↩2 ↩3
-
Microsoft Learn, WinRT APIs callable from a desktop app. On most WinRT APIs being usable from .NET and native C++ desktop apps, with classes designed specifically for UWP, such as CoreDispatcher, CoreWindow, and ApplicationView, being the exceptions. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Display WinRT UI objects that depend on CoreWindow. On some pickers, popups, and dialogs depending on CoreWindow; CoreWindow not being supported in desktop apps; classes that implement IInitializeWithWindow (or the equivalent IDataTransferManagerInterop) allowing the owner window’s HWND to be set before display; and the steps for WinUI 3, WPF, and WinForms respectively. ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, WinRT APIs not supported in desktop apps. On the two families of WinRT APIs unusable in desktop apps, those that depend on UWP-only UI features and those that require package identity (such as ToastNotificationHistory and JumpList), and on the latter being supported only in apps packaged with MSIX. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, RoInitialize function (roapi.h). On RoInitialize initializing the current thread for the Windows Runtime with the specified concurrency model (RO_INIT_SINGLETHREADED/RO_INIT_MULTITHREADED); every thread that activates and operates on WinRT objects needing prior initialization; and a conflicting specification on a thread already initialized as MTA resulting in RPC_E_CHANGED_MODE. ↩ ↩2
-
Microsoft Learn, WinUI 3. On WinUI being the recommended native UI framework for new Windows desktop apps, being provided as part of the Windows App SDK, running on Windows 10 version 1809 and later, and being built in the open. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Call Windows Runtime APIs in desktop apps. On specifying a TFM with a Windows OS version (such as net10.0-windows10.0.22621.0) on .NET 6 or later causing the Windows SDK targeting package to be referenced so that WinRT APIs can be called, and on C++ using C++/WinRT with the Microsoft.Windows.CppWinRT NuGet package and C++17 or later. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Introduction to C++/WinRT. On C++/WinRT being a language projection in entirely standard, modern C++17 implemented as a header-file-based library; being the recommended successor to C++/CX and WRL; WinRT being based on COM APIs and designed to be accessed through language projections; projections hiding the details of COM; and cppwinrt.exe generating projection headers from .winmd. ↩ ↩2 ↩3
-
Microsoft Learn, The Windows Runtime (WinRT) type system. On every WinRT interface implicitly requiring IInspectable and IInspectable requiring IUnknown; IUnknown defining QueryInterface, AddRef, and Release; the three methods IInspectable adds, GetIids, GetRuntimeClassName, and GetTrustLevel; GetRuntimeClassName returning a type name resolvable through metadata, which enables language projection; and inheritance between user-defined interfaces being absent from the WinRT type system and expressed with requires instead. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, IInspectable interface (inspectable.h). On IInspectable providing functionality required by every WinRT class, inheriting from IUnknown, and having the three methods GetIids, GetRuntimeClassName, and GetTrustLevel. ↩
-
Microsoft Learn, Introduction to Microsoft Interface Definition Language 3.0. On MIDL 3.0 being a concise, modern syntax for declaring WinRT types, and WinRT contracts still being written in IDL, with the MIDL compiler generating Windows Metadata (.winmd). ↩
-
Microsoft Learn, C#/WinRT. On cswinrt.exe, included in the C#/WinRT NuGet package, processing .winmd to generate C# code and compiling it into an interop assembly, and this being positioned in the same way as C++/WinRT generating headers for C++. ↩
-
Microsoft Learn, Built-in support for WinRT is removed from .NET. On the built-in support for the Windows Runtime being removed from .NET in .NET 5 and moved to the CsWinRT toolchain. ↩ ↩2
-
Microsoft Learn, IInitializeWithWindow interface (shobjidl_core.h). On this being the interface for providing an owner window to WinRT objects used in desktop apps, inheriting from IUnknown, and having an Initialize(HWND) method. ↩
-
Microsoft Learn, Use WinRT COM interop classes in .NET. On some WinRT objects, such as file pickers and dialogs, needing an HWND before they work in a desktop app, and on the type-safe C# classes WinRT.Interop.WindowNative and WinRT.Interop.InitializeWithWindow allowing initialization without hand-written QueryInterface calls. ↩ ↩2
-
Microsoft Learn, Tutorial: Open files and folders with pickers in WinUI. On the classic Windows.Storage.Pickers having to be initialized with an HWND before display when used in a desktop (WinUI 3) app, or else throwing or failing silently; WinUI 3 desktop apps having no CoreWindow; and the new Windows App SDK pickers (Microsoft.Windows.Storage.Pickers) taking a WindowId in the constructor so that the InitializeWithWindow pattern is unnecessary. ↩ ↩2 ↩3
-
Microsoft Learn, Features that require package identity. On some Windows features and WinRT APIs requiring package identity at run time, and on an identity being obtainable not only by distributing as an MSIX package but also with a package with external location. ↩ ↩2
-
Microsoft Learn, CoInitialize function (objbase.h). On CoInitialize initializing the COM library as an STA; new apps being expected to call CoInitializeEx; and RoInitialize or Windows::Foundation::Initialize having to be called instead when using the Windows Runtime. ↩
-
GitHub, WinUI: Now Developing in the Open (microsoft/microsoft-ui-xaml Discussion #10700). The official announcement at the end of July 2025. On the phased approach for opening up the WinUI repository (increasing the mirror update frequency, enabling local builds, accepting community contributions once tests are in place, and finally making GitHub the primary home of development). ↩ ↩2 ↩3
-
Microsoft Learn, Windows App SDK. On the Windows App SDK being the current set of Windows app development libraries that includes WinUI. ↩
-
Microsoft Learn, Walkthrough: WinUI 3 app with Win32 interop. On the WinUI Window class having been extended to support desktop windows, and on Window in a WinUI 3 desktop app being backed by a Win32 window handle (HWND), so that the handle can be obtained and manipulated with Win32 APIs. ↩
-
Microsoft Learn, Host UWP XAML controls in desktop apps (UWP XAML Islands). On UWP-generation XAML Islands being the mechanism for hosting UWP XAML controls in WPF, WinForms, and C++ desktop apps, and on its use from WPF/WinForms being limited to apps targeting .NET Core 3.x, unsupported on current .NET and .NET Framework. ↩ ↩2
-
Microsoft Learn, DesktopWindowXamlSource Class (Microsoft.UI.Xaml.Hosting). On this being the core class of the Windows App SDK XAML hosting API, able to host WinUI controls in any UI element associated with an HWND, and usable from desktop apps built with WPF, Windows Forms, and Win32 (Windows API). ↩ ↩2
-
Microsoft Learn, Quickstart: Sending a toast notification from the desktop. On sending a toast from a desktop app presupposing a Start menu shortcut with System.AppUserModel.ID set, and on that AppUserModelID having to be passed to the CreateToastNotifier call, without which the toast is not shown. ↩
-
Microsoft Learn, Use app notifications with a .NET app. On using the Windows App SDK’s AppNotificationManager in a WPF/WinForms app requiring a call to Register() after registering the NotificationInvoked handler; Register() automatically performing the COM server registration that launches the app when a notification is clicked, for unpackaged apps; and the prerequisites being adding the Windows App SDK and configuring WinRT API calls. ↩ ↩2
Related Articles
Recent articles sharing the same tags. Deepen your understanding with closely related topics.
Windows App Outsourcing and Custom Software Development: What to Sort Out Before You Ask
Before commissioning Windows app outsourcing or custom software development, here is how to sort out existing software modification, devi...
What Is an OLE Object? — How Embedding and Linking Work and the Pitfalls in Business Documents
An OLE object is what embeds an Excel table in Word. Learn embedding vs. linking, compound files, In-Place Activation, broken links, bloa...
How the Clipboard and Drag & Drop Work — Handling OLE Data Transfer Correctly in Business Apps
Why Excel pastes break and paste fails once the source closes: clipboard formats, delayed rendering, OLE drag and drop, and clipboard his...
Windows Shell Integration Today — Context Menus, File Associations, and What Changed in Windows 11
Why Windows 11 hides context menus behind "Show more options": extension → ProgID → verb basics, classic shell-extension caveats, and the...
A Developer's Strange Love, or: How I Learned to Stop Worrying and Love Windows
Windows is a hassle. But that hassle is the hassle of an OS that has carried real-world business on its back.
Related Topics
These topic pages place the article in a broader service and decision context.
Windows Technical Topics
Topic hub for KomuraSoft LLC's Windows development, investigation, and legacy-asset articles.
ActiveX Migration
Topic page for staged decisions around keeping, wrapping, or replacing COM / ActiveX / OCX assets.
Where This Topic Connects
This article connects naturally to the following service pages.
Windows App Development
We support Windows desktop applications that involve resident processing, device integration, operational logging, and maintainable structure.
Legacy Asset Reuse & Migration Support
We help plan staged migration while continuing to reuse COM / ActiveX / OCX assets, native code, and 32-bit dependencies.
Frequently Asked Questions
Common questions about the topic of this article.
- Is WinRT a managed runtime like .NET?
- No. WinRT (Windows Runtime) is not an execution environment with a virtual machine or a garbage collector; it is an ABI (a binary contract) built on COM. Microsoft's own documentation states plainly that "The Windows Runtime is based on COM", and every WinRT interface requires IInspectable, which derives from IUnknown. Every call is still a COM call through a vtable, resting on reference counting (AddRef/Release) and QueryInterface. The name ".winmd" and the close fit with .NET make it easy to mistake WinRT for a managed environment, but a .winmd is a metadata file that borrows the same physical format as ECMA-335, and the system-provided .winmd files contain no executable code. The reason C# can call it so naturally is that a language projection generates a C# view from that metadata.
- I hear UWP is no longer mainstream. Is there still any point in learning WinRT?
- Yes. UWP, the app model, and WinRT, the API foundation, are two different things. Even after UWP was scaled back, most WinRT APIs continue to be provided in a form that WPF, WinForms, and Win32 desktop apps can call, and many of the capabilities of today's Windows, such as toast notifications, Share, Bluetooth, and OCR, are exposed as WinRT APIs. On top of that, WinUI (Windows App SDK), the native UI framework Microsoft currently recommends, is built on the WinRT ABI. In other words, the machinery of WinRT (IInspectable, .winmd, language projections) is not a UWP relic; it is the very foundation of current Windows app development.
- Can a WPF or WinForms app call WinRT APIs?
- Yes. On .NET 6 or later, all it takes is setting the project's TargetFramework to a TFM with a Windows OS version, such as net8.0-windows10.0.19041.0; the Windows SDK projection assemblies are then referenced and the WinRT APIs in the Windows.* namespaces can be called directly from C#. In C++, add the Microsoft.Windows.CppWinRT NuGet package and use C++/WinRT with C++17 or later. There are three sticking points, however. First, UI classes that assume a CoreWindow, such as pickers and dialogs, need the owner window's HWND passed through IInitializeWithWindow before they are shown (the Share UI's DataTransferManager is the exception: instead of IInitializeWithWindow it uses the dedicated IDataTransferManagerInterop, a separate path that passes the HWND to ShowShareUIForWindow). Second, some APIs, such as the notification history and jump lists, require package identity (MSIX packaging or a package with external location). Third, a thread that handles WinRT objects must be initialized first. In native code, specify the STA/MTA concurrency model with winrt::init_apartment or RoInitialize (in C# WPF/WinForms apps the runtime normally takes care of it). Note that APIs that depend on CoreWindow or ApplicationView themselves cannot be used from desktop apps at all.
- Calling a picker such as FolderPicker from a desktop app throws an exception. Why?
- Because some WinRT picker and dialog classes were designed to display on a UWP CoreWindow. A desktop app has no CoreWindow, so the owner window has to be told explicitly before the object is shown. Concretely, first obtain the owner window's HWND (WinRT.Interop.WindowNative.GetWindowHandle for a WinUI Window, WindowInteropHelper for WPF, the form's Handle property for WinForms), then in C# hand it to the picker with WinRT.Interop.InitializeWithWindow.Initialize. In C++/WinRT, QueryInterface the object for IInitializeWithWindow (as<IInitializeWithWindow>()) and call Initialize(hwnd). Without this initialization, the call throws or fails silently. Note that the newer Windows App SDK pickers (Microsoft.Windows.Storage.Pickers) were redesigned to take a WindowId in the constructor, which makes this initialization pattern unnecessary (but because they are Windows App SDK APIs, a TFM setting alone is not enough: the SDK must be added, and for an unpackaged app the runtime must be deployed and initialized on the target machines).
- I heard WinUI development has been opened up on GitHub. Should existing WPF/WinForms apps be thrown away?
- There is no need to rush. Opening up mainline WinUI development is a statement of direction, that Microsoft is investing seriously in its native framework, not a termination notice for the existing frameworks. WPF and WinForms are still supported as part of .NET today. Split the decision in two. One is whether to migrate the UI framework, a large decision that depends on the size of the screen assets, third-party controls, and the development organization. The other is whether to use WinRT APIs selectively from the existing app, which can start today with a TFM setting. Note that the TFM only makes the APIs callable; toast notifications, for example, require registration in addition to the call (on the classic path, an unpackaged app must register a shortcut with an AppUserModelID — a packaged app does not, because the package identity supplies the AppUserModelID; on the Windows App SDK path, the SDK must be added — and for an unpackaged app the Windows App SDK runtime must also be installed on each target PC — and AppNotificationManager's Register() must be called, while for an app packaged with MSIX the automatic registration by Register() does not work and a COM activator must additionally be declared in Package.appxmanifest). Also, on the Windows App SDK path notifications from a process elevated to administrator are unsupported, and Show fails quietly without throwing — an app that needs elevation should consider separating notifications into a non-elevated process. Even so, if all you want is toast notifications, migrating to WinUI is unnecessary. Note that mixing WinUI controls into existing WPF/WinForms screens (XAML Islands) requires distinguishing generations. The UWP-generation Islands stopped at .NET Core 3.x for WPF/WinForms, and for the WinUI 3 generation the Windows App SDK XAML hosting API (DesktopWindowXamlSource) is usable from WPF/WinForms, but there are no convenient wrapper controls and the implementation burden is heavy, so evaluating it cautiously is the safe course at present.