WinUI 3 al Desnudo: De los Handles de Windows al Renderizado Moderno

Explora la historia y arquitectura de WinUI 3. Desde los límites de handles en Win32 y el drama de WPF hasta el moderno renderizado con DirectX y async/await.

Este texto ha sido generado por Gemini 2.5/3.1 a partir del audio del autor. El contenido y las ideas son íntegramente del autor; la redacción ha sido asistida por IA.


¿Qué pasa, patáticas mías? Aquí vuestro reportero maricharachero para continuar con la serie sobre WinRT. Hoy nos sumergimos de lleno en WinUI 3, y os adelanto que todo lo que os conté sobre WinRT sigue siendo perfectamente válido, porque WinUI no es más que la aplicación práctica de toda esa tecnología para construir las interfaces gráficas de las aplicaciones modernas.

Un Viaje al Pasado: Win32, Handles y los Primeros Frameworks

Para entender WinUI, hay que retroceder. Frameworks como Windows Forms eran, en esencia, una capa relativamente fina que envolvía los componentes nativos de Windows: el botón, el cuadro de edición, el checkbox… todo. Nos abstraían del famoso «handle de ventana» o HWND.

Aquí viene la anécdota para los más jóvenes. En el viejo Win32, todo, absolutamente todo, era una ventana. Por eso se llama Windows. Un simple campo de texto era una ventana con su propio handle. Esto tenía una consecuencia brutal: Windows tenía un número limitado de handles gráficos, unos 65.536.

¿Qué significaba esto? Que si abrías una aplicación gráficamente intensa como el viejo Borland C++ Builder, con sus toolbars llenas de iconitos (cada uno una ventana), y luego abrías otra similar, podías agotar los handles. El resultado era que los gráficos no se veían, o peor, la aplicación reventaba. La única solución era cerrar programas o, en el peor de los casos, reiniciar Windows. ¡Ah, los maravillosos años 90!

La Guerra Civil de Microsoft: La Tumultuosa Llegada de WPF

Luego llegó WPF (Windows Presentation Foundation), que en sus primeras versiones seguía dependiendo de este sistema de handles. Aunque introdujo una capa muy potente encima, con XAML y un sistema de layout que nos ahorraba los cálculos manuales que sufríamos en MFC, su nacimiento fue… complicado.

Cuando Microsoft decidió que la interfaz de Visual Studio debía rehacerse en WPF, se topó con un muro. El equipo de Visual Studio, de los pocos sin pelos en la lengua, les dijo sin rodeos que aquello era «una puta mierda pinchada en un puto palo de mierda». Y tenían razón. Yo mismo, en media hora, reporté 7 u 8 bugs gravísimos en la primera versión. Esas demos espectaculares de Microsoft eran humo; en la práctica, nada funcionaba.

Tras una buena ración de «arrojo de boñigas de mierda» interno, Microsoft se puso las pilas y arregló WPF. La primera versión de Visual Studio con WPF fue lenta y tosca, pero las siguientes mejoraron hasta el punto de que, a partir de .NET Framework 4.7, funciona francamente bien.

El Problema de Estar Atado al Sistema Operativo

¿Cuál fue la solución mágica para arreglar WPF? Integrar su motor directamente en el núcleo de Windows. Las DLLs que antes venían con el instalador de .NET Framework ahora formaban parte del propio sistema operativo. Esto solucionó los problemas de rendimiento, pero creó uno nuevo y gigantesco.

Cada vez que necesitaban corregir un bug o añadir una mejora a WPF, tenían que lanzar una actualización completa de Windows. Esto era insostenible. ¿Recordáis esas actualizaciones de .NET Framework que tardaban una eternidad en instalarse? Probablemente se debían a esto. El sistema estaba recompilando y optimizando el código para tu máquina específica, un proceso lento y pesado.

Nace WinUI 3: La Independencia del Framework

La necesidad de agilidad, especialmente con la llegada de las apps de la tienda de Windows (UWP) y el problema de una ABI (Application Binary Interface) fija, obligó a Microsoft a cambiar de estrategia. Usando la misma tecnología de componentes COM que vimos en WinRT, decidieron sacar el motor de UI fuera del núcleo de Windows.

