Patrones de implementación para Microsoft .NET Compact Framework

18 de Julio de 2005

Publicado: Noviembre de 2004

Por Dan Fox y Jon Box

http://atomic.quilogy.com/

Este artículo se aplica a:

Microsoft® .NET Compact Framework

Microsoft® Visual Studio® .NET 2003

Descargue el componente de actualización de aplicaciones para dispositivos inteligentes.

Resumen: los desarrolladores de software que finalizan un producto con éxito necesitan al final del día ponerlo a disposición del usuario. Ni siquiera el software más elegante escrito con las herramientas nuevas más eficaces es capaz de reducir los trámites burocráticos, mejorar el acceso a la información ni aumentar la precisión de los datos si los usuarios a los que va dirigido el producto no pueden poner sus manos en él. Este es el caso de los desarrolladores de software "ajustado" y de los desarrolladores corporativos que trabajan en entornos de escritorio y con dispositivos inteligentes. En este artículo trataremos los patrones que se pueden utilizar para implementar aplicaciones escritas utilizando Microsoft .NET Compact Framework. Este documento es una extensa colección agregada al debate en torno a este tema que desarrollamos en el capítulo 10 de nuestro libro Building Solutions with the Microsoft .NET Compact Framework.

Aunque el título de este artículo se centra en la idea de la implementación, en realidad se tratan varios temas relacionados que se pueden dividir en tres secciones: Empaquetado (los pasos necesarios para crear un binario distribuible), Implementación (cómo el binario distribuible se implementa e instala en el dispositivo inteligente) y Mantenimiento (cómo se actualiza la aplicación a medida que se modifica). En este artículo se tratarán estos tres aspectos y se intentará proporcionar una descripción general al respecto. Asimismo, se ofrecen varios vínculos a otros recursos que se pueden utilizar para empaquetar, implementar y mantener aplicaciones .NET Compact Framework. (22 páginas impresas.) (Este artículo contiene vículos a páginas en inglés.)

En esta página

Empaquetado Empaquetado
Implementación Implementación
Mantenimiento Mantenimiento
Resumen Resumen

Empaquetado

El primer paso necesario para poner a disposición del usuario una aplicación para dispositivos inteligentes es el empaquetado. Esta fase incluye la definición de una carpeta de salida y de acciones de generación en los archivos del proyecto, el cambio a modo de liberalización, la comprensión del funcionamiento de la caché de ensamblados global (GAC) en .NET Compact Framework y, por último, el empaquetado en sí de la aplicación en un archivo .cab para su posterior implementación.

Definición de la carpeta de salida

El primer paso de la fase de empaquetado de la aplicación consiste en definir la carpeta de archivos de salida en la ventana Propiedades del proyecto. De forma predeterminada, esta carpeta se define en el directorio \Archivos de programa\nombredeproyecto del dispositivo. Esta será la ruta en la que se implementará la aplicación cuando se ejecute el archivo .cab.

Definición de acciones de generación

En un proyecto para aplicaciones inteligentes, cada archivo se marca con una acción de generación que se define en la ventana Propiedades del modo siguiente.

  • Un archivo marcado como Compile, valor predeterminado para todos los archivos y formularios de código, compilará los archivos en el ensamblado resultante.

  • Un archivo marcado como Content permite el empaquetado del archivo en el archivo .cab y su implementación en el proyecto. Asimismo, resulta útil para la implementaciún de archivos de configuración XML y bases de datos SQL Server CE.

  • Un archivo marcado como None simplemente se ignora. Esta acción resulta útil para incluir documentación en el proyecto, como diagramas de Visio que no se deben implementar.

  • Un archivo marcado como Embedded Resource se incluye en el ensamblado ejecutable como un recurso. Esta acción permite que el código escrito extraiga el recurso mediante programación. Asimismo, resulta eficaz para empaquetar imágenes y archivos de secuencias de comandos que se pueden utilizar posteriormente en la aplicación. Por ejemplo, el fragmento de código siguiente carga una imagen en un objeto System.Drawing.Bitmap y la asigna a la propiedad Image de un control PictureBox.

Dim a As [Assembly] = [Assembly].GetExecutingAssembly()  
Dim b As Bitmap = New Bitmap(a.GetManifestResourceStream("logo.gif"))  
pbLogo.Image = b

Cambio a modo de liberación

Antes de pasar a compilar realmente la aplicación para dispositivos inteligentes y crear los archivos .cab para la implementación, no se debe olvidar cambiar el modo de generación del proyecto de depuración a liberación. Esto reducirá el tamaño del ejecutable en el dispositivo (hecho importante en el caso de los dispositivos inteligentes de almacenamiento restringido) y aumentará la velocidad de ejecución. El modo de generación se puede cambiar en la barra de herramientas estándar de Visual Studio .NET o en el cuadro de diálogo Administrador de configuración que se muestra en la figura 1, al cual se obtiene acceso si se elige Administrador de configuración tras hacer clic con el botón secundario del mouse en la solución en la ventana Explorador de soluciones o en el menú Generar.

Figura 1. Definición del modo de generación

Ya se puede generar el proyecto utilizando el menú Generar.

Uso de la caché GAC

Al igual que en .NET Framework de escritorio, .NET Compact Framework admite el uso compartido de ensamblados a través de la caché de ensamblados global. Aunque en .NET Framework de escritorio esta caché se utiliza principalmente para que las aplicaciones puedan utilizar de forma transparente versiones actualizadas de ensamblados, .NET Compact Framework no admite la directiva de versión configurable y, por tanto, el ejecutable siempre se enlazará a la versión del ensamblado con la que se compiló. En cambio, en .NET Compact Framework, la caché GAC resulta útil principalmente en tanto que permite la implementación de una copia del ensamblado en el dispositivo, lo cual permite el ahorro de espacio.

En un dispositivo inteligente, la caché GAC se ubica en el directorio \Windows y consta simplemente de los ensamblados, a cuyo nombre se le agrega el prefijo "GAC" y el número de versión y la referencia cultural; por ejemplo GAC_System.Data.Common_v1_0_5000_0_cneutral_1.dll. Los ensamblados se sitúan en la caché GAC con la utilidad Cgacutil.exe, que invoca el motor de tiempo de ejecución cada vez que se ejecuta una aplicación .NET Compact Framework. Esta utilidad busca archivos de texto codificado en ANSI o UTF-8 con extensión .gac en el directorio \Windows que contengan listas de ensamblados compartidos para, a continuación, moverlos a la caché GAC.

El aspecto de un archivo .gac tópico será el siguiente.

\Program Files\MyApp\AssemblyA.dll  
\Program Files\MyApp\AssemblyB.dll

Si el archivo .gac se elimina del directorio \Windows o se actualiza, los cambios adecuados (inserciones y eliminaciones) se realizarán en la caché GAC la próxima vez que se ejecute una aplicación .NET Compact Framework.

Los ensamblados también se pueden instalar directamente en la caché GAC, así como quitarlos de ésta, iniciando la utilidad Cgacutil.exe desde una aplicación mediante programación. Por ejemplo, la aplicación puede utilizar una clase que encapsula a la función no administrada CreateProcess para invocar a Cgacutil y utilizar las opciones –i y –u para instalar y desinstalar los ensamblados en la caché GAC, como se muestra en su forma simplificada en la lista 1.

