Compilación de una aplicación WPF

Las aplicaciones de Windows Presentation Foundation (WPF) pueden compilarse como aplicaciones ejecutables de .NET Framework (.exe), bibliotecas (.dll) o una combinación de ambos tipos de ensamblados. En este tema se ofrece una introducción a la compilación de aplicaciones WPF y se describen los pasos clave del proceso de compilación.

Compilar una aplicación de WPF

Una aplicación WPF se puede compilar de las maneras siguientes:

Canalización de compilación de WPF

Cuando se compila un proyecto de WPF, se invoca la combinación de destinos específicos del lenguaje y de WPF. El proceso de ejecución de estos destinos se denomina canalización de compilación y los pasos principales se muestran en la siguiente ilustración.

Proceso de compilación de WPF

Inicializaciones previas a la compilación

Antes de compilar, MSBuild determina la ubicación de herramientas y bibliotecas importantes, entre las que se incluyen las siguientes:

  • .NET Framework.

  • Los directorios de Windows SDK.

  • Ubicación de los ensamblados de referencia de WPF.

  • La propiedad de las rutas de búsqueda de ensamblados.

La primera ubicación en la que MSBuild busca los ensamblados es el directorio de ensamblados de referencia (%ProgramFiles%\Reference Assemblies\Microsoft\Framework\v3.0\). Durante este paso, el proceso de compilación inicializa también las distintas propiedades y grupos de elementos, y efectúa el trabajo de limpieza necesario.

Resolver referencias

El proceso de compilación busca y enlaza los ensamblados necesarios para generar el proyecto de aplicación. Esta lógica se incluye en la tarea ResolveAssemblyReference. Todos los ensamblados declarados como Reference en el archivo de proyecto se proporcionan a la tarea junto con información sobre las rutas de búsqueda y los metadatos de los ensamblados que ya están instalados en el sistema. La tarea busca los ensamblados y usa los metadatos del ensamblado instalado para filtrar los ensamblados de WPF básicos que no deben mostrarse en los manifiestos de salida. Esto es necesario para evitar información redundante en los manifiestos de ClickOnce. Por ejemplo, como PresentationFramework.dll se puede considerar representativo de una compilación de aplicación y de WPF y, puesto que todos los ensamblados de WPF están en la misma ubicación en cada equipo que tiene .NET Framework instalado, no hay necesidad de incluir toda la información sobre todos los ensamblados de referencia de .NET Framework en los manifiestos.

Compilación del marcado: Paso 1

En este paso, los archivos de XAML se analizan y compilan para que el motor en tiempo de ejecución no tenga que encargarse de analizar XML y validar los valores de propiedad. El archivo de XAML compilado se convierte en un token para que pueda cargarse mucho más rápido que un archivo XAML en tiempo de ejecución.

Durante este paso, se realizan las siguientes operaciones para cada archivo de XAML que es un elemento de compilación Page:

  1. El compilador de marcado analiza el archivo XAML.

  2. Se crea una representación compilada para el XAML y se copia a la carpeta obj\Release.

  3. Se crea una representación de CodeDOM de una nueva clase parcial y se copia a la carpeta obj\Release.

Además, se genera un archivo de código específico del lenguaje para cada archivo XAML. Por ejemplo, para una página Page1.xaml de un proyecto de Visual Basic, se genera un archivo Page1.g.vb; para una página Page1.xaml de un proyecto de C#, se genera un archivo Page1.g.cs. ".g" en el nombre de archivo indica que el archivo es código generado que tiene una declaración de clase parcial para el elemento de nivel superior del archivo de marcado (como Page o Window). La clase se declara con el modificador partial de C# (Extends en Visual Basic) para indicar que es otra declaración de la clase en otro lugar, normalmente en el archivo Page1.xaml.cs de código subyacente.

La clase parcial se extiende desde la clase base correspondiente (como Page para una página) e implementa la interfaz System.Windows.Markup.IComponentConnector. La interfaz IComponentConnector tiene métodos que inicializan un componente y conectan los nombres y eventos en elementos en su contenido. Por consiguiente, el archivo de código generado tiene una implementación de métodos como la siguiente:

public void InitializeComponent() {
    if (_contentLoaded) {
        return;
    }
    _contentLoaded = true;
    System.Uri resourceLocater =
        new System.Uri(
            "window1.xaml",
            System.UriKind.RelativeOrAbsolute);
    System.Windows.Application.LoadComponent(this, resourceLocater);
}
Public Sub InitializeComponent() _

    If _contentLoaded Then
        Return
    End If

    _contentLoaded = True
    Dim resourceLocater As System.Uri = _
        New System.Uri("mainwindow.xaml", System.UriKind.Relative)

    System.Windows.Application.LoadComponent(Me, resourceLocater)

End Sub

De manera predeterminada, la compilación del marcado se ejecuta en el mismo AppDomain que el motor de MSBuild. Esto proporciona una mejora del rendimiento importante. Este comportamiento se puede activar o desactivar alternativamente con la propiedad AlwaysCompileMarkupFilesInSeparateDomain. Esto presenta la ventaja de descargar todos los ensamblados de referencia descargando el objeto AppDomain independiente.

Compilación del marcado: Paso 2

No todas las páginas XAML se compilan en el paso 1 de la compilación de marcado. Los archivos XAML que tienen referencias de tipos definidos localmente (referencias a tipos definidos en el código en otro lugar del mismo proyecto) no se compilan en este momento. Esto se debe a que estos tipos definidos localmente solo existen en el código fuente y aún no están generados. Para determinar esto, el analizador usa heurística que implica la búsqueda de elementos como x:Name en el archivo de marcado. Cuando se encuentra este tipo de instancia, la compilación del archivo de marcado se pospone hasta que se hayan generado los archivos de código y, en ese momento, el segundo paso de la compilación de marcado procesa estos archivos.