Así nació WinUI 3. Es, en esencia, la evolución de WPF desacoplada del sistema operativo. Este proceso, como era de esperar, no fue trivial y trajo nuevas ineficiencias. Al tener que comunicar entre el espacio de usuario (anillo 3) y el kernel (anillo 0), se introdujo una latencia que lo hizo más lento en ciertos aspectos.

El Motor de WinUI 3: Dos Hilos y Mucho DirectX

La arquitectura de WinUI 3 es un batiburrillo interesante de tecnologías: Win32, WinRT y, sobre todo, DirectX. Por ahí leí una descripción que sonaba a «método cuántico arquitectónico indispensable», pero en cristiano, se basa en un modelo de dos hilos para lograr su rendimiento.

Por un lado, está el hilo de la UI (UI-Thread), que se encarga de procesar la lógica, ejecutar el código XAML y componer el árbol visual de lo que se va a mostrar. Aquí entran en juego optimizaciones clásicas de Windows, como las áreas de recorte, que evitan redibujar lo que no es necesario.

Una vez compuesto el «qué» se va a pintar, se le pasa el trabajo al hilo del compositor (Compositor-Thread). Este segundo hilo se comunica directamente con el hardware a través de DirectX para renderizar la escena en la pantalla. Esta separación es la clave de su rendimiento en aplicaciones gráficamente intensas, ya que el dibujado no bloquea la lógica de la aplicación.

Adiós al Invoke: Async/Await al Rescate de la UI

Un principio sagrado en el desarrollo de UI es mantener el hilo principal lo más libre posible para que la aplicación siempre responda. Antiguamente, si estabas en un hilo secundario y querías actualizar un texto en la pantalla, tenías que usar Invoke o BeginInvoke para enviar esa orden de vuelta al hilo principal.

WinUI 3, junto con las versiones modernas de C#, simplifica esto drásticamente gracias a async/await. Ahora, desde un método asíncrono, puedes modificar un elemento de la interfaz directamente. El compilador y el framework se encargan por arte de magia de gestionar los hilos y las máquinas de estado para que todo funcione sin bloquear la UI. Se acabó el Invoke.

La Experiencia del Desarrollador: Sin Diseñador Visual pero con Hot Reload

Un cambio importante para los que venimos del mundo clásico es que en WinUI 3 no hay un diseñador visual de XAML en Visual Studio. Si abres un fichero y no ves la vista previa, ya sabes que estás en el mundo moderno.

El flujo de trabajo ahora se basa en ejecutar la aplicación y modificar el XAML en caliente usando la función Hot Reload. Le das a recargar y los cambios aparecen al instante en la ventana. Bueno, en teoría. A veces funciona y a veces no, sobre todo si tocas cosas del patrón MVVM, obligándote a volver al método clásico de cerrar, recompilar y ejecutar.

Conclusión

Y este ha sido nuestro viaje por las entrañas de WinUI 3. Como veis, es el resultado de décadas de evolución, dramas internos en Microsoft y una búsqueda constante por un framework de UI potente y desacoplado. Mucho de esto es historia que he vivido, y otra parte es conocimiento que le he sonsacado a Gemini para complementar mis batallitas.

Por cierto, no he querido dejarme fuera la caótica gestión de versiones de .NET Core, con sus múltiples ABIs incompatibles y esa manía de dejar instaladas 50 versiones en el sistema. Es otro de esos detalles que forman parte de la historia de esta tecnología. ¡Espero que esta explicación os haya aclarado el panorama y os haya entretenido! ¡Adiós, mis pataticas!

De COM a WinRT: La tortuosa evolución de la interoperabilidad en Windows

Analizamos la evolución desde los objetos COM y sus ficheros IDL hasta WinRT, pasando por las simplificaciones de C# y los desafíos de la interoperabilidad en C++.

Este texto ha sido generado por Gemini 2.5/3.1 a partir del audio del autor. El contenido y las ideas son íntegramente del autor; la redacción ha sido asistida por IA.


En el audio anterior os conté todo el paripé que había que hacer para acceder a un objeto COM (Common Object Model). Hoy vamos a profundizar en esa historia, viendo cómo hemos evolucionado desde un sistema engorroso hasta las promesas de WinRT.

El engorroso mundo del COM clásico

Cuando querías usar un objeto COM, el compilador de C# se enfrentaba a un proceso complejo. Curiosamente, en su día, Borland tenía esto bastante mejor resuelto, aunque también fallaba más. Cada objeto COM venía con un fichero IDL (Interface Definition Language) que describía qué hacía, cómo cargarlo y cómo traducir las llamadas desde tu código al código nativo del objeto, que generalmente estaba en C++.

