Friday 1 July 2011

Migrando aplicaciones .Net de 32 a 64 bits

Hola a todos,
ya sé que no es un tema novedoso pero esta semana he estado migrando unas aplicaciones a 64 bits, y esto a hecho que me encuentre con algunas cosas interesantes que me gustaría compartir.
Lo primero que pensé cuando me asignaron la tarea fue ¿por qué tengo que revisar una aplicación .Net para que funcione en 64 bits si solamente generamos ensamblados que están en IL, no en código nativo? Bueno, en seguida me respondí a mi mismo, ya que, salvo que se haya compilado la aplicación con la opción, /clr:safe no seria realista pensar que cualquier aplicación se ejecutará de igual manera en arquitecturas de 64 o de 32 bits. Microsoft dice que se deben revisar las aplicaciones sobre todo si en nuestro código hacemos alguna de estas cosas: invocar APIs de la plataforma via p/invoke, invocar objetos COM, utilizar código no seguro, utilizar marshaling como mecanismo de intercambio de información o utilizar la serialización como una manera de persistir el estado.  Las dos primeras están a la orden del día así que será bastante difícil que no tengamos que hacer al menos una pequeña revisión en nuestro código.

Framework64

La principal restricción que nos encontramos cuando trabajamos con .Net Framework en un sistema operativo de 64 bits es que desde una aplicación  cargada en el entorno de 32 bits no se pueden cargar componentes de 64  bits y viceversa, si lo intentamos nos encontraremos con un  FileNotFoundException o BadImageFormatException dependiendo del tipo de  componente a cargar. Esto es así porque en los sistemas de 64 bits tenemos dos entornos de  ejecución desde la versión 2.0, el entorno que se ejecuta en 64 bits y  que se encuentra en el directorio %windir%\Microsoft.Net\Framework64 y  el de 32 bits que se ejecuta bajo el wow64 (Windows on windows) y está  en  %windir%\Microsoft.Net\Framework .

Ldr64

Para habilitar el entorno de 64 bits,  .Net Framework  crea la clave Enable64bits en el registro  de Windows que indica, si tiene el valor 1, que se utilizará este entorno y que, por lo tanto, los ensamblados sin marca, se intentarán cargar como  ensamblados de 64 bits. A la inversa, si lo que queremos es forzar que  los ensamblados compilados con "Any CPU" se ejecuten bajo el WoW,  podemos establecer este valor a 0. Para modificar este valor, mejor que  hacerlo a mano, podemos utilizar la utilidad ldr64.exe que se encuentra  en el directorio Framework64. Con esto conseguimos además que se aplique inmediatamente la configuración  seleccionada y que los ensamblados del .Net Framework que se utilicen sean los de la plataforma indicada.
La utilidad ldr64 tiene una sintaxis muy sencilla, ya que  solo se le pueden indicar los parámetros query, setwow o set64, que son  autoexplicativos. No he encontrado en la Web mucha documentación de esta utilidad y no sé  de su robustez más allá de para ser usada por programadores pero podría ser interesante utilizarla en entornos de producción que dispongan de un sistema operativo de 64 bits pero con muchos ensamblados con dependencias en 32 bits, de esta manera no sería necesario recompilarlos.

CorFlags

 Una vez instalados ambos frameworks, cuando se ejecuta una aplicación, el CLR debe decidir en
cual de estos  dos entornos se cargará el proceso y para eso utiliza unos flags que se establecen en el PE del ejecutable (portable execution header). Para  poder ver estos flags sobre un ensamblado e incluso modificarlos sin tener que recompilar, aunque  Microsoft no lo recomienda, tenemos la utilidad corflags.exe,  disponible con el Visual Studio. Cuidado con los ensamblados firmados con un strong name, porque si cambiamos los flags con esta utilidad tendremos que volverlos a firmar.

Hay que tener en cuenta que  cuando seleccionamos el tipo de plataforma  en Visual Studio, principalmente estamos definiendo el valor de algunos de estos flags, concretamente el flag 32BIT. Este flag funciona de una manera un tanto peculiar ya que  establecerlo indica que, en un SO de 64 bits, se ejecutará bajo el  wow64, mientras que no establecerlo indica que el CLR podrá cargarlo como ensamblado de 64 bits si así lo desea.


Aquí muestro los flags que se establecen con cada configuración:
Plataforma
PE
32BIT
anycpu
PE32
0
x86
PE32
1
x64
PE32+
0

 Para quien no lo sepa, podemos descubrir que procesos se ejecutan en 32 bits simplemente mirando en la pestaña Procesos del administrador de tareas, los procesos que se ejecutan bajo el wow64 tienen añadido *32 al final del nombre. También podemos utilizar el método Module.GetPEKind para comprobar programáticamente la configuración de un ensamblado. Mientras que en tiempo de ejecución podemos consultar la propiedad IntPtr.Size (4 en 32 bits, 8 en 64). Precisamente, uno de los problemas habituales al migrar código a 64 bits que hace llamadas código nativo es utilizar el tipo de datos int en lugar de la clase IntPtr cuando se hace referencia a punteros, claro que si realizamos este tipo de prácticas deberíamos usar la clase SafeHandle, pero este tema se merece un Post entero así que ya lo comentaremos en otra ocasión.

GAC_64

Al principio del Post comenté que podríamos encontrarnos con una FileNotFoundException, esta excepción puede  lanzarse a causa del algoritmo de búsqueda del ensamblado ya que al instalar  el framework de 64 bits, igual que se crea una carpeta Framework64, también  se crea una nueva carpeta gac_64 bajo el directorio  c:\windows\assembly. Esta carpeta, que contendrá los ensamblados  específicos de 64 bits, se añade a las ya existentes: gac (para  frameworks 1.0 y 1.1), gac_32 y gac_msil (para ensamblados "platform  agnostic"). Dentro de c:\windows\assembly también observaremos que, si hemos generado imágenes nativas para nuestro ensamblados mediante la utilidad ngen, éstas estarán instaladas en carpetas del tipo NativeImages_%framework_version%_%platform%, es decir, por ejemplo, NativeImages_v2.0.50727_64.

Así pues, cuando el CLR busque un ensamblado, primero  buscará en el directorio propio de la plataforma y luego en gac_msil.  Esto puede ocasionar que no se encuentre el ensamblado en el caso de que  la aplicación se esté ejecutando bajo el contexto de 64 bits y el  ensamblado necesario se encuentre en el directorio gac_32 o a la  inversa.  Esta casuística se puede ver muy claramente gracias a utilidades como Process Monitor o Fuslogvw, que nos muestra los archivos que nuestra aplicación ha intentado abrir sin éxito.
Mas info en los siguientes enlaces:
Corflags.exe
How to switch between the 32-bit versions of ASP.NET 1.1 and the 64-bit version  of ASP.NET 2.0

on a 64-bit version of Windows
Migrating 32-bit Managed Code to 64-bit
64-bit Applications
Run 32 bit .NET applications on 64 bit machines
Moving from 32-bit to 64-bit application development on .NET Framework
Hablan del ldr64 pero para ponerlo en set64

1 comment:

Eduardo Ferrón said...

Es un tema muy interesante. Un par de años atrás tuve que hacer algo similar en el trabajo, recuerdo que tuvimos que contactar algunos proveedores para conseguir controladores que corrieran a 64 bits.

Saludos