CoreBus y Native AOT: Experiencia práctica compilando una terminal multiplataforma
Compilar aplicaciones .NET usando Native AOT promete tiempos de inicio más rápidos y tamaños de distribución más pequeños, pero en la práctica, viene con una serie de limitaciones técnicas. Este artículo profundiza en un caso real de integración de Native AOT en CoreBus: una terminal multiplataforma para trabajar con puertos COM y protocolos Modbus.
¿Por qué Native AOT?
Los principales motivos para el cambio fueron los arranques en frío lentos en PC de gama baja y el gran tamaño del paquete de instalación. Después de migrar de WPF a Avalonia UI, el tamaño de la app se redujo en ~60 MB, pero alcanzar el objetivo de 10 MB aún parecía inalcanzable. Native AOT parecía el siguiente paso lógico, especialmente para una app dirigida a equipos industriales y sistemas embebidos donde los recursos suelen ser limitados.
Sin embargo, desde la etapa de compilación, quedó claro que el soporte de Native AOT requería una revisión completa de la arquitectura de bindings, la serialización y la estrategia de publicación.
Problema 1: Bindings compilados en Avalonia
Avalonia utiliza bindings dinámicos mediante reflexión por defecto, lo cual no es compatible con Native AOT. La solución es habilitar bindings precompilados:
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
Esto significa especificar explícitamente el tipo de datos en cada archivo XAML usando el atributo x:DataType en el elemento raíz (Window, UserControl) y dentro de los DataTemplates. Omitir este atributo provoca un error de compilación (Dynamic code generation is not supported on this platform) o un fallo inmediatamente después del lanzamiento.
En el proyecto CoreBus, esto requirió actualizar todo el marcado XAML, lo que tomó una cantidad significativa de tiempo pero eliminó las excepciones en tiempo de ejecución.
Problema 2: Serialización sin reflexión
Las configuraciones y preajustes de CoreBus se almacenan en archivos JSON usando System.Text.Json. Los métodos de serialización estándar están marcados con los atributos RequiresUnreferencedCode y RequiresDynamicCode, lo que genera advertencias IL2026 e IL3050 durante las compilaciones Native AOT.
La solución es usar serializadores JSON generados desde el código fuente mediante JsonSerializerContext. Aquí hay un ejemplo de una implementación correcta:
var options = new JsonSerializerOptions
{
WriteIndented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
TypeInfoResolver = SerializerContext.Default
};
using var stream = new FileStream(correctFilePath, FileMode.Open);
var jsonTypeInfo = options.TypeInfoResolver.GetTypeInfo(typeof(T), options);
if (jsonTypeInfo == null)
{
throw new Exception($"Not succeeded nayti type {typeof(T)} in obyavlenii konteksta serializatsii.");
}
JsonSerializer.Serialize(stream, data, jsonTypeInfo);
Para la deserialización, usa un enfoque similar:
var data = (T?)JsonSerializer.Deserialize(stream, typeof(T), SerializerContext.Default);
Esto evita completamente la reflexión y asegura la compatibilidad con el trimming y AOT.
Problema 3: Compilaciones multiplataforma
Native AOT requiere compilación nativa para el SO y arquitectura objetivo. La versión del kernel de Linux y glibc son críticas para la compatibilidad:
- Las compilaciones en Ubuntu 24.10 (kernel 6.11) no se ejecutaban en Astra Linux CE (kernel 5.15).
- Las compilaciones en Ubuntu 18.04 (glibc 2.27) fallaban en Astra Linux CE (glibc 2.24) debido a la falta de compatibilidad hacia atrás en la biblioteca C.
Estrategia final:
- Windows: Compilar en Windows 11, probar en Windows 7/10 — compatibilidad exitosa.
- Linux: Compilar directamente en Astra Linux CE con kernel 5.4 y glibc 2.24.
Esto maximiza la portabilidad pero complica el proceso de CI/CD.
Problema 4: Trimming y ensamblados dependientes
El trimming (eliminación de código no utilizado) proporciona los mayores ahorros solo en el proyecto ejecutable principal. Las bibliotecas auxiliares generan menos de 300 KB de ahorros totales.
Sin embargo, un trimming agresivo puede eliminar recursos esenciales de Avalonia, especialmente estilos. Se recomienda excluir explícitamente los temas del trimming:
<ItemGroup>
<TrimmerRootAssembly Include="Avalonia.Themes.Fluent" />
</ItemGroup>
Aunque esto podría no ser necesario en Avalonia 11.3.x, es mejor mantener la configuración como medida de precaución.
Problema 5: Falsos positivos de software antivirus
Las apps compiladas con Native AOT + trimming a menudo se marcan como maliciosas. Windows Defender etiquetó el instalador como Trojan:Win32/Bearfoos.B!ml y la versión portable como Trojan:Script/Wacatac.B!ml.
Este es un problema conocido relacionado con cambios en la estructura binaria y la falta de firma digital. La solución es pasar por la verificación de Microsoft SmartScreen y agregar una firma digital.
Puntos clave
- Native AOT requiere un rechazo completo de la reflexión y la generación de código dinámico.
- La compilación debe realizarse en la plataforma objetivo, teniendo en cuenta las versiones del kernel y las bibliotecas del sistema.
- La serialización generada desde el código fuente es obligatoria para el manejo de JSON.
- El trimming puede dañar recursos de UI — excluir manualmente los ensamblados clave.
- Las herramientas antivirus a menudo generan falsos positivos en binarios AOT sin firmar.
— Editorial Team
Aún no hay comentarios.