El compilador procesaba este fichero IDL y generaba una serie de clases «envolventes» para facilitar el manejo de la interfaz. Sin embargo, mucho trabajo seguía siendo manual. Tenías que llamar a IUnknown, gestionar punteros y liberar memoria explícitamente. Era un sistema lento, basado en punteros a punteros, y si se te olvidaba liberar algo, los problemas eran horrorosos. Digamos que era una solución a medio hacer.

Una de las funcionalidades interesantes era la integración de menús. Si tu aplicación Win32 o MFC tenía un menú «Archivo», el menú «Archivo» de un componente Office, como Word, podía fusionarse con el tuyo, mostrando tus opciones y las de Office. Un detalle curioso que demuestra el nivel de integración que se buscaba.

C# llega al rescate (parcialmente)

Con la llegada de C# y el framework .NET, las cosas se simplificaron enormemente. Cualquier clase de C# (o Visual Basic .NET) puede marcarse como COMVisible, y automáticamente está disponible para ser usada por otras aplicaciones. De C# a C# la integración es transparente y casi instantánea. Al añadir la referencia a tu proyecto, el sistema de reflexión te permite ver y usar todas las clases y métodos públicos sin esfuerzo.

El lenguaje se encarga de toda la fontanería por debajo, gestionando los IUnknown y las referencias por ti. Esto es una capa de abstracción potentísima que permite que tu componente funcione sin importar si se ejecuta en un servidor remoto, en un procesador ARM o en un x86. En teoría, claro, porque en la práctica siempre surgen problemas.

El problema persistente con C++ nativo

Pero, ¿qué pasaba si querías acceder a ese objeto COM hecho en C# desde C++ nativo? Volvíamos al punto de partida. Había que generar un fichero (en este caso, WinMD) que, a través de una herramienta como MIDL.exe, creaba las clases de envoltorio para C++. Y aquí empezaban los dolores de cabeza.

MIDL.exe estaba lleno de bugs. A veces, si usabas un objeto en C# que la herramienta no conocía, generaba mal las clases. El código compilaba, el IntelliSense no daba pistas, pero en tiempo de ejecución, al llamar a esa parte mal definida, el programa reventaba. Era algo muy parecido a los clásicos «Runtime Error» de Visual Basic. Si además querías usarlo desde Pascal, un COBOL moderno o JavaScript, el proceso era el mismo: crear envoltorios y cruzar los dedos.

WinRT: ¿La solución definitiva?

Aquí es donde entra en juego WinRT. Es el siguiente paso evolutivo, una interfaz COM gestionada a través de ficheros WinMD que define lo que se conoce como una ABI (Application Binary Interface) estática y bien definida. Esto es fundamental en tecnologías como .NET Core. El IUnknown sigue existiendo, pero el sistema es mucho más moderno.

Para C++, WinRT se presenta como un conjunto de plantillas en ficheros de cabecera, lo que significa que no hay sobrecarga de código. Microsoft promete que es súper rápido, que la adaptación es automática y que la liberación de memoria también es automática. Se acabaron los punteros manuales. Es, en esencia, la siguiente generación de COM, diseñada para ser súper fácil de usar.

Un paréntesis técnico: El ‘Name Mangling’

Para entender por qué una ABI estática es tan importante, hay que hablar del name mangling en C++. C++ no es un lenguaje de objetos en su ejecución final; simula la orientación a objetos pasando un puntero oculto (this) a las funciones. Si tienes un método HazPityCoreDeVoyne en la clase A y otro con el mismo nombre en la clase B, ¿cómo sabe el compilador a cuál llamar?

Lo que hace es cambiar los nombres internamente a algo como HazPityCoreDeVoyneClaseA y HazPityCoreDeVoyneClaseB. Este proceso, el name mangling, varía con cada compilador e incluso con cada versión. Si creas una DLL con Visual Studio 2017, puede que no sea compatible con una compilada con Visual Studio 2019 o con el compilador de Intel. WinRT soluciona esto al establecer una ABI fija y universal, simplificando radicalmente la interoperabilidad.

El escepticismo de siempre: El historial de Microsoft

