WinRT es COM — IInspectable, .winmd, proyecciones de lenguaje y por qué WinUI sigue apoyándose en un contrato binario
· Actualizado el: · Go Komura · Windows, WinRT, COM, WinUI, Windows App SDK, Desarrollo para Windows
Historial de revisiones (primera versión, publicada el 29 Aug 2026)
- Primera publicación
Quiere añadir una capacidad nueva de Windows a una aplicación WPF o WinForms. Pero llamar a un selector WinRT lanza una excepción. Luego lee sobre WinUI y parece que habría que reconstruir la interfaz. Esta confusión se aclara una vez que separa el funcionamiento de WinRT de la elección del marco de interfaz.
El punto de partida de este artículo es que WinRT también se apoya en el contrato binario de COM. Microsoft mismo afirma con claridad que «The Windows Runtime is based on COM».1 La tabla de Excel incrustada en Word que vimos en el artículo anterior sobre objetos OLE, y el WinRT y WinUI de hoy, tienen el mismo IUnknown en la raíz.
Aquí fijamos primero los tres elementos que componen WinRT, luego vemos a qué prestar atención al usarlo desde una aplicación de escritorio y, por último, cómo tratar los activos existentes. Los lectores previstos son desarrolladores con experiencia en COM o en el desarrollo de escritorio para Windows, los requisitos previos son Windows 10/11 y .NET 6 o posterior (C#) o C++17 (C++/WinRT), y el nivel de dificultad es intermedio.
1. Primero la conclusión
Hay tres conclusiones que conviene retener.
- WinRT no es un runtime administrado; es un ABI (un contrato binario) construido sobre COM. A ese contrato se añaden
.winmd, que transporta información de tipos, y las proyecciones de lenguaje, que permiten a cada lenguaje llamarlo con naturalidad.1234 - La mayoría de las API WinRT se pueden usar desde aplicaciones WPF, WinForms y Win32 existentes. Pero hay que comprobar tres requisitos previos: pasar un HWND, la identidad de paquete y la inicialización del subproceso.5678
- Una migración completa de interfaz a WinUI y el uso selectivo de API WinRT son decisiones distintas. WinUI también se asienta en el ABI de WinRT, y los activos COM/ActiveX existentes y WinRT pueden coexistir sobre el mismo fundamento.921
A partir de aquí, leer en el orden siguiente hace más fácil seguir las conexiones.
| Lo que quiere saber | Dónde leer |
|---|---|
| ¿Por qué se puede decir «WinRT es COM»? | Secciones 2–3: lo que comparte con COM, e IInspectable |
| ¿Por qué C# y C++ pueden llamarlo con tanta naturalidad? | Secciones 4–5: .winmd y las proyecciones de lenguaje |
| ¿Qué hay que comprobar para usarlo desde una aplicación existente? | Sección 6: HWND, identidad de paquete, apartments |
| ¿Hay que migrar a WinUI? | Secciones 7–9: lo que sostiene a WinUI, la decisión de migración, las condiciones de registro de las notificaciones |
El mapa de conocimiento siguiente sirve para revisar cómo se relacionan los elementos. Si prefiere leer primero la explicación, continúe en orden desde la sección 2.
En el diagrama, una línea continua marca una relación que siempre se cumple y una línea discontinua una relación condicional (las condiciones están en la explicación de cada relación en la página de detalle). La lista completa de relaciones (20 en total, con evidencia y grado de certeza) y las definiciones de los conceptos principales están reunidas en la página de detalle del mapa de conocimiento (en japonés). Datos: JSON-LD / Turtle
2. OLE y WinRT comparten en la raíz el mismo contrato binario
En este blog hemos seguido hasta ahora el mundo de COM clásico: la filosofía de diseño de COM, el modelo de subprocesos STA/MTA, el trato de ActiveX/OCX y los documentos compuestos OLE. Todas son tecnologías de la década de 1990.
WinRT, en cambio, es el fundamento de API introducido con Windows 8 (2012). Hoy proporciona notificaciones toast, Compartir, Bluetooth, OCR y más como las API de los espacios de nombres Windows.*, y es también aquello sobre lo que se apoyan WinUI y el Windows App SDK.10 Lo antiguo y lo nuevo parecen mundos enteramente separados, pero en sustancia son continuos.
Lo que comparten es la promesa de llamar a través de interfaces
Los componentes COM y las clases WinRT exponen ambos su funcionalidad a través de interfaces. Al comparar las interfaces base, la relación queda así.1
| COM clásico | WinRT |
|---|---|
La base de cada interfaz es IUnknown |
La base de cada interfaz es IInspectable, cuya base es IUnknown |
En otras palabras, WinRT no sustituyó COM por un mecanismo ajeno; es una capa más apilada sobre IUnknown. El recuento de referencias, QueryInterface y HRESULT siguen vivos tal como estaban.
La documentación de C++/WinRT también llama a las API WinRT «an evolution of COM» y explica que están diseñadas para consumirse, como API basadas en COM, a través de proyecciones de lenguaje.211
flowchart TB
accTitle: El linaje de COM clásico y WinRT
accDescr: Sobre el fundamento compartido del contrato binario COM hecho de IUnknown y vtables se sostienen, lado a lado, el mundo COM clásico de OLE y ActiveX de la década de 1990 y el mundo de WinRT a partir de 2012, con WinUI y el Windows App SDK encima; los dos no se excluyen, son continuos
base["Contrato binario COM (IUnknown, vtable)"]
base --> classic["COM clásico (OLE, ActiveX, COM propio)"]
base --> winrt["WinRT (IInspectable, .winmd)"]
winrt --> winui["WinUI / Windows App SDK"]
classic -.-> coexist["Pueden coexistir sobre el mismo fundamento"]
winrt -.-> coexist
Figura 1: COM clásico y WinRT no son mundos separados, sino dos generaciones, antigua y nueva, sobre el mismo contrato binario.
Por eso este artículo no es meramente «una introducción a una API nueva». Es un artículo que confirma dónde el conocimiento de COM que ya tiene se aplica en el desarrollo para Windows de 2026.
3. IInspectable encima de IUnknown
El fundamento inalterado y los tres métodos añadidos
La especificación oficial del sistema de tipos de WinRT estipula que cada interfaz WinRT exige implícitamente IInspectable, e IInspectable exige IUnknown. Lo que IUnknown define son, como siempre, los tres métodos QueryInterface, AddRef y Release.12
Encima, IInspectable añade los tres métodos siguientes.13
| Método | Papel |
|---|---|
GetIids |
Devuelve la lista de IID de las interfaces que implementa este objeto |
GetRuntimeClassName |
Devuelve el nombre de tipo WinRT plenamente calificado (como Windows.Storage.StorageFile) como HSTRING |
GetTrustLevel |
Devuelve el nivel de confianza del objeto |
flowchart TB
accTitle: IInspectable asentado sobre IUnknown
accDescr: Cada interfaz WinRT exige IInspectable, e IInspectable exige IUnknown. IUnknown proporciona QueryInterface, AddRef y Release; IInspectable proporciona GetIids, GetRuntimeClassName y GetTrustLevel; y los métodos de cada interfaz WinRT se asientan encima
unk["IUnknown (QI, AddRef, Release)"]
insp["IInspectable (GetIids, nombre de tipo, nivel de confianza)"]
api["Métodos de cada interfaz WinRT"]
unk --> insp
insp --> api
Figura 2: Un objeto WinRT apila los tres métodos de IInspectable sobre los tres métodos de IUnknown, y las API individuales se asientan encima.
A partir de un nombre de tipo se consultan las definiciones de métodos, propiedades y eventos
Lo que importa no es el número de métodos añadidos, sino la capacidad de conectar un nombre de tipo con metadatos.
En COM clásico, el modo estándar de conocer la identidad de un objeto en tiempo de ejecución era «conocer el IID y preguntar mediante QueryInterface». Para los lenguajes de scripting había una vía distinta, IDispatch.
En WinRT, el nombre de tipo obtenido de GetRuntimeClassName se puede resolver frente a los metadatos .winmd de la sección siguiente. De ahí se obtiene la definición completa de sus métodos, propiedades y eventos. La propia especificación dice que poder obtener un nombre de tipo WinRT resoluble mediante metadatos «enables language projection».12
flowchart TB
accTitle: De GetRuntimeClassName a la proyección de lenguaje
accDescr: Cuando el llamador invoca GetRuntimeClassName en un objeto, vuelve el nombre de tipo WinRT plenamente calificado; resolver ese nombre de tipo frente a Windows Metadata da la definición completa del tipo, y eso es lo que hace posible la proyección a cada lenguaje
obj["Objeto WinRT"] --> name["Nombre de tipo (GetRuntimeClassName)"]
name --> md["Resolver la definición de tipo en .winmd"]
md --> proj["La proyección de lenguaje se vuelve posible"]
Figura 3: «El nombre de tipo está disponible en tiempo de ejecución, y el nombre de tipo lleva a los metadatos» es el corazón del mecanismo de WinRT.
Separar «herencia» y «requires» en las interfaces definidas por el usuario
También hay una diferencia que quien está acostumbrado a COM debe notar. El sistema de tipos de WinRT no tiene herencia entre interfaces definidas por el usuario. Una derivación como IFileSystemBindData2 : IFileSystemBindData de COM clásico está deliberadamente ausente; en su lugar se expresa con la declaración «la interfaz A exige la interfaz B (requires)».121
Esto es un asunto distinto de la cadena ABI base IUnknown → IInspectable vista hasta ahora. Esa base permanece como fundamento de cada interfaz WinRT.
Los contratos definidos por el usuario se desplazaron hacia una forma más laxa, que no depende del diseño de herencia de la vtable. Al mismo tiempo, las llamadas en sí siguen pasando por la vtable. Importa no confundir la forma de escribir los contratos con el mecanismo de la llamada.
4. Lo que resolvió .winmd — el infierno de enlace de la información de tipos
La dificultad de COM clásico estaba en «cómo distribuir la información de tipos»
En la práctica de COM clásico, más esfuerzo iba a cómo distribuir la información de tipos que a implementar las interfaces mismas.
| Consumidor | Vía para entregar la información de tipos |
|---|---|
| C++ | Escribir el contrato en IDL, generar encabezados y proxy/stub con MIDL |
| VB6, scripting | Distribuir una biblioteca de tipos (TLB) |
| .NET | Construir un ensamblado de interop aparte |
Una TLB tiene restricciones de tipos orientadas a automatización: cierta información que se puede escribir en IDL no cabe en una TLB. Y como cada lenguaje tiene su propia vía, cuando cualquiera de ellas queda desfasada aparecen desajustes de tipos. Tratamos estas dificultades en el artículo sobre bibliotecas de tipos y dscom y el artículo sobre compatibilidad retroactiva de interfaces DLL y COM.
.winmd es el contrato común que lee cada lenguaje
La respuesta de WinRT es Windows Metadata (.winmd). Las API se describen como metadatos legibles por máquina, y las herramientas y las proyecciones de lenguaje los leen para generar una proyección para cada lenguaje.3
Windows incluye los metadatos de cada API WinRT que proporciona el sistema y también ofrece API que resuelven espacios de nombres y tipos en tiempo de ejecución. El Windows SDK contiene una copia para el tiempo de compilación. Los terceros también pueden participar en la proyección de lenguaje por el mismo mecanismo que las API del sistema, adjuntando un .winmd a su propio componente WinRT.3
Lo que cambió aquí es la forma en que se distribuye la información de tipos. En lugar de TLB, encabezados y ensamblados de interop dispersos por lenguaje, un único .winmd lo leen ahora las proyecciones de cada lenguaje.
Dicho esto, IDL no se ha vuelto innecesario. Cuando se crea un componente WinRT, el contrato se sigue describiendo en IDL (MIDL 3.0, modernizado para WinRT), y el compilador MIDL genera el .winmd.14
flowchart TB
accTitle: El conducto de producción de un componente WinRT
accDescr: El contrato de un componente WinRT se sigue escribiendo en IDL, es decir, MIDL 3.0, y el compilador MIDL lo compila en un .winmd. Lo que se distribuye es este .winmd, y la herramienta de proyección de cada lenguaje, como cppwinrt.exe o cswinrt.exe, lo lee para generar la proyección. Lo que se sustituyó no es IDL, sino la forma en que se distribuye la información de tipos
idl2["Escribir el contrato (IDL, MIDL 3.0)"]
midl2["Compilador MIDL"]
winmd4[".winmd (información de tipos distribuida)"]
proj3["Generar la proyección de cada lenguaje"]
idl2 --> midl2
midl2 --> winmd4
winmd4 --> proj3
Figura 4: El punto de entrada del contrato (IDL) sigue en servicio; el punto de salida, la información de tipos distribuida, se unificó en .winmd.
El mismo formato de archivo que .NET, pero no un runtime administrado
El formato físico de un .winmd usa la especificación ECMA-335, la misma que un ensamblado CLR. Las reglas sobre qué combinaciones de datos son válidas difieren, no obstante, de las de los ensamblados CLR. Tomar prestado el formato y necesitar el CLR para ejecutarse son dos cosas distintas.3
Aquí hay que leer por separado las API del sistema y los componentes de terceros.
| Objeto | Relación entre el .winmd y la implementación |
|---|---|
| API WinRT que proporciona el sistema | El .winmd es metadatos puros, sin código ejecutable. La implementación vive en DLL nativas del sistema operativo, y no se necesita el CLR para ejecutarla |
| Componentes WinRT de terceros | El .winmd también puede contener código de implementación. En un componente administrado (escrito en C#) que contiene MSIL, se requiere el runtime de .NET correspondiente para ejecutarlo |
Como un .winmd parece un ensamblado de .NET al abrirlo en una herramienta, es fácil caer en la idea falsa «WinRT = administrado». Pero el contenido de un .winmd que proporciona el sistema es un contrato para interfaces COM.3
flowchart TB
accTitle: Separación del .winmd y la implementación
accDescr: Un .winmd es un archivo de metadatos que toma prestado el formato físico ECMA-335; los que proporciona el sistema son contratos sin código ejecutable, y la implementación de las API WinRT que proporciona el sistema vive en DLL nativas del sistema operativo. Por esta separación, aunque un .winmd parezca un ensamblado de .NET, no se necesita el CLR para ejecutar las API WinRT del sistema (el .winmd de un componente administrado de terceros contiene MSIL y exige el runtime de .NET)
winmd3[".winmd (contrato, formato ECMA-335)"]
impl["DLL nativa del sistema operativo (implementación)"]
winmd3 -.->|"Proporcionado por el sistema: sin código"| note3["Las API del sistema no necesitan CLR"]
impl --> note3
winmd3 ---|"Las definiciones de tipo corresponden a la implementación"| impl
Figura 5: En las API WinRT que proporciona el sistema, el .winmd es el contrato y la implementación una DLL nativa del sistema operativo. El formato parece .NET, pero la ejecución es COM nativo.
flowchart TB
accTitle: Información de tipos de COM clásico frente a .winmd
accDescr: En COM clásico la vía de información de tipos estaba partida por lenguaje, de IDL a encabezados de C++, de bibliotecas de tipos a VB6 y scripting, de ensamblados de interop a .NET, lo que causaba incoherencias, mientras que en WinRT un único .winmd lo leen en común las proyecciones de cada lenguaje
subgraph old["COM clásico: una vía por lenguaje"]
idl["IDL a encabezados de C++"]
tlb["TLB a VB6 y scripting"]
ia["Ensamblado de interop a .NET"]
end
winmd[".winmd (metadatos únicos)"]
winmd --> all["Leído en común por la proyección de cada lenguaje"]
Figura 6: .winmd plegó la dispersión de la información de tipos por lenguaje en «un archivo de metadatos que todos leen».
Las restricciones no desaparecieron; se desplazaron a un eje de proyectabilidad
Para quien conoce COM clásico, en una frase: .winmd es «la biblioteca de tipos, rehecha». Piénselo como el papel que una TLB intentaba desempeñar, rediseñado desde el principio como única fuente de verdad compartida por todos los lenguajes, sobre el formato acreditado ECMA-335, y su lugar queda claro.
Las restricciones, no obstante, no se han ido. Las restricciones orientadas a automatización de las TLB fueron sustituidas por las restricciones del sistema de tipos propio de WinRT, cuyo eje es «poder proyectarse con seguridad a cada lenguaje». La ausencia de herencia entre interfaces definidas por el usuario, vista en la sección 3, es un ejemplo.12
En consecuencia, un contrato COM/IDL existente no necesariamente se puede llevar a WinRT tal cual. Puede que haya que rediseñar la API.
flowchart TB
accTitle: Sustitución de las restricciones de biblioteca de tipos por las del sistema de tipos WinRT
accDescr: Las restricciones de expresividad orientadas a automatización de la TLB (biblioteca de tipos) no las eliminó .winmd, sino que las sustituyó por las restricciones del sistema de tipos propio de WinRT, cuyo eje es la proyección segura a cada lenguaje. Un ejemplo es la ausencia de herencia de interfaces definidas por el usuario; un contrato COM/IDL existente no necesariamente se puede retomar tal cual, y puede que haya que rediseñar la API
tlb["Restricciones de TLB (orientadas a automatización)"] -->|"Sustituidas por"| wrt["Restricciones del sistema de tipos WinRT (eje de proyectabilidad)"]
wrt -.-> ex["Ejemplo: sin herencia definida por el usuario"]
wrt -.-> re["Los contratos COM existentes pueden necesitar rediseño"]
Figura 7: Las restricciones de TLB no «desaparecieron»; se sustituyeron por otras restricciones cuyo eje es la proyectabilidad a cada lenguaje.
5. C++/WinRT y C#/WinRT son proyecciones, no «contenedores»
Mostrar el contrato común a cada lenguaje en su forma natural
Una vez que hay un contrato común en forma de .winmd, la vista para cada lenguaje se puede generar automáticamente con herramientas. Esto es una proyección de lenguaje (language projection). Expone las API WinRT en el idioma de cada lenguaje y oculta los detalles de COM, ofreciendo una experiencia de programación natural para ese lenguaje.411
Las proyecciones que Microsoft admite actualmente son las dos siguientes.4
| Proyección | Lo que genera | Características |
|---|---|---|
| C++/WinRT | cppwinrt.exe genera encabezados de proyección C++ a partir de .winmd |
Una proyección basada en archivos de encabezado en C++17 estándar. No se necesitan extensiones de lenguaje como las de C++/CX; el sucesor de C++/CX y WRL11 |
| C#/WinRT (CsWinRT) | cswinrt.exe genera código C# a partir de .winmd y lo convierte en un ensamblado de interop |
La proyección para .NET. Una cadena de herramientas independiente del runtime1516 |
La historia del lado de C# es un poco confusa, así que la desenredamos. Hasta .NET Core 3.x, el runtime de .NET tenía compatibilidad integrada para consumir WinRT/winmd. En .NET 5 se retiró esa compatibilidad integrada y el papel pasó a C#/WinRT.16 Las API WinRT no dejaron de ser usables; cambió dónde se trata la proyección.
Hoy, cuando se indica en C# un TFM como net8.0-windows10.0.19041.0, los ensamblados de proyección del Windows SDK se referencian automáticamente.10
flowchart TB
accTitle: Generación de las proyecciones para cada lenguaje a partir de .winmd
accDescr: Cuando cppwinrt.exe lee el .winmd único, genera encabezados de proyección C++17; cuando cswinrt.exe lo lee, genera un ensamblado de interop C#; y las API WinRT se exponen de una forma que sigue el idioma de cada lenguaje
winmd2[".winmd (el contrato de API)"]
winmd2 --> cpp["cppwinrt.exe a encabezados C++17"]
winmd2 --> cs["cswinrt.exe a ensamblado de interop C#"]
cpp --> cppcode["Invocable en el idioma de C++"]
cs --> cscode["Invocable en el idioma de C#"]
Figura 8: Una proyección no es un contenedor escrito a mano; las herramientas la generan de forma mecánica a partir del contrato (.winmd).
Aunque se genere, la llamada en sí sigue siendo COM
Este artículo dice «proyección» en lugar de «contenedor» para subrayar que es un mecanismo derivado de forma mecánica de los metadatos, no una capa de traducción que las personas mantienen API por API. Cualquier API presente en el .winmd se puede hacer usable desde el principio en cada lenguaje admitido.
Y desde el lenguaje que se llame, lo que ocurre bajo la proyección es la misma llamada COM. Por ejemplo, cuando se escribe await picker.PickSingleFolderAsync() en C#, la proyección tiende un puente entre el IAsyncOperation de WinRT y el mundo del Task de .NET. Aun así, en el lado ABI se usan llamadas a métodos de vtable y HRESULT. Por eso los errores aparecen en forma de excepciones COM (un HRESULT como 0x80070005).
flowchart TB
accTitle: Las capas del código C# a la API WinRT del sistema operativo
accDescr: El código C# o C++ de la aplicación se convierte, a través de la proyección de lenguaje, en llamadas ABI de WinRT, es decir, llamadas de vtable sobre IInspectable, y llega a la API WinRT implementada por el sistema operativo. La proyección solo oculta los detalles de COM; la llamada en sí es COM
code["Código de la aplicación (C#, C++)"]
proj2["Proyección de lenguaje"]
abi["ABI de WinRT (vtable de IInspectable)"]
os["Implementación del sistema operativo de la API WinRT"]
code --> proj2
proj2 --> abi
abi --> os
Figura 9: Lo que oculta la proyección son los «detalles» de COM, no COM en sí.
Conocer esta estructura permite partir un incidente en dos capas. Un desajuste de versión del código generado o un TFM que falta es la capa de proyección; los HRESULT, los apartments y el recuento de referencias son la capa ABI. En esta última, la experiencia de desarrollo COM sirve de inmediato.
6. Dónde se atascan las aplicaciones de escritorio — HWND, identidad, apartments
Primero separar «poder referenciar la API» de «las condiciones para que funcione»
La mayoría de las API WinRT se pueden llamar desde aplicaciones de escritorio WPF, WinForms y Win32.5 El punto de entrada para llamarlas difiere entre C# y C++ como sigue.10
| Entorno | Configuración inicial |
|---|---|
| C#/.NET 6 o posterior | Establecer TargetFramework en un TFM con versión de sistema operativo Windows, como net8.0-windows10.0.19041.0 |
| C++ | Añadir el paquete NuGet Microsoft.Windows.CppWinRT y usar C++/WinRT con C++17 o posterior |
Poder referenciar las API, no obstante, no significa que cada API funcione tal cual. Compruebe los tres puntos siguientes. Las condiciones de registro de las notificaciones toast se tratan por separado en la sección 9.
| Qué comprobar | Remedio principal |
|---|---|
| ¿Necesita una ventana en la que mostrarse? | Pasar un HWND mediante el interop COM que corresponda a la interfaz |
| ¿Necesita identidad de paquete? | Conceder identidad con MSIX o un paquete con ubicación externa |
| ¿Está el subproceso inicializado para WinRT? | En código nativo, inicializar indicando STA/MTA. En C# el runtime suele encargarse |
Punto de atasco 1: pasar un HWND a una interfaz como los selectores
Algunos selectores, cuadros de diálogo y la interfaz de Compartir esperan un CoreWindow de UWP como superficie de visualización. Una aplicación de escritorio no tiene CoreWindow, así que el HWND de la ventana propietaria debe pasarse de forma explícita antes de mostrar el objeto.6
El punto de entrada que se usa para selectores y similares es una interfaz COM llamada IInitializeWithWindow. Hereda de IUnknown y proporciona una ventana propietaria a los objetos WinRT usados en aplicaciones de escritorio.17
En C#, primero obtenga el HWND según el marco de interfaz en uso.186
| Ventana propietaria | Cómo obtener el HWND |
|---|---|
Una Window de WinUI |
WinRT.Interop.WindowNative.GetWindowHandle |
| Una ventana WPF | WindowInteropHelper |
| Un formulario WinForms | La propiedad Handle del formulario |
A continuación, páselo al selector con WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd) y solo entonces muéstrelo. En C++/WinRT, obtenga el objeto como as<IInitializeWithWindow>() y luego llame a Initialize(hwnd). Si se omite la inicialización, lanza o falla en silencio.1819
Inicializar un objeto WinRT moderno mediante QueryInterface a una interfaz COM clásica: que este puente sea la práctica oficial es otro lugar en el que WinRT muestra que es COM.
La interfaz de Compartir usa otra interfaz. Para DataTransferManager no se usa IInitializeWithWindow. Se usa el IDataTransferManagerInterop dedicado y se pasa el HWND a ShowShareUIForWindow.6
Los selectores más nuevos del Windows App SDK son otra vía distinta. Microsoft.Windows.Storage.Pickers recibe un WindowId en el constructor, así que el patrón InitializeWithWindow es innecesario. No son, no obstante, API que se puedan usar solo con un ajuste de TFM. Además de añadir el Windows App SDK, una aplicación sin empaquetar necesita que el runtime se despliegue e inicialice en las máquinas de destino.19
sequenceDiagram
accTitle: Pasos para mostrar un selector desde una aplicación de escritorio
accDescr: Tras crear un selector, una aplicación de escritorio que llama PickSingleFolderAsync tal cual obtiene una excepción o un fallo silencioso, así que primero hay que obtener el HWND de la ventana propietaria y pasarlo mediante Initialize de IInitializeWithWindow antes de mostrar el selector
participant A as Aplicación de escritorio
participant P as Selector (WinRT)
A->>P: Crear
alt Mostrar sin pasar un HWND
A->>P: PickSingleFolderAsync
P-->>A: Excepción o fallo silencioso
else Pasar primero el HWND
A->>P: Establecer el HWND mediante IInitializeWithWindow
A->>P: PickSingleFolderAsync
P-->>A: Se muestra el selector
end
Figura 10: Cubrir el hueco de «no hay CoreWindow en el escritorio» pasando un HWND de forma explícita es la práctica oficial.
Punto de atasco 2: algunas API exigen identidad de paquete
Algunas API WinRT, como el historial de notificaciones toast (ToastNotificationHistory), las listas de accesos directos y los destinos de Compartir, solo funcionan en aplicaciones que tienen identidad de paquete (aplicaciones empaquetadas). Llamarlas desde una aplicación sin empaquetar distribuida con un instalador tradicional falla.7
Hay dos remedios. Empaquetar la aplicación con MSIX, o usar un «paquete con ubicación externa» (un sparse package), que concede una identidad y conserva el instalador existente.20 Qué API exigen identidad se puede comprobar en la lista oficial.7
flowchart TB
accTitle: Dos vías hacia las API que exigen identidad de paquete
accDescr: Llamar una API WinRT que exige identidad de paquete desde una aplicación sin empaquetar falla, así que dé a la aplicación identidad de paquete empaquetándola con MSIX o con un paquete con ubicación externa, que solo concede el identificador y conserva el instalador existente
need["Querer usar una API que exige identidad"]
need --> m1["Empaquetar con MSIX"]
need --> m2["Paquete con ubicación externa"]
m1 --> id2["Obtener identidad de paquete"]
m2 --> id2
id2 -.-> okid["El historial de notificaciones etc. funciona"]
Figura 11: El remedio para las API que exigen identidad es una elección entre dos: «MSIX» o «instalador existente + concesión de un identificador».
Punto de atasco 3: comprobar la inicialización del subproceso y STA/MTA
Un subproceso que trata objetos WinRT debe inicializarse para WinRT de antemano. En código nativo, use RoInitialize o winrt::init_apartment e indique el modelo de concurrencia, STA o MTA. En aplicaciones C# WPF/WinForms, el runtime suele encargarse de la inicialización.8
RoInitialize es el punto de entrada de la generación WinRT dentro del mismo marco que CoInitializeEx de COM. La propia documentación de CoInitialize indica llamar a RoInitialize o Windows::Foundation::Initialize en su lugar cuando se usa Windows Runtime.21
El subproceso de interfaz de WPF/WinForms es un STA; los objetos de interfaz deben tocarse en el subproceso de interfaz; las esperas bloqueantes en un STA invitan a interbloqueos. El pensamiento tratado en el artículo de STA/MTA se traslada sin cambios a las API WinRT.
Algunas API no se pueden usar ni con HWND e identidad en su sitio
Los remedios hasta aquí conciernen a API que tienen un punto de entrada para uso de escritorio. Las API que dependen de CoreWindow o ApplicationView en sí no se pueden usar desde aplicaciones de escritorio. En ese caso, en lugar de añadir inicialización, busque una API alternativa.57
flowchart TB
accTitle: Ramificación de los puntos de atasco al llamar API WinRT desde el escritorio
accDescr: Primero confirmar que el subproceso está inicializado para WinRT (en código nativo, indicar STA/MTA de forma explícita con RoInitialize o similar; en C#, el runtime suele encargarse). Luego, si la API WinRT que se quiere llamar es una interfaz que presupone un CoreWindow, pasar un HWND mediante IInitializeWithWindow o usar los selectores nuevos; si exige identidad de paquete, conceder un identificador con MSIX o un paquete con ubicación externa; las API que dependen de CoreWindow o ApplicationView en sí no se pueden usar en el escritorio, así que hay que buscar una API alternativa; y el gran resto de API se pueden llamar tal cual solo con el ajuste de TFM o C++/WinRT
pre["Inicialización del subproceso"] --> q{"¿Qué tipo de API es?"}
pre -.-> auto["Nativo: explícito; C#: suele ser automático"]
q -->|"Interfaz"| h["HWND o los selectores nuevos"]
q -->|"Identidad exigida"| p2["MSIX o conceder un identificador"]
q -->|"Depende de CoreWindow en sí"| x["Buscar una API alternativa"]
q -->|"Todo lo demás"| ok3["Invocable tal cual"]
Figura 12: Con la inicialización del subproceso como requisito previo (explícita en código nativo, en C# normalmente dejada al runtime), los puntos de atasco caen en tres familias, cada una con un remedio tipo.
flowchart TB
accTitle: Correspondencia de la inicialización del subproceso entre COM y WinRT
accDescr: En COM clásico un subproceso se inicializa con CoInitializeEx indicando STA o MTA, mientras que en WinRT se inicializa con RoInitialize indicando el mismo modelo de concurrencia STA o MTA. La documentación de CoInitialize también indica llamar a RoInitialize al usar WinRT, y el concepto de apartment es compartido
com3["COM clásico: CoInitializeEx"] --> apt["Apartment (STA / MTA)"]
wrt["WinRT: RoInitialize"] --> apt
apt -.-> rule["El subproceso de interfaz es STA; cuidado con las esperas"]
Figura 13: El nombre de la API de inicialización cambió, pero se sigue usando el mismo concepto de apartment.
7. Lo que sostiene a WinUI — el desarrollo a la vista no cambia el contrato
Pasar al desarrollo a la vista no es un cambio del contrato binario
En el verano de 2025, Microsoft anunció oficialmente, como enfoque por fases, su política de llevar el desarrollo principal de WinUI a la vista en GitHub. Hay cuatro etapas: aumentar la frecuencia de actualización del espejo, hacer posibles las compilaciones locales, aceptar contribuciones de la comunidad una vez que las pruebas estén en su sitio y, por último, hacer de GitHub el hogar principal del desarrollo.22
A la fecha de publicación de este artículo (finales de agosto de 2026), la documentación oficial también afirma con claridad que WinUI está «built in the open». El avance cotidiano de la ingeniería se puede seguir ahora en el repositorio público.9
Los desarrolladores que han seguido los relevos generacionales de WinForms a WPF a UWP a WinUI sienten con naturalidad «¿el marco vuelve a cambiar?». Pero los marcos de interfaz que no han dejado de cambiar y el fundamento debajo hay que mirarlos por separado. Win32 y COM, y desde 2012 el ABI de WinRT, se han quedado en el mismo sitio.
Las ventanas WinUI también las sostiene un HWND
WinUI es un conjunto de API WinRT proporcionado como parte del Windows App SDK.923 Su Microsoft.UI.Xaml.Window es una ventana sostenida por un HWND, que sustituye el modelo de ventana basado en CoreWindow de la generación UWP. El tutorial oficial de interop también empieza por obtener el identificador de ventana.24
En otras palabras, una aplicación WinUI es una aplicación Win32 en la que un árbol de objetos que sigue el contrato IInspectable se ejecuta sobre una ventana HWND. El conocimiento de QueryInterface, recuento de referencias y apartments adquirido a través de COM, y el conocimiento de HWND y bucles de mensajes adquirido a través de Win32, se aplican ambos de inmediato a la resolución de problemas de WinUI.
flowchart TB
accTitle: Relevos generacionales de los marcos de interfaz y el fundamento inalterado
accDescr: Los marcos de interfaz WinForms, WPF, XAML de UWP y WinUI han encadenado generaciones, pero WinForms y WPF se asientan directamente sobre el fundamento de Win32 y COM, mientras que el XAML de UWP y WinUI se asientan sobre el mismo fundamento de Win32 y COM a través del ABI de WinRT. Lo que cambió de generación es la capa superior; el contrato debajo no ha cambiado
gen["Relevos generacionales de los marcos de interfaz"]
gen --> f1["WinForms, WPF"]
gen --> f2["XAML de UWP"]
gen --> f3["WinUI (actual)"]
f2 --> abi3["ABI de WinRT (desde 2012)"]
f3 --> abi3
f1 --> stable["Fundamento inalterado (Win32 + COM)"]
abi3 --> stable
Figura 14: Lo que subió y bajó fue la capa del marco. WinForms y WPF se asientan directamente sobre Win32 + COM; UWP y WinUI se asientan sobre el mismo fundamento a través del ABI de WinRT.
flowchart TB
accTitle: Las capas que sostienen una aplicación WinUI
accDescr: El XAML y los controles de WinUI se proporcionan como parte del Windows App SDK, se ejecutan sobre el ABI de WinRT, es decir, el contrato IInspectable, y debajo está el fundamento de COM y el HWND de Win32. Bajo los relevos generacionales de los marcos de interfaz, este contrato subyacente no ha cambiado
ui["WinUI (XAML, controles)"]
sdk["Windows App SDK"]
abi2["ABI de WinRT (IInspectable)"]
base2["COM + Win32 (HWND)"]
ui --> sdk
sdk --> abi2
abi2 --> base2
Figura 15: Debajo de WinUI está el ABI de WinRT, y más abajo aún COM clásico y Win32. Solo cambió el apilado; el fundamento es el mismo.
Mezclar con XAML Islands: comprobar las restricciones de cada generación
La estrategia de «mezclar solo controles WinUI en pantallas WPF/WinForms existentes» hay que evaluarla por separado para cada generación de XAML Islands.
| Generación | Situación al usarla desde WPF/WinForms |
|---|---|
| XAML Islands de generación UWP | Existen controles contenedor del Windows Community Toolkit. Pero las versiones WPF/WinForms se detuvieron en la generación .NET Core 3.x y no se admiten en el .NET actual25 |
| Generación WinUI 3 | Se puede hospedar desde WPF, WinForms y Win32 con DesktopWindowXamlSource del Windows App SDK. Pero no hay controles contenedor cómodos como los de la generación UWP, y recae en usted la carga de implementación y validación de tratar la API de hospedaje de forma directa26 |
Si planea una mezcla gradual, es más seguro no presuponer solo la mezcla a nivel de control. Organizar el plan en torno al uso de API WinRT a nivel de funcionalidad (sección 6) o a una separación a nivel de pantalla o de proceso es el enfoque más sólido a fecha de 2026.
8. Implicaciones para las aplicaciones de negocio — la migración completa y el uso selectivo son problemas distintos
«WinRT es un mundo nuevo y separado, así que usarlo significa tirar los activos existentes y reconstruir.» Esta idea falsa, que aparece en proyectos de desarrollo a medida, se disuelve una vez que se parte en dos.
Los activos COM existentes y WinRT pueden coexistir
A una aplicación WPF que usa automatización COM de Excel, hospeda controles ActiveX y llama a componentes COM internos se le pueden añadir API WinRT. No es ninguna acrobacia especial, porque se combinan funciones sobre el mismo fundamento COM.
Para las notificaciones toast, por ejemplo, establezca el TFM para referenciar la API y cumpla las condiciones de registro para mostrar notificaciones. Para que esto último no se olvide, la sección 9 lo expone vía por vía. En C++/WinRT, las interfaces WinRT y COM clásicas se pueden tratar con los mismos mecanismos, winrt::com_ptr y winrt::implements.21
Estimar por separado la renovación de interfaz y la adición de funciones
«¿Migrar la interfaz por completo a WinUI?» y «¿Usar API WinRT solo donde haga falta?» son decisiones que difieren en escala, duración y riesgo.
Una migración completa de interfaz es un problema de elección de marco que depende de los activos de pantalla, los controles de terceros y la organización de desarrollo. Tratamos esa decisión en Cómo elegir entre WinForms, WPF y WinUI. El uso selectivo de API WinRT, en cambio, es una mejora pequeña que se puede empezar hoy en una aplicación existente.
Para un proyecto que solo quiere notificaciones toast, no hace falta estimar una migración completa de interfaz. A la inversa, no hace falta aplazar el uso de API WinRT «porque no vamos a migrar a WinUI».
flowchart TB
accTitle: La migración completa y el uso selectivo son decisiones distintas
accDescr: Una migración completa de interfaz a WinUI es una gran decisión de elección de marco que depende de los activos de pantalla y de la organización, mientras que el uso selectivo de API WinRT es una decisión pequeña que se puede añadir hoy a una aplicación WPF o WinForms existente con un ajuste de TFM o similar; considere las dos por separado sin confundirlas
goal["Querer usar funciones nuevas de Windows"]
goal --> big["Migración completa de interfaz (decisión grande)"]
goal --> small["Uso selectivo de API WinRT (decisión pequeña)"]
big -.-> dep["Depende de los activos de pantalla y de la organización"]
small -.-> today["Se puede añadir a la aplicación existente hoy"]
Figura 16: Hay dos caminos hacia «funciones nuevas», y confundirlos desvía tanto la estimación como la decisión.
9. Tabla de decisión — la versión WinRT de conservar, envolver, reemplazar
Elegir solo los cambios que cada situación necesita
Como extensión de la tabla de decisión de ActiveX, aquí hay una guía de decisión por situación del lado WinRT.
| Situación | Recomendación | Motivo |
|---|---|---|
| Querer usar API WinRT como toast, Compartir o Bluetooth desde WPF/WinForms | Uso selectivo mediante un ajuste de TFM (o añadiendo C++/WinRT) | Invocable hoy sin migración de interfaz. Pero la interfaz de Compartir pasa por IDataTransferManagerInterop (sección 6), y para toast vea el complemento bajo la tabla106 |
| Excepción o fallo silencioso en un selector o un cuadro de diálogo | Pasar un HWND mediante IInitializeWithWindow. Para código nuevo, los selectores nuevos basados en WindowId (exige añadir el Windows App SDK) |
La práctica oficial de cubrir el diseño basado en CoreWindow con un HWND619 |
| El historial de notificaciones, las listas de accesos directos y similares no funcionan | Conceder identidad de paquete con MSIX o un paquete con ubicación externa | Las API que exigen identidad presuponen empaquetado720 |
| Activos COM/ActiveX/OLE existentes | No descartarlos. Decidir conservar, envolver o reemplazar por activo | No son excluyentes de WinRT; coexisten sobre el mismo fundamento (sección 8) |
| Interfaz de una aplicación de escritorio nueva | Evaluar WinUI como primer candidato (WPF sigue vigente) | El desarrollo a la vista aclaró la dirección de inversión. Debajo está el ABI de WinRT229 |
| Mezclar controles WinUI en pantallas WPF/WinForms existentes | Evaluar con cautela, previendo la carga de implementación de la falta de contenedores | Los Islands de generación UWP se detuvieron en .NET Core 3.x; la generación WinUI 3 solo ofrece la API de hospedaje2526 |
| Un plan que presupone «WinRT terminó junto con UWP» | Corregir el presupuesto | WinRT es el fundamento de API actual, invocable desde el escritorio5 |
Las notificaciones toast no aparecen solo con un ajuste de TFM
El ajuste que hace la API invocable y el registro que hace aparecer una notificación son dos cosas distintas. Cuando «la compilación funciona pero no aparece ninguna notificación», compruebe no solo el código de llamada, sino la vía en uso, la forma de empaquetado y si el proceso está elevado.
Al usar el ToastNotificationManager clásico
Para una aplicación sin empaquetar y sin identidad de paquete, el requisito previo es registrar un acceso directo del menú Inicio con un AppUserModelID (AUMID) asignado. Sin él no se pueden mostrar toasts.27
Una aplicación empaquetada con MSIX o similar no necesita este registro manual, porque la identidad de paquete proporciona el AUMID.
Al usar AppNotificationManager del Windows App SDK
En esta vía recomendada actualmente se exigen añadir el Windows App SDK y llamar a Register() al arrancar. Más allá, las condiciones se ramifican según la forma de empaquetado.28
| Forma de empaquetado | Qué comprobar además |
|---|---|
| Sin empaquetar | El runtime del Windows App SDK debe desplegarse en cada PC de destino. Register() realiza el registro del servidor COM |
| Empaquetada con MSIX | El registro automático de Register() no funciona. Declare un activador COM en Package.appxmanifest |
Además, en la vía del Windows App SDK las notificaciones desde un proceso elevado a administrador no están admitidas. Show falla en silencio sin lanzar. En una aplicación que necesita elevación, plantee separar las notificaciones en un proceso no elevado.28
Los detalles de implementación en torno al registro se tratan en la guía de implementación de iconos de bandeja y notificaciones.
flowchart TB
accTitle: Las dos vías de las notificaciones toast y el registro que cada una necesita
accDescr: Para mostrar una notificación toast desde una aplicación de escritorio, la vía clásica ToastNotificationManager se ramifica según si la aplicación está empaquetada: una aplicación sin empaquetar debe pasar por el registro de un acceso directo del menú Inicio con un AppUserModelID asignado, mientras que para una empaquetada la identidad de paquete proporciona el AppUserModelID. En la vía AppNotificationManager del Windows App SDK, además de añadir el SDK y llamar a Register al arrancar, una aplicación sin empaquetar debe desplegar el runtime en cada PC de destino y una aplicación empaquetada con MSIX debe declarar un activador COM en el manifiesto antes de poder seguir. La vía del Windows App SDK se ramifica además según si el proceso está elevado: las notificaciones desde un proceso elevado a administrador no están admitidas y Show falla en silencio sin lanzar, así que en esta vía una notificación solo se muestra para un proceso no elevado, y una aplicación que necesita elevación separa las notificaciones en un proceso no elevado
want["Querer mostrar un toast"]
want --> c1["Clásico: ToastNotificationManager"]
want --> c2["WASDK: AppNotificationManager"]
c1 --> q1{"¿Empaquetada?"}
q1 -->|"No"| s1["Registrar un acceso directo AUMID"]
q1 -->|"Sí"| s2["La identidad proporciona el AUMID"]
c2 --> r2["Añadir el SDK + Register()"]
r2 --> q2{"¿Empaquetada?"}
q2 -->|"No"| s3["Desplegar el runtime"]
q2 -->|"Sí"| s4["Declarar COM en el manifiesto"]
s1 --> shown["Se muestra la notificación"]
s2 --> shown
s3 --> elev{"¿Proceso elevado?"}
s4 --> elev
elev -->|"No"| shown
elev -->|"Sí"| fail["No admitido: Show falla en silencio"]
fail -.-> comp["Separar las notificaciones en un proceso no elevado"]
Figura 17: En cualquiera de las dos vías, la notificación aparece solo después de satisfacer los requisitos previos de la forma de empaquetado. En la vía del Windows App SDK, incluso con todos los requisitos previos en su sitio, no se muestra nada desde un proceso elevado.
Por último, volver a conservar, envolver, reemplazar
¿Necesita una función nueva, o quiere renovar la interfaz en sí? Volver a esta pregunta permite decidir por separado sobre el uso de WinRT y sobre el reemplazo de los activos existentes.
flowchart TB
accTitle: Flujo de decisión sobre cómo encajan los activos existentes y WinRT
accDescr: Partiendo de una aplicación de escritorio existente, un flujo de decisión por pasos: si no se necesita una función nueva de Windows, conservar; si se necesita una función, envolver con uso selectivo de API WinRT (atentos a los puntos de atasco de la sección 6 de HWND, identidad de paquete e inicialización del subproceso); y solo cuando la interfaz en sí necesite renovarse, plantear reemplazarla por WinUI
start["Aplicación de escritorio existente"] --> q2{"¿Qué se necesita?"}
q2 -->|"El estado actual basta"| keep2["Conservar (mantener tal cual)"]
q2 -->|"Una función nueva"| wrap2["Envolver (uso selectivo de API WinRT)"]
q2 -->|"Renovación de interfaz"| rep["Reemplazar (evaluar WinUI)"]
wrap2 -.-> note2["Atención a HWND, identidad, inicialización (sección 6)"]
Figura 18: La misma estructura «conservar, envolver, reemplazar» que la tabla de decisión de ActiveX se aplica de inmediato del lado WinRT.
10. Resumen
WinRT no es un entorno de ejecución nuevo, cortado de COM. Es un fundamento de API que combina el contrato binario de COM con metadatos y proyecciones a cada lenguaje.
| Elemento | Su papel según este artículo |
|---|---|
IUnknown e IInspectable |
Construidos sobre QueryInterface y el recuento de referencias, añadiendo tres métodos que devuelven el nombre de tipo y más |
.winmd |
El contrato compartido por cada lenguaje, a partir del cual un nombre de tipo lleva a una definición. Toma prestado el formato ECMA-335, pero los que proporciona el sistema no contienen código ejecutable |
| C++/WinRT y C#/WinRT | Generan una API para cada lenguaje a partir del contrato. Desde .NET 5, la proyección de C# es una cadena de herramientas independiente del runtime |
Aunque la superficie sea una API C# o C++ natural, debajo hay llamadas COM a través de vtables y HRESULT. Por eso entender el paso de HWND en el escritorio, la identidad de paquete y STA/MTA con la inicialización del subproceso hace más fácil aislar los incidentes de WinRT.
El esfuerzo de llevar el desarrollo principal de WinUI a la vista se anunció en el verano de 2025 como un enfoque por fases. A la fecha de publicación de este artículo la documentación oficial también dice «built in the open», pero el contrato debajo, IUnknown → IInspectable, no ha cambiado.229
La conclusión para las aplicaciones de negocio es la misma. Los activos COM/ActiveX existentes y WinRT pueden coexistir. No confundir una migración completa de interfaz con el uso selectivo de API WinRT es lo que protege la precisión de las estimaciones y las decisiones.
Los documentos compuestos de la década de 1990 vistos en el artículo OLE y el WinUI de hoy se sostienen sobre el mismo IUnknown. Conocer COM no es meramente «estar versado en lo legado». Es poder leer el fundamento de Windows hoy.
Artículos relacionados
- Qué es COM — por qué el diseño de Windows COM sigue siendo hermoso hoy
- Qué son COM, ActiveX y OCX - Diferencias y relación explicadas
- Conocimientos básicos de STA/MTA en COM - El modelo de subprocesos y cómo evitar los bloqueos
- ActiveX / OCX: cómo tratarlos hoy — tabla de decisión para mantener, envolver o reemplazar
- Cómo elegir entre WinForms, WPF y WinUI: tabla de decisión práctica
- What Is an OLE Object? — How Embedding and Linking Work, and the Pitfalls in Business Documents
- Cómo usar una DLL de .NET 8 desde VBA con tipado - Exposición COM y TLB con dscom
Áreas de consultoría relacionadas
KomuraSoft LLC se ocupa del desarrollo de componentes COM y del mantenimiento, la modificación y el reemplazo de aplicaciones de negocio para Windows que incluyen activos COM. Puede consultarnos desde la etapa en la que el contenido de este artículo es exactamente el tema en juego: «queremos notificaciones toast y selectores sin dejar WPF», «llamar a una API WinRT se detuvo con una excepción», «queremos decidir si migrar a WinUI o aprovechar los activos existentes».
- Desarrollo de aplicaciones para Windows
- Desarrollo de componentes COM
- Consultoría técnica y revisión de diseño
- Contacto
Referencias
-
Microsoft Learn, Consume COM components with C++/WinRT. Sobre programar en COM a través de interfaces en lugar de objetos; que esto también vale entre bastidores de las API WinRT, que son «an evolution of COM»; y tratar WinRT y COM clásico en el mismo estilo con el puntero inteligente COM winrt::com_ptr. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Windows Metadata (WinMD) files. Sobre que las API WinRT se describen en metadatos legibles por máquina llamados .winmd, usados por herramientas y proyecciones de lenguaje; que Windows incluye los metadatos de cada API WinRT que proporciona el sistema y ofrece API de resolución; que los terceros pueden participar en la proyección de lenguaje con el mismo formato; y que el formato físico es la especificación ECMA-335 (la misma que los ensamblados CLR), siendo los archivos WinMD que proporciona el sistema metadatos puros. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Windows Runtime (WinRT) language projections. Sobre que las proyecciones de lenguaje exponen las API WinRT en el idioma de cada lenguaje; que .winmd define las API WinRT y las proyecciones lo leen; y que las dos proyecciones que admite Microsoft son C++/WinRT (C++17 o posterior) y C#/WinRT (.NET). ↩ ↩2 ↩3
-
Microsoft Learn, WinRT APIs callable from a desktop app. Sobre que la mayoría de las API WinRT son usables desde aplicaciones de escritorio .NET y C++ nativo, siendo excepciones las clases diseñadas específicamente para UWP, como CoreDispatcher, CoreWindow y ApplicationView. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Display WinRT UI objects that depend on CoreWindow. Sobre que algunos selectores, ventanas emergentes y cuadros de diálogo dependen de CoreWindow; que CoreWindow no se admite en aplicaciones de escritorio; que las clases que implementan IInitializeWithWindow (o el equivalente IDataTransferManagerInterop) permiten establecer el HWND de la ventana propietaria antes de mostrarlas; y los pasos para WinUI 3, WPF y WinForms respectivamente. ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, WinRT APIs not supported in desktop apps. Sobre las dos familias de API WinRT inutilizables en aplicaciones de escritorio, las que dependen de funciones de interfaz exclusivas de UWP y las que exigen identidad de paquete (como ToastNotificationHistory y JumpList), y sobre que estas últimas solo se admiten en aplicaciones empaquetadas con MSIX. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, RoInitialize function (roapi.h). Sobre que RoInitialize inicializa el subproceso actual para Windows Runtime con el modelo de concurrencia indicado (RO_INIT_SINGLETHREADED/RO_INIT_MULTITHREADED); que cada subproceso que activa y opera sobre objetos WinRT necesita una inicialización previa; y que una indicación contradictoria en un subproceso ya inicializado como MTA da RPC_E_CHANGED_MODE. ↩ ↩2
-
Microsoft Learn, WinUI 3. Sobre que WinUI es el marco de interfaz nativo recomendado para aplicaciones de escritorio Windows nuevas, que se proporciona como parte del Windows App SDK, que se ejecuta en Windows 10 versión 1809 y posterior, y que se desarrolla a la vista. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Call Windows Runtime APIs in desktop apps. Sobre que indicar un TFM con versión de sistema operativo Windows (como net10.0-windows10.0.22621.0) en .NET 6 o posterior hace que se referencie el paquete de destino del Windows SDK de modo que se puedan llamar API WinRT, y que C++ usa C++/WinRT con el paquete NuGet Microsoft.Windows.CppWinRT y C++17 o posterior. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Introduction to C++/WinRT. Sobre que C++/WinRT es una proyección de lenguaje en C++17 enteramente estándar y moderno, implementada como biblioteca basada en archivos de encabezado; que es el sucesor recomendado de C++/CX y WRL; que WinRT se basa en API COM y está diseñado para accederse a través de proyecciones de lenguaje; que las proyecciones ocultan los detalles de COM; y que cppwinrt.exe genera encabezados de proyección a partir de .winmd. ↩ ↩2 ↩3
-
Microsoft Learn, The Windows Runtime (WinRT) type system. Sobre que cada interfaz WinRT exige implícitamente IInspectable e IInspectable exige IUnknown; que IUnknown define QueryInterface, AddRef y Release; los tres métodos que añade IInspectable, GetIids, GetRuntimeClassName y GetTrustLevel; que GetRuntimeClassName devuelve un nombre de tipo resoluble mediante metadatos, lo que habilita la proyección de lenguaje; y que la herencia entre interfaces definidas por el usuario está ausente del sistema de tipos de WinRT y se expresa con requires en su lugar. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, IInspectable interface (inspectable.h). Sobre que IInspectable proporciona funcionalidad exigida por cada clase WinRT, hereda de IUnknown y tiene los tres métodos GetIids, GetRuntimeClassName y GetTrustLevel. ↩
-
Microsoft Learn, Introduction to Microsoft Interface Definition Language 3.0. Sobre que MIDL 3.0 es una sintaxis concisa y moderna para declarar tipos WinRT, y que los contratos WinRT se siguen escribiendo en IDL, generando el compilador MIDL Windows Metadata (.winmd). ↩
-
Microsoft Learn, C#/WinRT. Sobre que cswinrt.exe, incluido en el paquete NuGet C#/WinRT, procesa .winmd para generar código C# y compilarlo en un ensamblado de interop, y que esto se sitúa de la misma forma que C++/WinRT generando encabezados para C++. ↩
-
Microsoft Learn, Built-in support for WinRT is removed from .NET. Sobre que la compatibilidad integrada con Windows Runtime se retiró de .NET en .NET 5 y se trasladó a la cadena de herramientas CsWinRT. ↩ ↩2
-
Microsoft Learn, IInitializeWithWindow interface (shobjidl_core.h). Sobre que esta es la interfaz para proporcionar una ventana propietaria a objetos WinRT usados en aplicaciones de escritorio, que hereda de IUnknown y que tiene un método Initialize(HWND). ↩
-
Microsoft Learn, Use WinRT COM interop classes in .NET. Sobre que algunos objetos WinRT, como selectores de archivos y cuadros de diálogo, necesitan un HWND antes de funcionar en una aplicación de escritorio, y que las clases C# seguras respecto al tipo WinRT.Interop.WindowNative y WinRT.Interop.InitializeWithWindow permiten la inicialización sin llamadas QueryInterface escritas a mano. ↩ ↩2
-
Microsoft Learn, Tutorial: Open files and folders with pickers in WinUI. Sobre que los Windows.Storage.Pickers clásicos deben inicializarse con un HWND antes de mostrarlos cuando se usan en una aplicación de escritorio (WinUI 3), o de lo contrario lanzan o fallan en silencio; que las aplicaciones de escritorio WinUI 3 no tienen CoreWindow; y que los selectores nuevos del Windows App SDK (Microsoft.Windows.Storage.Pickers) reciben un WindowId en el constructor, de modo que el patrón InitializeWithWindow es innecesario. ↩ ↩2 ↩3
-
Microsoft Learn, Features that require package identity. Sobre que algunas funciones de Windows y API WinRT exigen identidad de paquete en tiempo de ejecución, y que una identidad se puede obtener no solo distribuyendo un paquete MSIX, sino también con un paquete con ubicación externa. ↩ ↩2
-
Microsoft Learn, CoInitialize function (objbase.h). Sobre que CoInitialize inicializa la biblioteca COM como STA; que se espera que las aplicaciones nuevas llamen a CoInitializeEx; y que hay que llamar a RoInitialize o Windows::Foundation::Initialize en su lugar cuando se usa Windows Runtime. ↩
-
GitHub, WinUI: Now Developing in the Open (microsoft/microsoft-ui-xaml Discussion #10700). El anuncio oficial de finales de julio de 2025. Sobre el enfoque por fases para abrir el repositorio de WinUI (aumentar la frecuencia de actualización del espejo, permitir compilaciones locales, aceptar contribuciones de la comunidad una vez que las pruebas estén en su sitio y, por último, hacer de GitHub el hogar principal del desarrollo). ↩ ↩2 ↩3
-
Microsoft Learn, Windows App SDK. Sobre que el Windows App SDK es el conjunto actual de bibliotecas de desarrollo de aplicaciones para Windows que incluye WinUI. ↩
-
Microsoft Learn, Walkthrough: WinUI 3 app with Win32 interop. Sobre que la clase Window de WinUI se amplió para admitir ventanas de escritorio, y que Window en una aplicación de escritorio WinUI 3 la sostiene un identificador de ventana Win32 (HWND), de modo que el identificador se puede obtener y manipular con API Win32. ↩
-
Microsoft Learn, Host UWP XAML controls in desktop apps (UWP XAML Islands). Sobre que los XAML Islands de generación UWP son el mecanismo para hospedar controles XAML de UWP en aplicaciones de escritorio WPF, WinForms y C++, y que su uso desde WPF/WinForms se limita a aplicaciones que tienen como destino .NET Core 3.x, no admitido en el .NET actual ni en .NET Framework. ↩ ↩2
-
Microsoft Learn, DesktopWindowXamlSource Class (Microsoft.UI.Xaml.Hosting). Sobre que esta es la clase central de la API de hospedaje XAML del Windows App SDK, capaz de hospedar controles WinUI en cualquier elemento de interfaz asociado a un HWND, y usable desde aplicaciones de escritorio construidas con WPF, Windows Forms y Win32 (API de Windows). ↩ ↩2
-
Microsoft Learn, Quickstart: Sending a toast notification from the desktop. Sobre que enviar un toast desde una aplicación de escritorio presupone un acceso directo del menú Inicio con System.AppUserModel.ID establecido, y que ese AppUserModelID hay que pasarlo a la llamada CreateToastNotifier, sin el cual el toast no se muestra. ↩
-
Microsoft Learn, Use app notifications with a .NET app. Sobre que usar AppNotificationManager del Windows App SDK en una aplicación WPF/WinForms exige una llamada a Register() tras registrar el controlador NotificationInvoked; que Register() realiza automáticamente, para aplicaciones sin empaquetar, el registro del servidor COM que inicia la aplicación al hacer clic en una notificación; y que los requisitos previos son añadir el Windows App SDK y configurar las llamadas a API WinRT. ↩ ↩2
Artículos relacionados
Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.
El estado actual de la integración con el shell de Windows — el menú contextual, la asociación de archivos y los cambios de Windows 11
Explica por qué el menú contextual de Windows 11 se oculta en «Mostrar más opciones», la asociación de archivos (extensión→ProgID→verb), ...
Externalización y desarrollo por encargo de una app Windows: qué aclarar antes de solicitarlo
Antes de encargar la externalización o el desarrollo por encargo de una app Windows, estos son los puntos a aclarar: revisión de software...
Time Travel Debugging — Grabar y rebobinar los errores que no se reproducen en aplicaciones de larga duración
Un error que aparece una vez al mes deja en el volcado de memoria solo el resultado. Grabe y rebobine la ejecución con Time Travel Debugg...
¿Qué es un objeto OLE? — Incrustación, vinculación y las trampas de los documentos empresariales
La función que incrusta una tabla de Excel en Word es, en realidad, un objeto OLE. El artículo explica en clave práctica la diferencia en...
Cómo funcionan el portapapeles y arrastrar y soltar — tratar correctamente la transferencia de datos OLE en aplicaciones empresariales
Una tabla de Excel se deshace al pegarla y deja de pegarse al cerrar el origen: el portapapeles coloca el mismo contenido en varios forma...
Temas relacionados
Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.
Temas técnicos de Windows
Portal sobre desarrollo de Windows, investigación de fallos y aprovechamiento de activos existentes.
Migración de ActiveX
Decisiones para conservar, encapsular o sustituir componentes COM / ActiveX / OCX.
Servicios relacionados con este tema
El artículo está directamente relacionado con los siguientes servicios.
Desarrollo de aplicaciones para Windows
Aplicaciones empresariales, integración de dispositivos y herramientas de comunicación, de los requisitos al desarrollo.
Reutilización y migración de activos existentes
Reutilización y migración de activos COM / ActiveX / OCX y dependencias de 32 o 64 bits.
Preguntas frecuentes
Preguntas habituales en las consultas sobre el tema del artículo.
- ¿Es WinRT un runtime administrado como .NET?
- No. WinRT (Windows Runtime) no es un entorno de ejecución con una máquina virtual o un recolector de basura; es un ABI (un contrato binario) construido sobre COM. La propia documentación de Microsoft afirma con claridad que "The Windows Runtime is based on COM", y cada interfaz WinRT exige IInspectable, que deriva de IUnknown. Cada llamada sigue siendo una llamada COM a través de una vtable, basada en el recuento de referencias (AddRef/Release) y QueryInterface. El nombre ".winmd" y el encaje cercano con .NET hacen fácil tomar WinRT por un entorno administrado, pero un .winmd es un archivo de metadatos que toma prestado el mismo formato físico que ECMA-335, y los archivos .winmd que proporciona el sistema no contienen código ejecutable. Que C# pueda llamarlo con tanta naturalidad se debe a que una proyección de lenguaje genera una vista C# a partir de esos metadatos.
- Oigo que UWP ya no es lo principal. ¿Sigue teniendo sentido aprender WinRT?
- Sí. UWP, el modelo de aplicación, y WinRT, el fundamento de API, son dos cosas distintas. Incluso después de que UWP se recortara, la mayoría de las API WinRT siguen ofreciéndose de forma que las aplicaciones de escritorio WPF, WinForms y Win32 puedan llamarlas, y muchas de las capacidades de Windows hoy, como las notificaciones toast, Compartir, Bluetooth y OCR, se exponen como API WinRT. Además, WinUI (Windows App SDK), el marco de interfaz nativo que Microsoft recomienda actualmente, está construido sobre el ABI de WinRT. En otras palabras, la maquinaria de WinRT (IInspectable, .winmd, proyecciones de lenguaje) no es un vestigio de UWP; es precisamente el fundamento del desarrollo actual de aplicaciones para Windows.
- ¿Puede una aplicación WPF o WinForms llamar a API WinRT?
- Sí. En .NET 6 o posterior basta con establecer el TargetFramework del proyecto en un TFM con versión de sistema operativo Windows, por ejemplo net8.0-windows10.0.19041.0; entonces se referencian los ensamblados de proyección del Windows SDK y las API WinRT de los espacios de nombres Windows.* se pueden llamar directamente desde C#. En C++, añada el paquete NuGet Microsoft.Windows.CppWinRT y use C++/WinRT con C++17 o posterior. Hay, no obstante, tres puntos de atasco. Primero, las clases de interfaz que presuponen un CoreWindow, como selectores y cuadros de diálogo, necesitan que el HWND de la ventana propietaria se pase mediante IInitializeWithWindow antes de mostrarlas (la excepción es DataTransferManager de la interfaz de Compartir: en lugar de IInitializeWithWindow usa el IDataTransferManagerInterop dedicado, una vía distinta que pasa el HWND a ShowShareUIForWindow). Segundo, algunas API, como el historial de notificaciones y las listas de accesos directos, exigen identidad de paquete (empaquetado MSIX o un paquete con ubicación externa). Tercero, un subproceso que trata objetos WinRT debe inicializarse primero. En código nativo, indique el modelo de concurrencia STA/MTA con winrt::init_apartment o RoInitialize (en aplicaciones C# WPF/WinForms el runtime suele encargarse). Tenga en cuenta que las API que dependen de CoreWindow o ApplicationView en sí no se pueden usar en absoluto desde aplicaciones de escritorio.
- Llamar a un selector como FolderPicker desde una aplicación de escritorio lanza una excepción. ¿Por qué?
- Porque algunas clases WinRT de selector y de cuadro de diálogo se diseñaron para mostrarse en un CoreWindow de UWP. Una aplicación de escritorio no tiene CoreWindow, así que hay que indicar explícitamente la ventana propietaria antes de mostrar el objeto. En concreto, primero obtenga el HWND de la ventana propietaria (WinRT.Interop.WindowNative.GetWindowHandle para una Window de WinUI, WindowInteropHelper para WPF, la propiedad Handle del formulario para WinForms) y, en C#, páselo al selector con WinRT.Interop.InitializeWithWindow.Initialize. En C++/WinRT, haga QueryInterface del objeto a IInitializeWithWindow (as<IInitializeWithWindow>()) y llame a Initialize(hwnd). Sin esta inicialización, la llamada lanza o falla en silencio. Tenga en cuenta que los selectores más nuevos del Windows App SDK (Microsoft.Windows.Storage.Pickers) se rediseñaron para recibir un WindowId en el constructor, lo que hace innecesario este patrón de inicialización (pero, como son API del Windows App SDK, un ajuste de TFM no basta: hay que añadir el SDK y, para una aplicación sin empaquetar, desplegar e inicializar el runtime en las máquinas de destino).
- He oído que el desarrollo de WinUI se ha abierto en GitHub. ¿Hay que tirar las aplicaciones WPF/WinForms existentes?
- No hay que precipitarse. Abrir el desarrollo principal de WinUI es una declaración de dirección, de que Microsoft invierte en serio en su marco nativo, no un aviso de fin de los marcos existentes. WPF y WinForms siguen admitidos hoy como parte de .NET. Divida la decisión en dos. Una es si migrar el marco de interfaz, una decisión grande que depende del tamaño de los activos de pantalla, los controles de terceros y la organización de desarrollo. La otra es si usar API WinRT de forma selectiva desde la aplicación existente, lo que puede empezar hoy con un ajuste de TFM. Tenga en cuenta que el TFM solo hace las API invocables; las notificaciones toast, por ejemplo, requieren un registro además de la llamada (en la vía clásica, una aplicación sin empaquetar debe registrar un acceso directo con AppUserModelID — una empaquetada no, porque la identidad de paquete proporciona el AppUserModelID; en la vía del Windows App SDK hay que añadir el SDK — y para una aplicación sin empaquetar también instalar el runtime del Windows App SDK en cada PC de destino — y llamar a Register() de AppNotificationManager, mientras que en una aplicación empaquetada con MSIX el registro automático de Register() no funciona y además hay que declarar un activador COM en Package.appxmanifest). Además, en la vía del Windows App SDK las notificaciones desde un proceso elevado a administrador no están admitidas, y Show falla en silencio sin lanzar — una aplicación que necesita elevación debería plantearse separar las notificaciones en un proceso no elevado. Aun así, si solo quiere notificaciones toast, no hace falta migrar a WinUI. Tenga en cuenta que mezclar controles WinUI en pantallas WPF/WinForms existentes (XAML Islands) exige distinguir generaciones. Los Islands de generación UWP se detuvieron en .NET Core 3.x para WPF/WinForms, y para la generación WinUI 3 la API de hospedaje XAML del Windows App SDK (DesktopWindowXamlSource) es usable desde WPF/WinForms, pero no hay controles contenedor cómodos y la carga de implementación es alta, así que evaluarlo con cautela es por ahora el camino seguro.
Perfil del autor
Página de presentación del autor del artículo.
Go Komura
Representante de KomuraSoft LLC
Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.