Public Structure PROCESS_INFORMATION  
  
    Public hProcess As UInt32  
    Public hThread As UInt32  
    Public dwProcessId As UInt32  
    Public dwThreadId As UInt32  
End Structure  
  
Public NotInheritable Class WindowsCE  
    Private Sub New()  
    End Sub  
  
    <DllImport("coredll.dll", EntryPoint:="CreateProcess", _  
     SetLastError:=True)> _  
    Private Shared Function CreateProcess( _  
        ByVal lpszImageName As String, _  
        ByVal lpszCmdLine As String, _  
        ByVal lpsaProcess As IntPtr, _  
        ByVal lpsaThread As IntPtr, _  
        ByVal fInheritHandles As Integer, _  
        ByVal fdwCreate As UInt32, _  
        ByVal lpvEnvironment As IntPtr, _  
        ByVal lpszCurDir As IntPtr, _  
        ByVal lpsiStartInfo As IntPtr, _  
        ByRef lppiProcInfo As PROCESS_INFORMATION) As Integer  
    End Function  
  
    Public Shared Function StartProcess(ByVal imageName As String, _  
      ByVal cmdLine As String) As Integer  
        Dim procInfo As New PROCESS_INFORMATION  
        CreateProcess(imageName, cmdLine, _  
         IntPtr.Zero, IntPtr.Zero, 0, Convert.ToUInt32(0), _  
         IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, procInfo)  
        Return Convert.ToInt32(procInfo.dwProcessId)  
    End Function  
  
    Public Shared Sub GACInstall(ByVal imageName As String)  
        WindowsCE.StartProcess("cgacutil.exe", "-i " & imageName)  
    End Sub  
  
    Public Shared Sub GACUninstall(ByVal imageName As String)  
       WindowsCE.StartProcess("cgacutil.exe", "-u " & imageName)  
    End Sub  
End Class

Lista 1. Modificación de la caché GAC mediante programación

La aplicación cliente puede agregar a continuación un ensamblado a la caché GAC:

WindowsCE.GACUInstall("\Program Files\My App\AssemblyA.dll")

Al igual que a los ensamblados Framework de escritorio situados en la caché GAC se les debe asignar nombres seguros mediante los atributos AssemblyKeyFile o AssemblyKeyName. .NET Compact Framework no admite la firma retardada con el atributo AssemblyDelaySign.

Creación de archivos Cab

La forma más simple de empaquetar una aplicación para dispositivos inteligentes antes de implementarla en el dispositivo es crear un archivo .cab. Afortunadamente, Visual Studio .NET facilita esta tarea al proporcionar un menú para la generación de este tipo de archivos, que se puede invocar si se hace clic con el botón secundario del mouse en el proyecto. Se creará un directorio .cab en el directorio de modo de generación actual, el cual contendrá una serie de archivos .cab mediante el uso de la convención de nomenclatura cpu_plataforma_aplicación.CAB. Para ver el directorio .cab, es necesario seleccionar la opción Mostrar todos los archivos, que se muestra en la figura 2.

Figura 2. Archivos Cab creados para la implementación

En cuanto a la convención de nomenclatura, "plataforma" hace referencia a Windows CE (WCE) o Pocket PC (PPC), mientras que CPU identifica el tipo de procesador admitido por .NET Compact Framework al que va dirigido el archivo .cab. Entre las CPU compatibles se incluyen ARM, ARMV4, MIPS, SH3 y X86.

Con el fin de personalizar el proceso de instalación, los archivos necesarios para personalizar y regenerar los archivos .cab se sitúan en el directorio obj\buildmode. En éste se encuentran los archivos BuildCab.bat, Dependencias_plataforma.txt y Nombredeproyecto_plataforma.inf, junto con un archivo de configuración para cada tipo de procesador. Estos archivos se utilizan del modo siguiente:

  • El archivo por lotes se puede utilizar para inicializar la regeneración de los archivos .cab. El archivo BuildCab.bat invoca a la utilidad CabWiz.exe, la cual se puede invocar también de forma independiente para personalizar aún más el proceso de generación. Para revisar la sintaxis de invocación del archivo CabWizFor, consulte el tema Cab wizard syntax en el SDK de Windows CE .NET 4.2.

  • El archivo de dependencias contiene la lista de archivos .cab de los cuales este archivo depende, y siempre contiene una referencia al archivo .cab de .NET Compact Framework correspondiente a cada tipo de procesador. Estas dependencias se comprueban en tiempo de instalación a través de los archivos vsd_config.txt.plataforma con el fin de garantizar que se ha instalado la versión adecuada de .NET Compact Framework en el dispositivo. Es importante tener en cuenta que el archivo .cab de .NET Compact Framework no se incluye en el archivo .cab creado para el proyecto. Este es el caso también del archivo .cab específico de la plataforma SQL Server CE necesario para instalarla en el dispositivo.

  • El archivo .inf contiene la configuración de instalación que es preciso utilizar cuando se ejecuta el archivo .cab. Para personalizar la instalación en el dispositivo, se puede modificar el archivo .inf. Un modo sencillo de llevar a cabo esta operación consiste en agregar un acceso directo al menú Inicio de Pocket PC. Para ello, se pueden modificar las secciones siguientes del archivo .inf:

[DefaultInstall]  
CEShortcuts=Shortcuts  
  
[Shortcuts]  
My Application,0,myapp.exe,%CE17%

En este caso, la sección CEShortcuts se redirige a la sección Shortcuts del archivo, donde se crea un acceso directo (un archivo con formato especial en Windows CE) especificando el texto del acceso directo, 0 para identificarlo como archivo, el archivo al que lleva el acceso directo y la carpeta en la que se sitúa este último. El identificador %CE17% especifica el directorio \Windows\StartMenu en un dispositivo Windows CE. Para obtener más información sobre las distintas secciones del archivo .inf, consulte el tema Creating an .INF file en el SDK de Windows CE .NET 4.2

Entre otros ejemplos de personalización del archivo .inf se incluyen la adición de otros archivos (por ejemplo, bases de datos SQL Server CE y archivos .gac) para su distribución en el dispositivo a través del archivo .cab modificando la sección SourceDiskFiles.

Una vez creados los archivos .cab del proyecto, éstos se implementan en el dispositivo inteligente. En la sección siguiente se tratan las técnicas utilizadas para llevar a cabo esta implementación. La simple ejecución del archivo .cab en el dispositivo inicia la instalación a través del ejecutable WCELoad en Windows CE, lo que, a su vez, realiza el desempaquetado del archivo .cab, la instalación de su contenido y la eliminación automática del archivo .cab una vez completada la instalación. Asimismo, durante este proceso, se almacena en el dispositivo la información necesaria para que la aplicación se pueda desinstalar si se puntea en Configuración, Quitar programas.

Implementación

La implementación de la aplicación en un dispositivo inteligente se puede hacer de varias formas, una de las cuales ni siquiera requiere haber creado archivos .cab. En esta sección, se verán las técnicas disponibles para implementar tanto .NET Compact Framework como la aplicación, y se debatirá en torno al modo y el momento en el que se deben implementar cada uno.

Implementación de .NET Compact Framework y SQL Server CE

Antes de poder ejecutar la aplicación, es preciso instalar .NET Compact Framework en el dispositivo. Si la aplicación requiere SQL Server CE, también será necesario instalar el archivo .cab adecuado. Aunque ambos se instalan automáticamente al implementar la aplicación desde Visual Studio .NET mediante el menú Implementar o al depurar el dispositivo, en producción será necesario emplear un mecanismo diferente.