La idea de WinRT es absolutamente genial. El problema, para mí, es que me da mucho repelús. Es el siguiente paso en la saga de las APIs para la tienda de Windows. Primero tuvimos Silverlight, luego las apps de la tienda, después UWP… y ahora WinRT. Todas las anteriores llegaron llenas de bugs y problemas.

Microsoft es famosa por tener ideas maravillosas y luego tardar años en pulir la implementación. Justo cuando algo se vuelve estable, deciden cambiarlo. Prometieron una API estática con .NET Core y creo que ya van por la tercera versión de esa promesa «para siempre». Conociéndolos, aunque digan que van a mejorar WinUI y WinRT, me temo que será más de lo mismo.

Conclusión: Una idea brillante, una implementación incierta

WinRT está diseñado para funcionar con .NET y se puede usar desde C++ nativo simplemente incluyendo una cabecera. La sintaxis puede ser un poco barroca, pero es funcional. Es importante aclarar que WinRT no sustituye a Win32; es una capa de abstracción muy rápida que corre por encima y termina haciendo llamadas a Win32.

La idea es chula, pero la práctica ya la veremos. O mejor dicho, ya se verá, porque yo personalmente no creo que lo use. Por lo menos, ahora ya sé qué es y cómo funciona. Ya veremos si esta vez Microsoft consigue una implementación robusta desde el principio.

Y con esto cerramos el capítulo de WinRT. En el próximo audio hablaremos sobre WinUI, pero eso será otra historia. ¡No olvidéis sospechosos, habitualizaros! Adiós.

2006IIP – Qué es WinRT (I de II)

Descubre los orígenes de WinRT explorando su predecesor, el complejo y lento modelo COM de Microsoft. Una historia de desarrollo, errores y reinicios.

Este texto ha sido generado por Gemini 2.5/3.1 a partir del audio del autor. El contenido y las ideas son íntegramente del autor; la redacción ha sido asistida por IA.


¿Qué pasa, patáticas mías? Aquí vuestro reportero maricharachero de barrio, Sésamo. Sí, hoy toca un nuevo saludo, «patáticas», que no sé cuánto durará, quizás un par de programas o quizás este sea el único. Hoy nos adentramos en un tema técnico que surgió del audio anterior, donde hablaba de las promesas de Microsoft para mejorar su sistema operativo.

El plan es hablar de WinRT y WinUI 3, pero son dos bestias muy diferentes, así que iremos por partes. Empezaremos por la de más bajo nivel: WinRT. Como no lo tenía del todo claro, le pedí a Gemini que me preparara un informe en profundidad, y ahora que lo entiendo, os lo voy a explicar. Pero para entender qué es WinRT, primero tenemos que hacer un viaje en el tiempo.

Un viaje al pasado: Corva, DDE y el nacimiento de COM

Si sois desarrolladores con solera, de los viejos como yo, seguramente os suenen términos como Corva, DDE o las aplicaciones COM. Hubo una época en la que se pusieron de moda los sistemas de objetos, una idea adelantada a su tiempo que buscaba que las librerías y componentes de software se autodescribieran.

La teoría era fantástica: tener una «caja negra» universal que un programador pudiera usar sin importar la plataforma (Solaris, Windows, Linux), la arquitectura o cualquier otro detalle. La funcionalidad debía ser agnóstica. Sin embargo, en la práctica, era una chorrada, porque como programador, sigues necesitando saber qué hacen esas librerías para poder usarlas correctamente.

Microsoft contraatacó a estas tendencias primero con DDE y luego con su propio protocolo: COM (Common Object Model). La idea de COM también estaba adelantada a su tiempo, pero a diferencia de otras, esta sí que funcionaba, aunque con matices importantes que veremos más adelante.

Entendiendo COM con una metáfora: el portátil y el dock

Para que entendáis el concepto de COM, olvidemos el código por un momento y pensemos en una metáfora. Imagina que tu aplicación es un «dock» de escritorio y un objeto COM es un ordenador portátil. Cuando conectas el portátil al dock, de repente el sistema se expande: tienes más puertos USB, el cargador se conecta, quizás accedes a un disco duro externo SCSI o a un almacenamiento PCI Express.