Clasificación de archivos

El proceso de compilación coloca los archivos de salida en grupos de recursos diferentes en función del ensamblado de aplicación en el que se van a incluir. En una aplicación no traducida típica, todos los archivos de datos marcados como Resource se colocan en el ensamblado principal (ejecutable o biblioteca). Cuando UICulture se establece en el proyecto, todos los archivos XAML compilados y los recursos explícitamente marcados como específicos del lenguaje se colocan en el ensamblado de recursos satélite. Además, todos los recursos neutrales en cuanto al idioma se colocan en el ensamblado principal. Esta determinación se realiza en este paso del proceso de compilación.

Las acciones de compilación de ApplicationDefinition, Page y Resource del archivo de proyecto se pueden incrementar con los metadatos de Localizable (los valores permitidos son true y false), que indican si el archivo es específico del lenguaje o neutral en cuanto a este.

Compilación básica

El paso básico de compilación implica la compilación de los archivos de código. Esta compilación está controlada por los archivos de destino específicos del lenguaje Microsoft.CSharp.targets y Microsoft.VisualBasic.targets. Si la heurística ha determinado que basta con un solo paso del compilador de marcado, se genera el ensamblado principal. En cambio, si uno o varios archivos XAML del proyecto tienen referencias a tipos definidos localmente, se genera un archivo .dll temporal para que los ensamblados de aplicación finales se puedan crear una vez completado el segundo paso de compilación del marcado.

Generación de manifiestos

Al final del proceso de compilación, cuando todos los ensamblados de aplicación y archivos de contenido están listos, se generan los manifiestos de ClickOnce para la aplicación.

El archivo de manifiesto de implementación describe el modelo de implementación: la versión actual, el comportamiento de actualización y la identidad del editor junto con la firma digital. Se supone que este manifiesto lo crean los administradores que controlan la implementación. La extensión de archivo es .xbap (para aplicaciones de navegador XAML (XBAP)) y .application para las aplicaciones instaladas. La primera se indica a través de la propiedad HostInBrowser del proyecto y, como resultado, el manifiesto identifica la aplicación como hospedada en el explorador.

El manifiesto de aplicación (un archivo .exe.manifest) describe los ensamblados de la aplicación y las bibliotecas dependientes, e incluye los permisos necesarios para la aplicación. Se supone que este archivo lo crea el desarrollador de la aplicación. Para iniciar una aplicación ClickOnce, un usuario abre el archivo de manifiesto de implementación de la aplicación.

Estos archivos de manifiesto siempre se crean para las aplicaciones XBAP. Para las aplicaciones instaladas, no se crean a menos que la propiedad GenerateManifests se especifique en el archivo de proyecto con el valor true.

Las aplicaciones XBAP obtienen dos permisos adicionales además de los permisos asignados a las aplicaciones típicas de zona de Internet:WebBrowserPermission y MediaPermission. El sistema de compilación de WPF declara esos permisos en el manifiesto de la aplicación.

Compatibilidad con la compilación incremental

El sistema de compilación de WPF proporciona compatibilidad con compilaciones incrementales. Es bastante inteligente a la hora de detectar los cambios efectuados en el marcado o el código, y solo detecta los artefactos afectados por el cambio. El mecanismo de compilación incremental usa los archivos siguientes:

  • Un archivo $(nombreDeEnsamblado)_MarkupCompiler.Cache para mantener el estado actual del compilador.

  • Un archivo $(nombreDeEnsamblado)_MarkupCompiler.lref para almacenar en caché los archivos XAML con referencias a tipos definidos localmente.

A continuación, se incluye un conjunto de reglas que rigen la compilación incremental:

  • El archivo es la unidad más pequeña en la que el sistema de compilación detecta un cambio. Por tanto, para un archivo de código, el sistema de compilación no puede determinar si se ha cambiado un tipo o se ha agregado código. Lo mismo se aplica a los archivos de proyecto.

  • El mecanismo de compilación incremental debe saber que una página XAML define una clase o usa otras clases.

  • Si las entradas Reference cambian, vuelva a compilar todas las páginas.

  • Si un archivo de código cambia, vuelva a compilar todas las páginas con referencias de tipos definidos localmente.

  • Si un archivo XAML cambia:

    • Si XAML se declara como Page en el proyecto: si XAML no tiene referencias de tipos definidos localmente, vuelva a compilar ese XAML además de todas las páginas XAML con referencias locales; si XAML tiene referencias locales, vuelva a compilar todas las páginas XAML con referencias locales.

    • Si XAML se declara como ApplicationDefinition en el proyecto: vuelva a compilar todas las páginas XAML (razón: cada XAML hace referencia a un tipo Application que puede haber cambiado).

  • Si el archivo de proyecto declara un archivo de código como definición de aplicación en lugar de un archivo XAML:

    • Compruebe si el valor de ApplicationClassName en el archivo de proyecto ha cambiado (¿hay un nuevo tipo de aplicación?). En ese caso, vuelva a compilar toda la aplicación.

    • De lo contrario, vuelva a compilar todas las páginas XAML con referencias locales.

  • Si un archivo de proyecto cambia: aplique todas las reglas anteriores y determine lo que tiene que volver a compilar. Los cambios en las propiedades siguientes desencadenan una recompilación completa: AssemblyName, IntermediateOutputPath, RootNamespace y HostInBrowser.

Las situaciones de recompilación posibles son las siguientes:

  • Se vuelve a compilar toda la aplicación.

  • Solo se vuelven a compilar los archivos XAML que tienen referencias de tipos definidos localmente.

  • No se vuelve a compilar nada (si no ha cambiado nada en el proyecto).

Vea también