Como se mencionó anteriormente, los archivos .cab creados para un proyecto no incluyen los archivos .cab correspondientes a .NET Compact Framework o SQL Server CE. Aunque Pocket PC 2003, y dispositivos posteriores, suelen incluir .NET Compact Framework en ROM, será preciso implementar los archivos .cab específicos de la plataforma en cuestión en el caso en que dichos dispositivos no los incluyan. La ejecución del archivo .msi cuando el dispositivo está acoplado instalará .NET Compact Framework. Si el dispositivo no está acoplado al ejecutar este archivo, NCFSetup.exe se puede ejecutar desde la carpeta de instalación una vez que el dispositivo esté conectado. Si el dispositivo no se puede acoplar, debido, por ejemplo, a razones de logística, los archivos .cab específicos de la plataforma se pueden descargar en el dispositivo y, a continuación, ejecutarse haciendo uso de una de las técnicas que se describen más adelante en esta sección.

Una técnica más compleja para llevar a cabo la implementación, e incluso la actualización, de .NET Compact Framework y SQL Server CE consiste en utilizar el código de ejemplo proporcionado por Stan Adermann en su artículo Creating an MSI Package that Detects and Updates the .NET Compact Framework. El código de ejemplo muestra el modo de detectar la presencia de .NET Compact Framework en el dispositivo mediante el uso de las API remotas (RAPI) CeRapiInitEx y CeFindAllFiles desde el escritorio y la consulta en el registro del dispositivo para determinar el número de versión. A continuación, se puede detectar el tipo de dispositivo mediante la llamada a CeGetVersionEx, copiar el archivo .cab adecuado en el dispositivo y ejecutarlo con WCELoad.

XCopy

Como se indicó en la sección anterior, el modo más simple de instalar una aplicación .NET Compact Framework en un dispositivo inteligente ni siquiera requiere la creación de archivos .cab. Al igual que ocurre con Framework de escritorio, basta con copiar el ejecutable de la aplicación .NET Compact Framework y sus ensamblados dependientes (y el propio archivo .cab de .NET Compact Framework) en una carpeta del dispositivo. Este método resulta especialmente útil cuando la aplicación no se basa en ensamblados ubicados en la caché GAC, lo que suele conllevar la inclusión de un archivo .gac en el directorio \Windows. Sin embargo, incluso una aplicación implementada con XCopy puede, durante el inicio, registrar sus propios ensamblados con la caché GAC mediante la técnica descrita anteriormente

Existen dos formas principales de llevar a cabo la implementación mediante XCopy.

  • Copiar la aplicación en el dispositivo con la utilidad CECopy a través de Developer Power Toys para Windows Mobile. Se trata de una utilidad de línea de comandos que se basa en la API remota (RAPI) para conectarse y copiar archivos.

  • Utilizar las capacidades de sincronización de carpeta de ActiveSync configuradas como se muestra en la figura 3. Esta característica permite copiar una aplicación .NET Compact Framework en un directorio en un PC y, a continuación, copiarla automáticamente en el dispositivo al realizar una conexión ActiveSync.

Figura 3. Configuración de la sincronización de archivos en ActiveSync 3.7

Esta técnica puede resultar útil durante la implementación y prueba inicial en varios dispositivos, o cuando la aplicación es muy simple y no requiere la creación de accesos directos u otros eventos de instalación.

Sitio Web

Un segundo modo de implementar una aplicación .NET Compact Framework consiste en situar los archivos .cab adecuados en un sitio de Intranet. De este modo, el usuario sólo necesita ir al sitio con Pocket IE, como se muestra en la figura 4, y hacer clic en el hipervínculo. En este ejemplo, se enumeran los archivos .cab dirigidos a los dispositivos compatibles con una organización en particular. Por supuesto, la eficacia de esta técnica es máxima dentro del firewall cuando el dispositivo incluye conectividad WLAN (LAN inalámbrica), y fuera cuando admite conectividad WAN, como TDMA, CDMA, GSDM o GPRS, así como con dispositivos Pocket PC Phone Edition.

Figura 4. Implementación desde un sitio Web

Para aumentar el nivel de seguridad, el sitio Web se puede configurar de modo que requiera autenticación Básica y utilizar el nivel de socket seguro (SSL) para proteger los archivos .cab y el canal de comunicaciones, ambos compatibles con Pocket IE en el dispositivo.

Esta técnica es relativamente simple y alcanza su mayor utilidad en escenarios de Intranet con usuarios avanzados que tengan los conocimientos necesarios para ir al sitio Web. Su mayor ventaja reside en el hecho de que libera al usuario de la necesidad de acoplar el dispositivo a un PC. Para facilitar el proceso, si los dispositivos se utilizan para el envío de correo electrónico a través de, por ejemplo, Pocket Outlook, los hipervínculos se pueden enviar en un mensaje de correo electrónico de modo que el usuario sólo tenga que puntear en el hipervínculo para instalar la aplicación.

Recurso compartido de archivos

Una tercera técnica, que es prácticamente idéntica al uso de un sitio Web, consiste en el envío de los archivos .cab a un recurso compartido en una LAN. Obviamente, esta técnica sólo se puede utilizar dentro del firewall. El recurso compartido se puede proteger también de modo que pida permiso al usuario al abrir el recurso desde el Explorador de archivos del dispositivo inteligente, como se muestra en la figura 5.

Figura 5. Apertura de un recurso compartido de archivos en Pocket PC

Esta técnica resulta eficaz cuando el dispositivo dispone de conectividad WLAN, ya que, como se dijo anteriormente, libera al usuario de la necesidad de acoplar el dispositivo. Sin embargo, también se puede utilizar cuando el dispositivo está acoplado utilizando ActiveSync..

Aplicaciones empaquetadas

A las empresas de gran envergadura que deben administrar cientos de dispositivos, a menudo resulta rentable invertir en una solución empaquetada. Por ejemplo, el uso de Afaria de XcelleNet permite que un sitio central se pueda utilizar para controlar y administrar una amplia gama de dispositivos que incluyan Pocket PC, así­ como instalar y mantener automáticamente aplicaciones en dichos dispositivos.

En los escenarios en los que el dispositivo se utiliza en una LAN inalámbrica (WLAN) en función de la ubicación, se puede utilizar el servidor de provisión (Appear Provisioning Server) APS de Appear Network's. Este producto permite a los administradores configurar "zonas de hacer clic y ejecutar" de modo que cuando los dispositivos entren en una zona determinada se instale y ejecute automáticamente un software determinado.

Asimismo, Microsoft va a aumentar la compatibilidad para la administración de dispositivos móviles que ejecutan Windows CE 4.2 o Windows Mobile 2003 para Pocket PC en un próximo pack de características de administración de dispositivos para Systems Management Server (SMS) 2003 (actualmente en versión beta).

Para obtener más información acerca de la administración de dispositivos móviles y vínculos a otros proveedores, consulte el artículo Pocket PC Systems Management.

Tarjeta de almacenamiento

Uno de los escenarios habituales es aquel en el que es necesario que la aplicación se implemente junto con una base de datos de gran tamaño de SQL Server CE u otros archivos de datos de gran volumen. La implementación de una aplicación de este tipo a través de un sitio Web, o incluso una conexión acoplada, puede llevar bastante tiempo y requerir demasiado trabajo.