El dock (la aplicación) ya sabe qué interfaces tiene el portátil (el objeto COM). Sabe que tiene un conector de carga, puertos USB y una salida de vídeo, y sabe cómo conectarse a ellos, alimentarlos y activarlos. A partir de una interfaz base llamada IUnknown (Interfaz Desconocida), la aplicación puede «preguntarle» al objeto qué es capaz de hacer y solicitar una instancia de sus funcionalidades.

Lo realmente potente de este modelo es la flexibilidad. Puedes tener cinco modelos de portátiles diferentes, pero si todos tienen los conectores en el mismo sitio, puedes intercambiarlos. Un día usas tu portátil con un procesador i3 para tareas sencillas. Al día siguiente, necesitas potencia bruta, así que sacas el i3 y conectas un i9 con 600 TB de RAM. Los periféricos del dock siguen siendo los mismos, pero el «objeto» que los alimenta ha cambiado por completo.

Esta idea, nacida en el mundo de los «objetos de negocio», tenía otra ventaja brutal: el objeto COM no tenía por qué estar en tu ordenador. Podía estar ejecutándose en un servidor remoto. Tu aplicación simplemente llamaba al objeto a través de su identificador y, según la configuración del sistema, este se cargaba en otro equipo de la red sin que tu programa se diera cuenta.

La dolorosa realidad: lentitud, memoria y el infierno de la conversión

Sobre el papel, COM era una maravilla. En la práctica, tenía un problema gigantesco: no era lento, era inmanejablemente lento. Además, consumía una cantidad de memoria increíble. ¿Por qué? Por el coste de los «acoples», las conversiones de datos entre el objeto y la aplicación.

Imagina que tu portátil tiene una salida de vídeo DisplayPort, pero tu monitor es un viejo VGA. Como no hay un conversor directo, tienes que encadenar adaptadores: de DisplayPort a DVI, de DVI a HDMI, y finalmente de HDMI a VGA. Cada adaptador en esa cadena es un paso intermedio que añade latencia y complejidad. Eso es exactamente lo que pasaba con COM.

Veámoslo con un ejemplo de programación. Supongamos que un objeto COM tiene una cadena de texto «Hola Mundo» en un formato propio: Unicode con un CRC de verificación y terminada en el carácter 67. La interfaz intermedia de COM usa otro formato, los Hstrings. Y tu aplicación, escrita en C++, usa el formato de la librería STL, que guarda primero el tamaño y luego el texto. Para leer esa simple cadena, el sistema tiene que hacer tres conversiones, pasando por un formato intermedio.

Ahora imagina que quieres modificarla. La cosa se pone todavía peor. Modificas la cadena en C++, lo que crea una nueva asignación de memoria. Luego, esa cadena modificada debe convertirse al formato intermedio, que a su vez realiza su propio proceso de memoria. Y finalmente, se convierte al formato del objeto original. Si, para colmo, el objeto COM no permite modificar cadenas, acabas de liarla pardísima.

Para gestionar todo esto, había que definir un «Lenguaje de Definición de Interfaces» (IDL), donde se especificaba con todo lujo de detalles qué podía hacer cada objeto, si sus datos eran mutables, qué formato tenían las estructuras de datos (como el registro de un cliente), etc. Era un sistema propenso a errores y terriblemente complejo.

Anécdotas desde las trincheras: cuando Word decidía explotar

En la práctica, usar esto era una aventura. Imagina que querías automatizar Microsoft Office desde tu aplicación. El proceso parecía fácil: importabas el objeto ActiveX de Word, lo instanciabas y le decías «abre este documento». Y de repente, todo reventaba. ¿Por qué?

¡Ah, amigo! Quizás una llamada anterior había dejado a Word en un estado inestable en la memoria, y al volver a llamarlo, el programa se rompía. De esto, mi amiga Penny tiene bastante experiencia. La solución clásica era reiniciar el ordenador, y entonces, el mismo código funcionaba mágicamente. Aquello era un batiburrillo de diferentes ABIs (Interfaces Binarias de Aplicación) y una complejidad estructural demencial, especialmente en la era de Windows 95, 98 y 2000.

Este era el mundo en el que vivíamos los desarrolladores. Un sistema potente pero frágil y complicadísimo. Entender este caos es fundamental para apreciar por qué nació WinRT como su sucesor. Pero los pasos exactos para instanciar y trabajar con estos objetos… eso os lo contaré en el siguiente audio.

Así que ya sabéis, no olvidéis supextros habitualizaros. ¡Hasta el siguiente audio!