Por ello, resulta adecuado implementar la aplicación en una tarjeta de almacenamiento de memoria, como una tarjeta Compact Flash. Al insertar la tarjeta en el dispositivo, el usuario puede buscar el archivo .cab y ejecutarlo para instalar la aplicación.

Sin embargo, en lugar de que el usuario deba ejecutar el archivo .cab una vez que la tarjeta de almacenamiento está insertada en el dispositivo, los dispositivos Pocket PC incluyen una característica de ejecución automática.

Gracias a esta característica, cuando una tarjeta de almacenamiento se inserta en el dispositivo, Pocket PC busca el ejecutable Autorun.exe en una carpeta asignada al tipo de procesador (recuperado desde la función no administrada GetSystemInfo) del dispositivo. Por ejemplo, si el tipo de procesador es ARM, Pocket PC buscará el archivo \Tarjeta de almacenamiento\2577\Autorun.exe en la tarjeta. Una vez encontrado, el ejecutable se copiará en la carpeta \Windows y se ejecutará con un parámetro de instalación. Del mismo modo, al extraer la tarjeta del dispositivo, el mismo ejecutable se ejecuta con un parámetro de desinstalación.

Aunque se salga un tanto del ámbito de este artículo, el ejecutable de ejecución automática debe realizar los pasos siguientes.

  • Determinar en qué modo se debe ejecutar . Debido a que el parámetro de línea de comandos de instalación o desinstalación se pasa a la aplicación, la aplicación de ejecución automática debe, en primer lugar, determinar en cuál de los dos modos ejecutarse. Si se detecta el parámetro de desinstalación, puede que la aplicación de ejecución automática simplemente se cierre correctamente, o que compruebe si la aplicación ya está en ejecución.

  • Comprobar que la aplicación no está instalada todavía en el dispositivo. Esto se puede realizar buscando el directorio de instalación de la aplicación.

  • Encontrar el nombre de la tarjeta de almacenamiento. Debido a que los nombres de las tarjetas de almacenamiento pueden variar en dispositivos localizados (no siempre se llama "Tarjeta de almacenamiento") y debido a que los dispositivos admiten más de una tarjeta, se pueden utilizar técnicas como el uso de la función API FindFirstFile de Windows CE, como se describe en el artículoPocket PC Programming Tips.

  • Buscar el archivo .cab específico del procesador . Si es necesario admitir varios tipos de procesadores, se puede detectar el procesador en el dispositivo para, a continuación, asignarlo al directorio adecuado de la tarjeta de almacenamiento. La función GetSystemInfo de Windows CE devolverá la arquitectura del procesador en su estructura SYSTEM_INFO. Obviamente, podemos saltarnos este paso creando ejecutables personalizados para cada tipo de procesador y situándolos a continuación en la tarjeta de almacenamiento.

  • Ejecutar el archivo .cab . Para ello, se puede utilizar una API de Windows CE, como ShellExecuteEx o CreateProcess. Ésta se puede utilizar para ejecutar el archivo .cab de .NET Compact Framework y el de la aplicación, ya que puede que .NET Compact Framework no se hubiera instalado anteriormente en el dispositivo. Por esta razón, la aplicación Autorun.exe se suele escribir en eMbedded Visual C.

Escritorio

Tal vez, la técnica más utilizada para implementar aplicaciones para dispositivos inteligentes consiste en utilizar el administrador de aplicaciones de ActiveSync, de modo que la aplicación se implemente automáticamente cuando el dispositivo esté acoplado y conectado a un equipo de escritorio. Este proceso conlleva el paso al administrador de aplicaciones (CeAppMgr.exe) de la ruta a un archivo .ini que especifique la información acerca de la aplicación que se va a instalar en el dispositivo, como la que se muestra a continuación..

[CEAppManager]  
Version      = 1.0  
Component    = MyApp  
  
[MyApp]  
Description  = My Application  
Uninstall    = MyApp  
CabFiles     = MyApp_PPC.ARM.cab, MyApp_PPC.SH3.cab, MyApp_PPC.ARMV4.cab

Uno de las ventajas que conlleva el uso de esta técnica es que el administrador de aplicaciones detecta el tipo de dispositivo, por lo que copia e instala el archivo .cab adecuado en el mismo. Sin embargo, esta técnica requiere claramente que el dispositivo se sitúe en una ubicación central y se conecte a un PC.

El proyecto de instalación puede invocar de forma adicional una instalación previa a la generación para ejecutar el archivo BuildCab.bat y regenerar los archivos .cab para el proyecto.

Aunque está claramente descrita en el artículo al que se hace referencia anteriormente, la parte más importante de esta técnica se basa en la creación de una clase que herede de System.Configuration.Install.Installer y a la que la aplicación de instalación pueda llamar para invocar al administrador de aplicaciones, como se muestra en la lista 2.

<RunInstaller(True)> _  
Public Class AppManagerInstaller : Inherits Installer  
  
    Private Sub AppManagerInstaller_AfterInstall(ByVal sender As Object, _  
       ByVal e As InstallEventArgs) _  
       Handles MyBase.AfterInstall  
  
        Dim ceApp As String = GetCeAppMgr()  
        If ceApp = String.Empty Then  
            Return  
        End If  
        Dim args As String = GetIniArgs()  
  
        ' Start the application manager process  
        Process.Start(ceApp, args)  
    End Sub  
  
    Private Sub AppManagerInstaller_AfterUninstall( _  
       ByVal sender As Object, ByVal e As InstallEventArgs) _  
       Handles MyBase.AfterUninstall  
  
        Dim ceApp As String = GetCeAppMgr()  
        If ceApp = String.Empty Then  
            Return  
        End If  
        Dim args As String = GetIniArgs()  
  
        ' Start the application manager process without parameters  
        Process.Start(ceApp, ")  
    End Sub  
  
    Private Function GetCeAppMgr() As String  
  
        ' Get the key from the registry  
        Dim ceApp As String = KeyExists()  
        If ceApp = String.Empty Then  
            MessageBox.Show( _  
          "The Application Manager is not installed on this machine.", _  
             "Setup", MessageBoxButtons.OK, MessageBoxIcon.Error)  
            Return String.Empty  
        Else  
            Return ceApp  
        End If  
  
    End Function  
  
    Private Function GetIniArgs() As String  
       Return " & _  
         Path.Combine([Assembly].GetExecutingAssembly().Location, _  
       "Setup.ini")&"  
    End Function  
  
    Private Function KeyExists() As String  
        Dim key As RegistryKey = Registry.LocalMachine.OpenSubKey( _  
    "SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\CEAPPMGR.EXE")  
  
        If key Is Nothing Then  
            Return String.Empty  
        Else  
           Return key.GetValue(", String.Empty).ToString  
  
        End If  
    End Function  
End Class

Lista 2. Un componente de instalación personalizado para invocar al administrador de aplicaciones ActiveSync

La clase de la lista 2 se compila como un ensamblado de biblioteca de clases independiente y, a continuación, se agrega como una acción de instalación y desinstalación personalizada en el proyecto de instalación. Cuando el evento AfterInstall se activa, el código busca la presencia del administrador de aplicaciones en el equipo consultando el registro. Si lo encuentra, construye un argumento para pasarlo al administrador de aplicaciones que incluye la ruta al archivo Setup.ini. Por último, inicia el administrador de aplicaciones utilizando Process.Start y pasándolo en el argumento. En la desinstalación, el administrador de aplicaciones se ejecuta sin el argumento para permitir al usuario desinstalar la aplicación del dispositivo.

Mantenimiento

Una cosa es implementar una aplicación para dispositivos inteligentes y otra muy distinta es mantenerla actualizada a medida que se solucionan los problemas y cambian los requisitos del negocio. El mantenimiento resulta especialmente difícil si los dispositivos no se llevan regularmente a una ubicación central en la que se puedan acoplar y actualizar mediante las técnicas de implementación de escritorio descritas en la sección anterior. Para enfrentarse a este escenario se puede crear una aplicación para dispositivos inteligentes de actualización automática poniendo en práctica los conceptos que los desarrolladores de Framework de escritorio utilizan habitualmente para implementar las conocidas aplicaciones de "cliente inteligente".

Creación de aplicaciones de actualización automática

Alex Feinman, en su artículo Creating Self-Updating Applications With the .NET Compact Framework, describe un enfoque para crear aplicaciones que se puedan actualizar automáticamente sin necesidad de utilizar ActiveSync. En este artículo, se implementa una aplicación de actualización complementaria con la aplicación. Este "subprograma" lee un archivo de configuración y llama a un servicio Web para determinar si se dispone de actualizaciones para la aplicación. En caso afirmativo, se descarga el archivo .cab adecuado y se ejecuta en el dispositivo.

La ventaja de este enfoque consiste en que se descarga e instala una versión completamente nueva de la aplicación sin que sea necesario el uso del acoplamiento, y se hace en un único archivo .cab, lo que reduce el uso de ancho de banda y, además, es compatible con varias plataformas. No obstante, el principal inconveniente de este enfoque es que no permite que la aplicación interactúe con el proceso de actualización.

Creación de un componente de actualización de aplicaciones para dispositivos inteligentes

Un enfoque más integrado consistiría en crear un componente al que pudiera hacer referencia la aplicación para integrar su proceso de actualización. Se ha creado uno llamado SmartAppUpdater, que se tratará en el resto del artículo y el cual se puede utilizar libremente como punto de inicio para la implementación de un componente propio.

Sin embargo, antes de analizar su funcionalidad, podemos hacernos una idea de cómo funciona este componente echando un vistazo a la API pública que se muestra en la tabla 1 y al diagrama de arquitectura de la figura 6.

Miembro

Descripción

FileDownloaded

Evento que se activa cuando se actualiza un archivo que pertenece a una versión nueva

Updated

Evento que se activa cuando se ha completado la descarga y aplicación de la versión nueva

Updating

Evento que se activa cuando se encuentra una versión nueva de la aplicación

AppName

Propiedad que devuelve el nombre de la aplicación que se va a actualizar

AppVersion

Propiedad que devuelve la versión actual de la aplicación

Downloader

Propiedad que devuelve el componente IDownloadable utilizado para la descarga de los archivos

DownloadLocation

Propiedad que devuelve la ubicación de servidor desde la que se descargarán las versiones nuevas

KeepOld

Propiedad que determina si se mantendrá o no la versión anterior de la aplicación

NewPath

Propiedad que devuelve la ruta de la versión nueva de la aplicación

RootPath

Propiedad que devuelve la ruta raíz de la aplicación

PostProcessors

Propiedad que expone una colección de objetos IPostProcessor utilizados para ejecutar código personalizado tras un proceso de actualización

BeginStartUpdate

Método que ejecuta el proceso de actualización en un subproceso en segundo plano

LogMessage

Método sobrecargado para registrar mensajes

MoveFile

Método que mueve un archivo de la carpeta actual a una nueva tras realizar una actualización

StartNewVersion

Método que inicia la versión nueva de la aplicación

StartUpdate

Método que ejecuta el proceso de actualización de forma sincrónica

Tabla 1. Interfaz del componente SmartAppUpdater

Figura 6. Arquitectura de SmartAppUpdater

Para comprender el modo en que se puede utilizar el componente SmartAppUpdater, dividiremos su funcionalidad en variasáreas clave, entre las que se incluyen:

  • Compilación automática del proceso de modo que la versión actual siempre esté cargada

  • Descarga de archivos de un servidor que incluye un manifiesto y archivos de aplicación

  • Detección de versiones nuevas de la aplicación y descarga de actualizaciones

  • Generación de notificaciones al encontrar una versión nueva, descargar archivos o actualizar la aplicación

  • Procesamiento posterior de modo que se pueda incluir código propio en el proceso

  • Inicio de la versión nueva de la aplicación

  • Limpieza de la versión anterior de la aplicación

Compilación automática del proceso

Para que un usuario pueda ejecutar la versión más actual de la aplicación es preciso realizar un proceso de compilación automática. Esta pequeña aplicación .NET Compact Framework sin IU llamada SmartAppLoader se encarga de leer el archivo de configuración SmartAppLoader.xml, que se muestra a continuación, y, posteriormente, iniciar la aplicación.

<?xml version="1.0" encoding="utf-8" ?>  
<!-- Used by the SmartAppLoader to start the current application -->  
<SmartAppLoader>  
    <AppPath>1.0.0.0</AppPath>  
    <AppImage>TestUpdater.exe</AppImage>  
    <AppName>Test Updater</AppName>  
</SmartAppLoader>

Se puede observar que todo lo que se necesita para comenzar es la carpeta relacionada con la carpeta en la que se ejecuta la aplicación y el nombre del ejecutable. A continuación, la clase SmartAppLoader, que se muestra en la lista 3, se utiliza para leer el archivo de configuración y exponer un método StartApp.

Namespace Atomic.CF.Deployment  
    Public Class SmartAppLoader  
        Private _appPath, _appImage, _appName, _curPath As String  
        Public Sub StartApp()  
            Atomic.CF.Utils.WindowsCE.StartProcess( _  
             _curPath & Path.DirectorySeparatorChar & _appPath & _  
Path.DirectorySeparatorChar & _appImage, Nothing)  
        End Sub  
  
        Public Sub New()  
            _InitClass()  
        End Sub  
  
        Public ReadOnly Property AppName() As String  
            Get  
                Return _appName  
            End Get  
        End Property  
  
        Private Sub _InitClass()  
  
            Dim xnl As XmlNodeList  
            Dim xd As XmlDocument  
  
            ' Get the current path  
            _curPath = Path.GetDirectoryName( _  
             [Assembly].GetExecutingAssembly().GetName().CodeBase)  
  
            xd = New XmlDocument  
            xd.Load(_curPath & "\SmartAppLoader.xml")  
  
' Read the app name  
xnl = xd.GetElementsByTagName("AppName")  
_appName = xnl(0).FirstChild.Value  
 ' Read the app path  
  xnl = xd.GetElementsByTagName("AppPath")  
  _appPath = xnl(0).FirstChild.Value  
 ' Read the app image  
  xnl = xd.GetElementsByTagName("AppImage")  
  _appImage = xnl(0).FirstChild.Value  
        End Sub  
    End Class  
End Namespace

Lista 3. La clase SmartAppLoader

El método StartApp se basa en la clase WindowsCE, que se muestra en la lista 1, para iniciar el proceso. Como se muestra más adelante, a continuación el punto de entrada de la aplicación simplemente crea una instancia del objeto SmartAppLoader y llama al método StartApp antes de salir silenciosamente. Si ocurre un error, aparecerá un cuadro de mensaje. Para utilizar SmartAppLoader, los accesos directos especificados en el archivo y creados en el dispositivo deben apuntar al ejecutable SmartAppLoader.

Public Sub Main()  
        Dim l As SmartAppLoader  
  
        Try  
            l = New SmartAppLoader  
            l.StartApp()  
        Catch ex As Exception  
            MsgBox("Could not start the application.", _  
              MsgBoxStyle.Critical, "Smart App Loader")  
        End Try  
        ' The loader exits  
    End Sub

Descarga de archivos

Debido a que el componente SmartAppUpdater se va a utilizar para actualizar la aplicación sin acoplamiento, va a ser necesario descargar archivos de un servidor mediante uno de los distintos métodos de transporte disponibles, por ejemplo, desde un sitio Web, un servicio Web o un recurso compartido de archivos. Para poder conectar clases propias para realizar la descarga, el ensamblado SmartAppUpdater incluye la interfaz IDownloadable, que se muestra aquí.

Public Interface IDownloadable  
    Sub GetServerFile(ByVal filePath As String, _  
      ByVal serverPath As String)  
    Property Credentials() As ICredentials  
End Interface

Esta interfaz sólo contiene un método GetServerFile que SmartAppUpdater invocará para descargar un archivo y la propiedad Credentials utilizada para proporcionar credenciales para la autenticación.

Debido a que la recuperación de versiones nuevas de un sitio Web es el enfoque más habitual, el ensamblado SmartAppUpdater también incluye una clase HttpDownloader que implementa la interfaz que se muestra en la lista 4.

Namespace Atomic.CF.Deployment  
    Public Class HttpDownloader  
        Implements IDownloadable  
  
        Private _creds As ICredentials  
  
        Public Sub New()  
        End Sub  
  
        Public Sub New(ByVal credentials As ICredentials)  
            _creds = credentials  
        End Sub  
  
        Public Sub GetServerFile(ByVal filePath As String, _  
           ByVal serverPath As String) _  
           Implements IDownloadable.GetServerFile  
  
            Dim hr As HttpWebRequest  
            Dim fs As FileStream  
            Dim s As Stream  
  
            Try  
                hr = CType(HttpWebRequest.Create(serverPath), _  
                   HttpWebRequest)  
  
                If Not _creds Is Nothing Then  
                    hr.Credentials = _creds  
                End If  
  
                ' Delete file if it exists  
                If File.Exists(filePath) Then File.Delete(filePath)  
  
                ' Get new file  
                fs = New FileStream(filePath, FileMode.Create)  
                s = hr.GetResponse.GetResponseStream  
  
                Dim buffer(4096) As Byte  
  
                Dim bytesRead As Integer = s.Read(buffer, _  
                  0, buffer.Length)  
                While bytesRead > 0  
                    fs.Write(buffer, 0, bytesRead)  
                    bytesRead = s.Read(buffer, 0, buffer.Length)  
                End While  
  
            Catch e As Exception  
                Throw New ApplicationException( _  
                 "Could not download file " & serverPath, e)  
            Finally  
                If Not s Is Nothing Then s.Close()  
                If Not fs Is Nothing Then fs.Close()  
            End Try  
        End Sub  
  
        Public Property Credentials() As ICredentials _  
         Implements IDownloadable.Credentials  
            Get  
                Return _creds  
            End Get  
            Set(ByVal Value As ICredentials)  
                _creds = Value  
            End Set  
        End Property  
    End Class  
End Namespace

Lista 4. La clase HttpDownloader

Esta clase simple implementa el método GetServerFile y utiliza el objeto HttpWebRequest para descargar el archivo mediante la misma técnica descrita por Jim Wilson en su artículo Improving .NET Compact Framework HTTP Communications using HttpWebRequest and Custom ASP.NET Providers. Es preciso tener en cuenta que, en primer lugar, el código aplica el objeto ICredentials si está presente y elimina el archivo existente, si lo hay, antes de descargar el archivo nuevo.

El nombre completo del componente IDownloadable que SmartAppUpdater utilizará y, de forma opcional, el ensamblado en el que se ubica, se almacenan en el archivo SmartAppUpdater.xml. Asimismo, en este archivo de configuración se incluye la ubicación del servidor desde el que se descargarán las actualizaciones, un indicador que determina si se guardarán o no las versiones anteriores de la aplicación, el número de la versión actual, el nombre de la aplicación y la ruta en la que se ubica SmartAppLoader, como se muestra a continuación.

<? Xml version="1.0" encoding="utf-8”?>  
<!-- Configuration file used by the SmartAppUpdater component -->  
<SmartAppUpdater>  
    <DownloadLocation>http://192.168.1.101/TestLoader</DownloadLocation>  
    <KeepOld>False</KeepOld>  
    <Downloader>Atomic.CF.Deployment.HttpDownloader</Downloader>  
    <AppVersion>1.0.0.0</AppVersion>  
    <AppName>Test Updater</AppName>  
    <RootPath>\Program Files\TestUpdater</RootPath>  
</SmartAppUpdater>

Nota. para admitir varias plataformas, el elemento DownloadLocation, en diferentes generaciones de la aplicación, se puede configurar de modo que señale a sitios Web específicos de una plataforma.

Esta información la lee directamente un método privado _InitClass (que se puede inspeccionar en el código de ejemplo) llamado desde el constructor del componente y ubicado en campos privados (que tienen un guión de subrayado como prefijo). Sin embargo, la parte interesante de este código es la creación del componente IDownloadable, que se muestra en la lista 5.

' Load the configuration file  
_curPath = Path.GetDirectoryName( _  
     [Assembly].GetExecutingAssembly().GetName().CodeBase)  
_xd = New XmlDocument  
_xd.Load(_curPath & Path.DirectorySeparatorChar & "SmartAppUpdater.xml")  
  
' Read the loader info and create the type  
xnl = _xd.GetElementsByTagName("Downloader")  
  
Dim downloader As String = xnl(0).FirstChild.Value.ToString  
Dim split() As Char = {","c}  
Dim d() As String = downloader.Split(split)  
Dim t As Type  
  
If d.Length = 2 Then ' Load from a different assembly  
    Dim a As [Assembly] = [Assembly].LoadFrom(_curPath&  
        Path.DirectorySeparatorChar & d(1))  
    t = a.GetType(d(0))  
Else  
    t = [Assembly].GetExecutingAssembly.GetType(d(0))  
End If  
  
_loader = CType(Activator.CreateInstance(t), IDownloadable)

Lista 5. Creación del componente IDownloadable

En este caso, se carga el archivo de configuración y se recupera el elemento Downloader del objeto XmlDocument. El nombre de tipo se extrae de la cadena junto con un nombre opcional de ensamblado. Si se encuentra el nombre del ensamblado, éste se carga utilizando el método LoadFrom (que asume que el ensamblado está en la carpeta actual) y el tipo creado. Si no se encuentra el nombre de un ensamblado, se asume que el tipo se puede encontrar en el ensamblado de ejecución. De cualquier forma, a continuación se crea una instancia del objeto utilizando el método CreateInstance de la clase System.Activator y se convierte de nuevo en un objeto IDownloadable. El componente utiliza a continuación la referencia a la variable _loader para descargar el manifiesto y las versiones nuevas de los archivos.

Detección y descarga de versiones nuevas

La primera responsabilidad del componente SmartAppUpdater es detectar versiones nuevas para la aplicación. Al igual que ocurre con los enfoques utilizados en las aplicaciones de Framework de escritorio, SmartAppUpdater busca un archivo de manifiesto en un servidor. Por ejemplo, la estructura del archivo de manifiesto tiene el siguiente aspecto.

&lt;?xml version="1.0" encoding="utf-8" ?>  
&lt;Application>  
&lt;Version>2.0.0.0&lt;/Version>  
&lt;Folder>2.0.0.0&lt;/Folder>  
&lt;Files>  
&lt;File>Atomic.CF.dll&lt;/File>  
&lt;File>NotificationOny.dll&lt;/File>  
&lt;File>SmartAppUpdater.dll&lt;/File>  
&lt;File>SmartAppUpdater.xml&lt;/File>  
&lt;File>TestUpdater.exe&lt;/File>  
&lt;/Files>  
&lt;/Application>

Este archivo especifica que la nueva versión disponible en el sitio Web es 2.0.0.0 y se ubica en la carpeta 2.0.0.0. Además, el elemento Files contiene los nombres de los archivos que se van a descargar.

Nota. una mejora para el componente SmartAppUpdater podría ser que permitiera especificar y descargar un único archivo .cab que incluyera todos los archivos nuevos. El componente IDownloadable ejecutaría el archivo .cab para extraer los archivos en un directorio específico. De este modo, se reduciría el uso de ancho de banda al realizar la actualización. Una segunda mejora sería agregar un atributo a los archivos que se deban instalar en la caché GAC. SmartAppUpdater podría, a continuación, instalar los archivos mediante programación, como se describió anteriormente en el artículo.

Cuando se llama al método StartUpdate (o BeginStartUpdate, que simplemente ejecuta el método StartUpdate en un objeto System.Threading.Thread nuevo), en primer lugar, el componente comprueba la presencia de una conexión de red utilizando el componente Network descrito en el artículo Testing for and Responding to Network Connections in the .NET Compact Framework. Si se encuentra una conexión, el componente llama al método privado CheckManifest que se muestra en la lista 6.

Private Function CheckManifest() As XmlNodeList  
       Dim newVersion As String  
       Dim manifest As XmlDocument  
       Dim xnl As XmlNodeList  
  
       Try  
           ' Try and download the manifest file if connected  
           _loader.GetServerFile(_curPath & Path.DirectorySeparatorChar _  
& "manifest.xml", _downloadLocation & "/manifest.xml")  
  
           ' Open the manifest  
           manifest = New XmlDocument  
           manifest.Load(_curPath & Path.DirectorySeparatorChar & _  
"manifest.xml")  
           ' Read the manifest  
           xnl = manifest.GetElementsByTagName("Version")  
           _newVersion = xnl(0).FirstChild.Value  
  
           ' Check the versions  
           If String.Compare(_newVersion, _appVersion) > 0 Then  
               ' Get the new folder  
              xnl = manifest.GetElementsByTagName("Folder")  
               _serverFolder = xnl(0).FirstChild.Value  
               Return manifest.GetElementsByTagName("File")  
           Else  
               Return Nothing  
           End If  
       Catch e As Exception  
           ' Couldn't get manifest so just go quietly  
           Me.LogMessage(e)  
           Return Nothing  
       End Try  
End Function

Lista 6. Comprobación del archivo de manifiesto en el servidor

El método que se muestra en la lista 6 utiliza la clase IDownloadable a la que hace referencia _loader para descargar el manifiesto y cargarlo en un objeto XmlDocument. A continuación, extrae el número de versión y lo compara con el número encontrado en el archivo de configuración SmartAppUpdater.xml. Aunque la información de versión del ensamblado de la aplicación se puede obtener interrogando a la propiedad Version del objeto AssemblyName recuperado del objeto System.Reflection.Assembly, decidimos situar el número de versión en los archivos de configuración por razones de facilidad de uso y flexibilidad.

Si el método CheckManifest determina que la versión de la aplicación en el servidor es mayor que la versión del dispositivo inteligente, dicho método devuelve XmlNodeList, que contiene los archivos que se van a descargar. Llegados a este punto, SmartAppUpdater sabe que la versión nueva de la aplicación está disponible para que el método StartUpdate continúe, y debe crear una carpeta nueva en el dispositivo para comenzar la descarga de los archivos, como se muestra en la lista 7.

Try  
    ' Create a new directory if needed  
   _newPath = _rootPath & Path.DirectorySeparatorChar & _newVersion  
    If Not Directory.Exists(_newPath) Then  
         Directory.CreateDirectory(_newPath)  
    End If  
  
    ' Walk through the files and download  
    Dim node As XmlNode  
    For Each node In xnl  
        Dim newfile As String = _newPath & Path.DirectorySeparatorChar _  
& node.FirstChild.Value  
_loader.GetServerFile(newfile, _downloadLocation & "/" _  
& _serverFolder & "/" & node.FirstChild.Value)  
    Next  
Catch e As Exception  
    Me.LogMessage(e)  
    ' Clean up  
    If Directory.Exists(_newPath) Then Directory.Delete(_newPath)  
If File.Exists(_curPath & Path.DirectorySeparatorChar & _  
"manifest.xml") Then File.Delete(_curPath & _  
Path.DirectorySeparatorChar & "manifest.xml")  
Throw New ApplicationException("Could not update application", e)  
End Try

Lista 7. Creación de una carpeta nueva y descarga de archivos

En la lista 7 se puede observar que primero se crea la carpeta nueva y, a continuación, se descarga cada archivo llamando al método GetServerFile del componente IDownloadable. Si se produce un error, la excepción queda registrada en un archivo de texto y se eliminan la carpeta nueva y el archivo de manifiesto.

Una vez que se ha descargado correctamente la versión nueva, se llama al método privado UpdateConfigs, que se puede examinar en el código de ejemplo, para actualizar el archivo SmartAppLoader.xml. A continuación, actualiza el elemento AppPath para señalar a la carpeta nueva de la aplicación e inserta un elemento OldAppPath que refleja el directorio de la aplicación actual si la propiedad KeepOld se define como False.

Generación de notificaciones

Durante el proceso de detección de una actualización y descarga de archivos, el componente SmartAppUpdater puede notificar a la aplicación generando eventos. El componente admite el evento Updating, que se activa cuando se detecta una versión nueva a través del archivo de manifiesto, el evento FileDownloaded que se activa tras llamar al método GetServerFile para descargar un archivo, y el evento Updated, que se activa cuando una actualización nueva se aplica correctamente.

Cada uno de estos eventos sigue el patrón de eventos documentado en el SDK de .NET Framework, pasando el objeto que activó el evento, SmartAppUpdater en este caso, así como un objeto UpdaterEventArgs que hereda de System.EventArgs, como se muestra a continuación.

Public Class UpdaterEventArgs : Inherits EventArgs  
    Public ServerPath As String  
    Public AppPath As String  
    Public AppName As String  
    Public NewVersion As String  
    Public CurrentFile As String  
End Class

Los argumentos de evento personalizados simplemente exponen campos públicos que incluyen la ruta desde la que se descargará la actualización, la ruta de aplicación de la versión nueva, el nombre de la aplicación, el número de la versión nueva y, en el caso del evento FileDownloaded, el archivo que se acaba de descargar. Por ejemplo, el método StartUpdate crea una instancia de un objeto UpdaterEventArgs y, a continuación, genera el evento de actualización tras comprobar el manifiesto y encontrar una versión nueva.

' Setup the event args  
args = New UpdaterEventArgs  
args.AppPath = _newPath  
args.NewVersion = _newVersion  
args.ServerPath = Me.DownloadLocation  
args.AppName = Me.AppName  
  
' Raise the updating event  
RaiseEvent Updating(Me, args)

Desde la aplicación se pueden agregar controladores para los eventos y notificar a los usuarios o tomar medidas específicas. Por ejemplo, en la lista 8, se crea una instancia de SmartAppUpdater en el método Sync al que se llama aparentemente desde un elemento de menú o un botón, y se asocia un controlador de eventos al evento de actualización. El controlador de eventos crea un objeto de notificación y agrega una notificación que proporciona una guía visual para el usuario indicando que se ha encontrado una versión nueva de la aplicación y se está descargando, como se muestra en la figura 7.

Nota. la clase Notification utiliza la API de notificación de Pocket PC, se compiló en un ensamblado independiente y la aplicación cliente hace referencia a ella. Este ensamblado se puede encontrar en el código de ejemplo de este artículo.

Private _updater As SmartAppUpdater  
  
Private Sub Sync()  
    _updater = New SmartAppUpdater()  
    AddHandler _updater.Updating, AddressOf UpdatingApp  
  
    ' Do your synchronization first  
  
    ' Now try to update the application asynchronously  
    _updater.BeginStartUpdate()  
End Sub  
  
Private Sub UpdatingApp(ByVal sender As Object, _  
 ByVal e As UpdaterEventArgs)  
        ' Show a notification when the application is being updated  
        Dim n As New Notification  
        n.Add("Updates found for " & e.AppName & " at " _  
& e.ServerPath, "Smart App Updater", UInt32.Parse(3))  
End Sub

Lista 8. Control de eventos desde SmartAppUpdater

Figura 7. Notificación al usuario

Procesamiento posterior

Uno de los requisitos más interesantes del componente SmartAppUpdater es que permite insertar código personalizado en el proceso de aplicación de actualizaciones. Este hecho resulta especialmente importante en las aplicaciones para dispositivos inteligentes, ya que éstas disponen normalmente de su propio almacén de datos, como una base de datos SQL Server CE o un conjunto de archivos XML en el directorio de la aplicación. A menudo, es necesario conservar estos datos, por lo que no se volverán a implementar en una versión nueva. Al disponer de una ubicación en la que poder ejecutar un método personalizado, se puede, por ejemplo, permitir que la aplicación compacte y copie la base de datos SQL Server CE en el directorio de la aplicación nueva tras haber aplicado una actualización.

Para facilitar el proceso, el ensamblado SmartAppUpdater incluye la interfaz simple IPostProcessor, que expone un método Process único que toma como su único argumento un objeto de tipo SmartAppUpdater.

Public Interface IPostProcessor  
    Sub Process(ByVal updater As SmartAppUpdater)  
End Interface

A continuación, se pueden crear procesadores posteriores personalizados mediante la implementación de la interfaz IPostProcessor, como es el caso de la clase que se muestra en la lista 8.

Public Class MyPostProcessor  
    Implements IPostProcessor  
  
    Public Sub Process(ByVal updater As _  
      Atomic.CF.Deployment.SmartAppUpdater) _  
      Implements Atomic.CF.Deployment.IPostProcessor.Process  
        ' Perform other logic here  
        updater.MoveFile("MyOtherFile.xml")  
updater.MoveFile("MyOtherFile2.xml")  
updater.LogMessage("Hello from My Post Processor")  
    End Sub  
End Class

Lista 9. Implementación de un componente IPostProcessor

Como se puede observar, el procesador posterior personalizado puede utilizar el componente SmartAppUpdater para llamar al método de ayuda MoveFile, que mueve un archivo de la carpeta anterior a la nueva, e incluso registrar mensajes en el registro de SmartAppUpdater utilizando el método sobrecargado LogMessage.

Para asociar un procesador posterior a SmartAppUpdater, se puede utilizar un constructor sobrecargado para pasar el componente.

Dim updater As New SmartAppUpdater(New MyPostProcessor)

Si es necesario admitir varios procesadores posteriores, SmartAppUpdater expone ArrayList a través de la propiedad PostProcessors, que se puede utilizar para agregar objetos.

En SmartAppUpdater, una vez aplicada la actualización y activado el evento Updated, el método StartUpdate enumera la lista ArrayList de PostProcessors que se convierte en la interfaz IPostProcessor y ejecuta sus métodos Process pasando una referencia a sí misma.

' Invoke the post processors  
Dim o As Object  
For Each o In _processors  
      If TypeOf (o) Is IPostProcessor Then  
         Dim p As IPostProcessor = CType(o, IPostProcessor)  
         p.Process(Me)  
      Else  
         _processors.Remove(o)  
      End If  
Next

Inicio de la versión nueva

Al utilizar un procesador posterior personalizado o controlar el evento Updated, la aplicación puede determinar que se ha descargado e instalado correctamente una versión nueva. Esto permite, por ejemplo, que el código solicite al usuario que reinicie la aplicación, o simplemente que un elemento de menú o botón indique las opciones de reinicio.

Para facilitar el reinicio de la aplicación, SmartAppUpdater expone un método StartNewVersion que utiliza la clase WindowsCE, que se muestra en la lista 1, para ejecutar el ejecutable SmartAppLoader. Debido a que el archivo SmartAppLoader.xml se ha actualizado, se iniciará la nueva versión de la aplicación.

Public Sub StartNewVersion()  
    WindowsCE.StartProcess(Me.RootPath & _  
Path.DirectorySeparatorChar & "SmartAppLoader.exe", ")  
End Sub

Para utilizar esto en el código, se puede llamar a este método e, inmediatamente, llamar a Application.Exit.

_updater.StartNewVersion()  
Application.Exit()

Limpieza de versiones anteriores

La última tarea que es preciso realizar consiste en quitar la versión anterior de la aplicación del dispositivo para ahorrar espacio. De esto se encarga el constructor de SmartAppLoader en la próxima ejecución de la aplicación, si en el archivo SmartAppLoader.xml se encuentra el elemento OldAppPath. Esta operación se realiza en este momento y no inmediatamente después de la actualización de SmartAppUpdater, ya que entonces se ejecuta la versión anterior de la aplicación y no se pueden eliminar los archivos.

Si se encuentra el elemento, la clase System.IO.Directory elimina la carpeta anterior.

' Read the old path if present  
xnl = xd.GetElementsByTagName("OldAppPath")  
If xnl.Count > 0 Then  
      Dim oldAppPath As String = xnl(0).FirstChild.Value  
      Try  
           If Directory.Exists(oldAppPath) _  
              Then   Directory.Delete(oldAppPath, True)  
             ' Clean up the file  
             xd.GetElementsByTagName( _  
              "SmartAppLoader")(0).RemoveChild(xnl(0))  
xd.Save(_curPath & Path.DirectorySeparatorChar & _  
              "SmartAppLoader.xml")  
      Catch ex As Exception  
          ' Could not clean up  
      End Try  
End If

Resumen

El software creado con .NET Compact Framework lanza varios retos a los desarrolladores en cuanto a empaquetado, implementación y mantenimiento. Esperamos que con las ideas y los recursos aportados en este artículo se disponga de mayor claridad en cuanto a las opciones disponibles y al uso de una solución que se adapte a los requisitos específicos de la aplicación en cuestión.

Mostrar: