Compartir a través de


Migración de código administrado de 32 bits a 64 bits

 

Microsoft Corporation

Actualizado en mayo de 2005

Se aplica a:
   Microsoft .NET
   Microsoft .NET Framework 2.0

Resumen: Descubra qué implica migrar aplicaciones administradas de 32 bits a 64 bits, problemas que pueden afectar a la migración y las herramientas que están disponibles para ayudarle. (17 páginas impresas)

Contenido

Introducción
Código administrado en un entorno de 32 bits
Escriba clR para el entorno de 64 bits.
Migración e invocación de plataforma
Migración e interoperabilidad COM
Migración y código no seguro
Migración y serialización
Migración y serialización
Resumen

Introducción

En estas notas del producto se describe lo siguiente:

  • Qué implica la migración de aplicaciones administradas de 32 bits a 64 bits
  • Los problemas que pueden afectar a la migración
  • Qué herramientas están disponibles para ayudarle

Esta información no está pensada para ser prescriptiva; sino que está pensado para familiarizarse con las distintas áreas que son susceptibles a problemas durante el proceso de migración a 64 bits. En este momento no hay ningún "libro de recetas" específico de los pasos que puede seguir y asegurarse de que el código funcionará en 64 bits. La información contenida en estas notas del producto le familiarizará con los diferentes problemas y lo que se debe revisar.

Como pronto verá, si el ensamblado administrado no es código seguro de tipo 100 %, deberá revisar la aplicación y sus dependencias para determinar los problemas con la migración a 64 bits. Muchos de los elementos que leerá en las secciones siguientes se pueden abordar a través de cambios de programación. En varios casos, también tendrá que reservar tiempo para actualizar el código con el fin de ejecutarse correctamente en entornos de 32 y 64 bits, si quiere que se ejecute en ambos.

Microsoft .NET es un conjunto de tecnologías de software para conectar información, personas, sistemas y dispositivos. Desde su versión 1.0 en 2002, las organizaciones han realizado correctamente la implementación de . Soluciones basadas en NET, ya sean integradas, por proveedores de software independientes (ISV) o alguna combinación. Hay varios tipos de aplicaciones .NET que insertan los límites del entorno de 32 bits. Estos desafíos incluyen, entre otros, la necesidad de una memoria direccionable más real y la necesidad de aumentar el rendimiento de punto flotante. x64 e Itanium ofrecen un mejor rendimiento para las operaciones de punto flotante que puede obtener en x86. Sin embargo, también es posible que los resultados que obtenga en x64 o Itanium sean diferentes de los resultados que obtiene en x86. La plataforma de 64 bits tiene como objetivo ayudar a solucionar estos problemas.

Con el lanzamiento de .NET Framework versión 2.0, Microsoft incluye compatibilidad con código administrado que se ejecuta en las plataformas x64 e Itanium de 64 bits.

El código administrado es simplemente "código" que proporciona suficiente información para permitir que Common Language Runtime (CLR) de .NET proporcione un conjunto de servicios principales, entre los que se incluyen:

  • Autodescripciones del código y los datos a través de metadatos
  • Recorrido de la pila
  • Seguridad
  • Recolección de elementos no utilizados
  • Compilación Just-In-Time

Además del código administrado, hay otras definiciones que son importantes para comprender a medida que investiga los problemas de migración.

Datos administrados: datos asignados en el montón administrado y recopilados a través de la recolección de elementos no utilizados.

Ensamblado: la unidad de implementación que permite a CLR comprender completamente el contenido de una aplicación y aplicar las reglas de control de versiones y dependencia definidas por la aplicación.

Código seguro de tipos: código que solo usa datos administrados y ningún tipo de datos no comprobable o operaciones de conversión o coerción de tipos de datos no admitidos (es decir, uniones no discriminadas o punteros de estructura o interfaz). C#, Visual Basic .NET y código de Visual C++ compilado con /clr:safe generan código seguro de tipos.

Código no seguro: código que puede realizar estas operaciones de nivel inferior como declarar y operar en punteros, realizar conversiones entre punteros y tipos enteros y tomar la dirección de las variables. Estas operaciones permiten interactuar con el sistema operativo subyacente, acceder a un dispositivo asignado a memoria o implementar un algoritmo crítico para el tiempo. El código nativo no es seguro.

Código administrado en un entorno de 32 bits

Para comprender las complejidades implicadas en la migración del código administrado al entorno de 64 bits, vamos a revisar cómo se ejecuta el código administrado en un entorno de 32 bits.

Cuando se selecciona una aplicación, administrada o no administrada, se selecciona para ejecutarse, se invoca al cargador de Windows y es responsable de decidir cómo cargar y, a continuación, ejecutar la aplicación. Parte de este proceso implica inspeccionar dentro del encabezado de ejecución portátil (PE) del ejecutable para determinar si se requiere CLR. Como es posible que ya haya adivinado, hay marcas en el PE que indican código administrado. En este caso, el cargador de Windows inicia clR que, a continuación, es responsable de cargar y ejecutar la aplicación administrada. (Se trata de una descripción simplificada del proceso, ya que hay muchos pasos implicados, incluida la determinación de la versión de CLR que se va a ejecutar, la configuración del "espacio aislado" de AppDomain, etc.).

Interoperabilidad

A medida que se ejecuta la aplicación administrada, puede interactuar (suponiendo que los permisos de seguridad adecuados) interactúen con las API nativas (incluida la API win32) y los objetos COM a través de las funcionalidades de interoperabilidad de CLR. Tanto si se llama a una API de plataforma nativa, como si se realiza una solicitud COM o se serializa una estructura, cuando se ejecuta completamente dentro del entorno de 32 bits, el desarrollador está aislado de tener que pensar en los tamaños de tipo de datos y la alineación de los datos.

Al considerar la migración a 64 bits, será esencial investigar qué dependencias tiene la aplicación.

Escriba clR para el entorno de 64 bits.

Para que el código administrado se ejecute en el entorno de 64 bits coherente con el entorno de 32 bits, el equipo de .NET desarrolló Common Language Runtime (CLR) para los sistemas Itanium y x64 de 64 bits. ClR tenía que cumplir estrictamente las reglas de Common Language Infrastructure (CLI) y Common Language Type System para asegurarse de que el código escrito en cualquiera de los lenguajes .NET podría interoperar como lo hacen en el entorno de 32 bits. Además, la siguiente es una lista de algunas de las otras piezas que también tenían que portado o desarrollado para el entorno de 64 bits:

  • Bibliotecas de clases base (System.*)
  • Compilador Just-In-Time
  • Compatibilidad con la depuración
  • SDK de .NET Framework

Compatibilidad con código administrado de 64 bits

La versión 2.0 de .NET Framework admite los procesadores Itanium y x64 de 64 bits que ejecutan:

  • Windows Server 2003 SP1
  • Versiones futuras del cliente de Windows 64 bits

(No se puede instalar .NET Framework versión 2.0 en Windows 2000. Los archivos de salida generados con las versiones 1.0 y 1.1 de .NET Framework se ejecutarán en WOW64 en un sistema operativo de 64 bits).

Al instalar .NET Framework versión 2.0 en la plataforma de 64 bits, no solo va a instalar toda la infraestructura necesaria para ejecutar el código administrado en modo de 64 bits, sino que va a instalar la infraestructura necesaria para que el código administrado se ejecute en el subsistema de Windows en Windows o WoW64 (modo de 32 bits).

Una migración simple de 64 bits

Considere la posibilidad de una aplicación .NET que sea código seguro de tipo 100 %. En este escenario, es posible tomar el ejecutable de .NET que se ejecuta en la máquina de 32 bits y moverlo al sistema de 64 bits y hacer que se ejecute correctamente. ¿Por qué funciona esto? Dado que el ensamblado es 100 % seguro de tipos sabemos que no hay dependencias en el código nativo o en los objetos COM y que no hay código "no seguro", lo que significa que la aplicación se ejecuta completamente bajo el control de CLR. CLR garantiza que, aunque el código binario que se genera como resultado de la compilación Just-In-Time (JIT) será diferente entre 32 y 64 bits, el código que se ejecuta será semánticamente el mismo. (No se puede instalar .NET Framework versión 2.0 en Windows 2000. Los archivos de salida generados con las versiones 1.0 y 1.1 de .NET Framework se ejecutarán en WOW64 en un sistema operativo de 64 bits).

En realidad, el escenario anterior es un poco más complicado desde la perspectiva de cargar la aplicación administrada. Como se explicó en la sección anterior, el cargador de Windows es responsable de decidir cómo cargar y ejecutar la aplicación. Sin embargo, a diferencia del entorno de 32 bits, ejecutarse en una plataforma Windows de 64 bits significa que hay dos (2) entornos donde se podría ejecutar la aplicación, ya sea en el modo nativo de 64 bits o en WoW64.

El cargador de Windows ahora tiene que tomar decisiones en función de lo que detecta en el encabezado PE. Como podría haber adivinado, hay marcas que se pueden establecer en el código administrado que ayudan con este proceso. (Consulte corflags.exe para mostrar la configuración en un PE). La siguiente lista representa información que se encuentra en el PE que ayuda en el proceso de toma de decisiones.

  • 64 bits: indica que el desarrollador ha creado el ensamblado específicamente destinado a un proceso de 64 bits.
  • 32 bits: indica que el desarrollador ha creado el ensamblado específicamente destinado a un proceso de 32 bits. En esta instancia, el ensamblado se ejecutará en WoW64.
  • Independiente: indica que el desarrollador creó el ensamblado con Visual Studio 2005, denominado "Whidbey". o las herramientas posteriores y que el ensamblado se puede ejecutar en modo de 64 o 32 bits. En este caso, el cargador de Windows de 64 bits ejecutará el ensamblado en 64 bits.
  • Heredado: indica que las herramientas que crearon el ensamblado eran "pre-Whidbey". En este caso concreto, el ensamblado se ejecutará en WoW64.

Nota También hay información en el PE que indica al cargador de Windows si el ensamblado está destinado a una arquitectura específica. Esta información adicional garantiza que los ensamblados destinados a una arquitectura determinada no se carguen en otro.

Los compiladores de C#, Visual Basic .NET y C++ Whidbey permiten establecer las marcas adecuadas en el encabezado PE. Por ejemplo, C# y THIRD tienen una opción del compilador /platform:{anycpu, x86, Itanium, x64} .

Nota Aunque técnicamente es posible modificar las marcas en el encabezado PE de un ensamblado una vez compilado, Microsoft no recomienda hacerlo.

Si tiene curiosidad por saber cómo se establecen estas marcas en un ensamblado administrado, puede ejecutar la utilidad ILDASM proporcionada en el SDK de .NET Framework. En la ilustración siguiente se muestra una aplicación "heredada".

Tenga en cuenta que un desarrollador que marca un ensamblado como Win64 determinó que todas las dependencias de la aplicación se ejecutarían en modo de 64 bits. Un proceso de 64 bits no puede usar un componente de 32 bits en proceso (y un proceso de 32 bits no puede cargar un componente de 64 bits en proceso). Tenga en cuenta que la posibilidad de que el sistema cargue el ensamblado en un proceso de 64 bits no significa automáticamente que se ejecute correctamente.

Por lo tanto, ahora sabemos que una aplicación formada por código administrado que es 100 % segura de tipos se puede copiar (o implementar xcopy ) en una plataforma de 64 bits y hacer que JIT y se ejecute correctamente con .NET en modo de 64 bits.

Sin embargo, a menudo vemos situaciones que no son ideales, y eso nos lleva al enfoque principal de este documento, que es aumentar el conocimiento de los problemas relacionados con la migración.

Puede tener una aplicación que no sea segura del 100 % de tipos y que siga siendo capaz de ejecutarse correctamente en .NET de 64 bits. Será importante que examine detenidamente la aplicación, teniendo en cuenta los posibles problemas descritos en las secciones siguientes y determine si puede o no ejecutarse correctamente en 64 bits.

Migración e invocación de plataforma

Hacer uso de las funcionalidades de invocación de plataforma (o p/invoke) de .NET hace referencia al código administrado que realiza llamadas a código no administrado o nativo. En un escenario típico, este código nativo es una biblioteca de vínculos dinámicos (DLL) que forma parte del sistema (API de Windows, etc.), parte de la aplicación o una biblioteca de terceros.

El uso de código no administrado no significa explícitamente que una migración a 64 bits tenga problemas; en su lugar, se debe considerar un indicador de que se requiere investigación adicional.

Tipos de datos en Windows

Cada aplicación y cada sistema operativo tiene un modelo de datos abstracto. Muchas aplicaciones no exponen explícitamente este modelo de datos, pero el modelo guía la forma en que se escribe el código de la aplicación. En el modelo de programación de 32 bits (conocido como modelo ILP32), los tipos de datos enteros, largos y punteros tienen una longitud de 32 bits. La mayoría de los desarrolladores han usado este modelo sin darse cuenta.

En Microsoft Windows de 64 bits, esta suposición de paridad en tamaños de tipo de datos no es válida. La creación de todos los tipos de datos de 64 bits de longitud desperdiciaría espacio, ya que la mayoría de las aplicaciones no necesitan el mayor tamaño. Sin embargo, las aplicaciones necesitan punteros a datos de 64 bits y necesitan la capacidad de tener tipos de datos de 64 bits en los casos seleccionados. Estas consideraciones llevaron al equipo de Windows a seleccionar un modelo de datos abstracto denominado LLP64 (o P64). En el modelo de datos LLP64, solo los punteros se expanden a 64 bits; todos los demás tipos de datos básicos (entero y largo) permanecen de 32 bits de longitud.

.NET CLR para plataformas de 64 bits usa el mismo modelo de datos abstracto de LLP64. En .NET hay un tipo de datos entero, no ampliamente conocido, que se designa específicamente para contener información de "puntero": IntPtr cuyo tamaño depende de la plataforma (por ejemplo, 32 o 64 bits) en el que se ejecuta. Tenga en cuenta el fragmento de código siguiente:

[C#]
public void SizeOfIntPtr() {
Console.WriteLine( "SizeOf IntPtr is: {0}", IntPtr.Size );
}

Cuando se ejecute en una plataforma de 32 bits, obtendrá la siguiente salida en la consola:

SizeOf IntPtr is: 4

En una plataforma de 64 bits, obtendrá la siguiente salida en la consola:

SizeOf IntPtr is: 8

Nota Si desea comprobar en tiempo de ejecución si se ejecuta o no en un entorno de 64 bits, puede usar IntPtr.Size como una manera de realizar esta determinación.

Consideraciones de migración

Al migrar aplicaciones administradas que usan p/invoke, tenga en cuenta los siguientes elementos:

  • Disponibilidad de una versión de 64 bits del archivo DLL
  • Uso de tipos de datos

Disponibilidad

Una de las primeras cosas que debe determinarse es si el código no administrado en el que la aplicación tiene una dependencia está disponible para 64 bits.

Si este código se desarrolló internamente, aumenta la capacidad de éxito. Por supuesto, todavía tendrá que asignar recursos para migrar el código no administrado a 64 bits junto con los recursos adecuados para pruebas, control de calidad, etc. (Esta notas del producto no está realizando recomendaciones sobre los procesos de desarrollo; en su lugar, está intentando señalar que es posible que los recursos deba asignarse a tareas al código de puerto).

Si este código procede de un tercero, deberá investigar si este tercero ya tiene el código disponible para 64 bits y si el tercero estaría dispuesto a ponerlo a disposición.

Se producirá el problema de mayor riesgo si el tercero ya no proporciona soporte técnico para este código o si el tercero no está dispuesto a realizar el trabajo. Estos casos requieren una investigación adicional sobre las bibliotecas disponibles que realizan una funcionalidad similar, si el tercero permitirá al cliente realizar el propio puerto, etc.

Es importante tener en cuenta que una versión de 64 bits del código dependiente puede tener firmas de interfaz modificadas que pueden significar un trabajo de desarrollo adicional y resolver las diferencias entre las versiones de 32 y 64 bits de la aplicación.

Tipos de datos

El uso de p/invoke requiere que el código desarrollado en .NET declare un prototipo del método de destino del código administrado. Dada la siguiente declaración de C:

[C++]
typedef void * HANDLE
HANDLE GetData();

A continuación se muestran ejemplos de métodos prototipos:

[C#]

[DllImport( "sampleDLL", CallingConvention=CallingConvention.Cdecl )]
      public static extern int DoWork( int x, int y );

[DllImport( "sampleDLL", CallingConvention=CallingConvention.Cdecl )]
      public unsafe static extern int GetData();

Vamos a revisar estos ejemplos con un ojo hacia los problemas de migración de 64 bits:

El primer ejemplo llama al método DoWork pasando dos (2) enteros de 32 bits y esperamos que se devuelva un entero de 32 bits. Aunque estamos ejecutando en una plataforma de 64 bits, un entero sigue siendo de 32 bits. En este ejemplo concreto no hay nada que impida nuestros esfuerzos de migración.

El segundo ejemplo requiere algunos cambios en el código para ejecutarse correctamente en 64 bits. Lo que estamos haciendo aquí es llamar al método GetData y declarar que esperamos que se devuelva un entero, pero donde la función devuelve realmente un puntero int. En este documento se encuentra nuestro problema: recuerde que los enteros son de 32 bits, pero en punteros de 64 bits son de 8 bytes. Como resulta, un poco de código en el mundo de 32 bits se escribió suponiendo que un puntero y un entero tenían la misma longitud, 4 bytes. En el mundo de 64 bits, esto ya no es cierto.

En este último caso, el problema se puede resolver cambiando la declaración de método para usar un IntPtr en lugar del int.

public unsafe static extern IntPtr GetData();

Realizar este cambio funcionará en los entornos de 32 y 64 bits. Recuerde que IntPtr es específico de la plataforma.

El uso de p/invoke en la aplicación administrada no significa que no sea posible migrar a la plataforma de 64 bits. Tampoco significa que habrá problemas. Lo que significa es que debe revisar las dependencias en código no administrado que tiene la aplicación administrada y determinar si habrá algún problema.

Migración e interoperabilidad COM

La interoperabilidad COM es una capacidad asumida de la plataforma .NET. Al igual que en la explicación anterior sobre la invocación de plataforma, hacer uso de la interoperabilidad COM significa que el código administrado realiza llamadas a código no administrado. Sin embargo, a diferencia de la invocación de plataforma, la interoperabilidad COM también significa tener la capacidad de que el código no administrado llame al código administrado como si fuera un componente COM.

Una vez más, el uso de código COM no administrado no significa que una migración a 64 bits tenga problemas; en su lugar, se debe considerar un indicador de que se requiere investigación adicional.

Consideraciones de migración

Es importante comprender que, con la versión 2.0 de .NET Framework, no hay compatibilidad con la interoperabilidad entre arquitecturas. Para ser más conciso, no puede usar la interoperabilidad COM entre 32 y 64 bits en el mismo proceso. Pero puede usar la interoperabilidad COM entre 32 y 64 bits si tiene un servidor COM fuera de proceso. Si no puede usar un servidor COM fuera de proceso, querrá marcar el ensamblado administrado como Win32 en lugar de Win64 o Independiente para que el programa se ejecute en WoW64 para que pueda interoperar con el objeto COM de 32 bits.

A continuación se explica las distintas consideraciones que se deben tener en cuenta para usar la interoperabilidad COM en la que el código administrado realiza llamadas COM en un entorno de 64 bits. Concretamente, puede:

  • Disponibilidad de una versión de 64 bits del archivo DLL
  • Uso de tipos de datos
  • Bibliotecas de tipos

Disponibilidad

La explicación de la sección p/invoke sobre la disponibilidad de una versión de 64 bits del código dependiente también es relevante para esta sección.

Tipos de datos

La explicación de la sección p/invoke sobre los tipos de datos de una versión de 64 bits del código dependiente también es relevante para esta sección.

Bibliotecas de tipos

A diferencia de los ensamblados, las bibliotecas de tipos no se pueden marcar como 'neutral'; deben marcarse como Win32 o Win64. Además, la biblioteca de tipos debe estar registrada para cada entorno en el que se ejecutará el COM. Use tlbimp.exe para generar un ensamblado de 32 o 64 bits desde una biblioteca de tipos.

El uso de la interoperabilidad COM en la aplicación administrada no significa que no sea posible migrar a la plataforma de 64 bits. Tampoco significa que habrá problemas. Lo que significa es que debe revisar las dependencias que tiene la aplicación administrada y determinar si habrá algún problema.

Migración y código no seguro

El lenguaje C# principal difiere notablemente de C y C++ en su omisión de punteros como un tipo de datos. En su lugar, C# proporciona referencias y la capacidad de crear objetos administrados por un recolector de elementos no utilizados. En el lenguaje C# principal, simplemente no es posible tener una variable no inicializada, un puntero "pendiente" o una expresión que indexa una matriz más allá de sus límites. Por lo tanto, se eliminan todas las categorías de errores que atormentan de forma rutinaria los programas de C y C++.

Aunque prácticamente todas las construcciones de tipo de puntero en C o C++ tienen un homólogo de tipo de referencia en C#, hay situaciones en las que el acceso a los tipos de puntero se convierte en una necesidad. Por ejemplo, la interacción con el sistema operativo subyacente, el acceso a un dispositivo asignado a memoria o la implementación de un algoritmo crítico para el tiempo puede no ser posible o práctico sin acceso a punteros. Para solucionar esta necesidad, C# proporciona la capacidad de escribir código no seguro.

En código no seguro, es posible declarar y operar en punteros, realizar conversiones entre punteros y tipos enteros, tomar la dirección de las variables, etc. En cierto sentido, escribir código no seguro es muy parecido a escribir código de C en un programa de C#.

De hecho, el código no seguro es una característica "segura" desde la perspectiva de los desarrolladores y los usuarios. El código no seguro debe marcarse claramente con el modificador unsafe, por lo que los desarrolladores no pueden usar características no seguras accidentalmente.

Consideraciones de migración

Para analizar los posibles problemas con el código no seguro, vamos a explorar el ejemplo siguiente. Nuestro código administrado realiza llamadas a un archivo DLL no administrado. En concreto, hay un método denominado GetDataBuffer que devuelve 100 elementos (en este ejemplo, se devuelve un número fijo de elementos). Cada uno de estos elementos consta de un entero y un puntero. El código de ejemplo siguiente es un extracto del código administrado que muestra la función no segura responsable de controlar estos datos devueltos.

[C#]

public unsafe int UnsafeFn() {
   IntPtr * inputBuffer = sampleDLL.GetDataBuffer();
   IntPtr * ptr = inputBuffer;
   int   result = 0;

   for ( int idx = 0; idx < 100; idx ++ ) {
      // Add 'int' from DLL to our result
      result = result + ((int) *ptr);

// Increment pointer over int (
      ptr = (IntPtr*)( ( (byte *) ptr ) + sizeof( int ) );

      // Increment pointer over pointer (
      ptr = (IntPtr*)( ( (byte *) ptr ) + sizeof( int ) );
   }
   return result;
}

Nota Este ejemplo concreto podría haberse logrado sin el uso de código no seguro. Más concretamente, hay otras técnicas como serialización que podrían haberse utilizado. Pero para este propósito estamos usando código no seguro.

UnsafeFn recorre en bucle los 100 elementos y suma los datos enteros. A medida que se recorre un búfer de datos, el código debe recorrer paso a paso por integer y el puntero. En el entorno de 32 bits, este código funciona bien. Sin embargo, como hemos explicado anteriormente, los punteros son de 8 bytes en el entorno de 64 bits y, por lo tanto, el segmento de código (que se muestra a continuación) no funcionará correctamente, ya que está haciendo uso de una técnica de programación común, por ejemplo, tratar un puntero como equivalente a un entero.

// Increment pointer over pointer (
ptr = (IntPtr*)( ( (byte *) ptr ) + sizeof( int ) );

Para que este código funcione tanto en el entorno de 32 bits como en el de 64 bits, sería necesario modificar el código a lo siguiente.

// Increment pointer over pointer (
ptr = (IntPtr*)( ( (byte *) ptr ) + sizeof( IntPtr ) );

Como acabamos de ver, hay instancias en las que es necesario usar código no seguro. En la mayoría de los casos, es necesario como resultado de la dependencia del código administrado en alguna otra interfaz. Independientemente de los motivos por los que existe código no seguro, debe revisarse como parte del proceso de migración.

El ejemplo anterior es relativamente sencillo y la corrección para hacer que el programa funcione en 64 bits era sencillo. Claramente hay muchos ejemplos de código no seguro que son más complejos. Algunos requerirán una revisión profunda y quizás retrocederán y replantearán el enfoque que usa el código administrado.

Para repetir lo que ya ha leído: el uso de código no seguro en la aplicación administrada no significa que la migración a la plataforma de 64 bits no será posible. Tampoco significa que habrá problemas. Lo que significa es que debe revisar todo el código no seguro que tiene la aplicación administrada y determinar si habrá algún problema.

Migración y serialización

Serialización proporciona una colección de métodos para asignar memoria no administrada, copiar bloques de memoria no administrados y convertir administrados a tipos no administrados, así como otros métodos varios que se usan al interactuar con código no administrado.

La serialización se manifiesta a través de la clase Marshal de .NET. Los métodos estáticos o compartidos en Visual Basic, definidos en la clase Marshal son esenciales para trabajar con datos no administrados. Los desarrolladores avanzados que crean serializadores personalizados que necesitan proporcionar un puente entre los modelos de programación administrados y no administrados suelen usar la mayoría de los métodos definidos.

Consideraciones de migración

La serialización plantea algunos de los desafíos más complejos asociados a la migración de aplicaciones a 64 bits. Dada la naturaleza de lo que el desarrollador intenta lograr con la serialización, es decir, la transferencia de información estructurada a, desde o hacia y desde código administrado y no administrado, veremos que estamos proporcionando información, a veces de bajo nivel, para ayudar al sistema.

En términos de diseño hay dos declaraciones específicas que el desarrollador puede realizar; Normalmente, estas declaraciones se realizan mediante el uso de atributos de codificación.

LayoutKind.Sequential

Vamos a revisar la definición tal y como se proporciona en la Ayuda del SDK de .NET Framework:

"Los miembros del objeto se disponen secuencialmente, en el orden en que aparecen cuando se exportan a la memoria no administrada. Los miembros se disponen según el empaquetado especificado en StructLayoutAttribute.Pack, y pueden ser no ambiguos".

Se nos dice que el diseño es específico del orden en que se define. A continuación, todo lo que debemos hacer es asegurarnos de que las declaraciones administradas y no administradas sean similares. Pero también se nos dice que el empaquetado es un ingrediente crítico. En este momento, no le sorprenderá saber que, sin intervención explícita del desarrollador, hay un valor de paquete predeterminado. Como es posible que ya haya adivinado, el valor de paquete predeterminado no es el mismo entre sistemas de 32 y 64 bits.

La instrucción de la definición con respecto a los miembros no contiguos se refiere al hecho de que, dado que hay tamaños de paquete predeterminados, los datos que se disponen en la memoria pueden no estar en byte 0, byte 1, byte2, etc. En su lugar, el primer miembro estará en byte 0, pero el segundo miembro podría estar en byte 4. El sistema realiza este empaquetado predeterminado para permitir que la máquina obtenga acceso a los miembros sin tener que lidiar con problemas de alineación incorrecta.

Este es un área que necesitamos prestar mucha atención al empaquetado y, al mismo tiempo, intentar permitir que el sistema actúe en su modo preferido.

A continuación se muestra un ejemplo de una estructura tal como se define en código administrado, así como la estructura correspondiente definida en código no administrado. Debe tener cuidado de cómo este ejemplo muestra cómo se establece el valor del paquete en ambos entornos.

[C#]
[StructLayout(LayoutKind.Sequential, Pack=1)]
public class XYZ {
      public byte arraysize = unchecked((byte)-1);
      [MarshalAs(UnmanagedType.ByValArray, SizeConst=52)]
      public int[] padding = new int[13];
};
[unmanaged c++]
#pragma pack(1)
typedef struct{
      BYTE arraysize;      // = (byte)-1;
      int      padding[13];
} XYZ;

LayoutKind.Explicit

Vamos a revisar la definición tal como se proporciona en la Ayuda de .NET FrameworkSDK:

"La posición precisa de cada miembro de un objeto en memoria no administrada se controla explícitamente. Cada miembro debe usar FieldOffsetAttribute para indicar la posición de ese campo dentro del tipo".

Aquí se nos dice que el desarrollador proporcionará desplazamientos exactos para ayudar en la serialización de información. Por lo tanto, es esencial que el desarrollador especifique correctamente la información en el atributo FieldOffset .

¿Dónde están los posibles problemas? Teniendo en cuenta que los desplazamientos de campo se definen sabiendo el tamaño del tamaño del miembro de datos que continúa, es importante recordar que no todos los tamaños de tipo de datos son iguales entre 32 y 64 bits. En concreto, los punteros tienen una longitud de 4 o 8 bytes.

Ahora tenemos un caso en el que es posible que tengamos que actualizar el código fuente administrado para tener como destino los entornos específicos. En el ejemplo siguiente se muestra una estructura que incluye un puntero. Aunque hemos hecho que el puntero sea IntPtr, todavía hay una diferencia al pasar a 64 bits.

[C#]
[StructLayout(LayoutKind.Explicit)]
    internal struct FooValue {
        [FieldOffset(0)] public int dwType;
        [FieldOffset(4)] public IntPtr pType;
        [FieldOffset(8)] public int typeValue;
    }

Para 64 bits, tenemos que ajustar el desplazamiento de campo para el último miembro de datos de la estructura, ya que realmente comienza en desplazamiento 12 en lugar de 8.

[C#]
[StructLayout(LayoutKind.Explicit)]
    internal struct FooValue {
        [FieldOffset(0)] public int dwType;
        [FieldOffset(4)] public IntPtr pType;
        [FieldOffset(12)] public int typeValue;
    }

El uso de serialización es una realidad cuando se requiere una interoperabilidad compleja entre código administrado y no administrado. Hacer uso de esta eficaz funcionalidad no es un indicador de que puede migrar la aplicación de 32 bits al entorno de 64 bits. Sin embargo, debido a las complejidades asociadas al uso de serialización, se trata de un área en la que se requiere mucha atención a los detalles.

El análisis del código indicará si se requieren archivos binarios independientes para cada una de las plataformas y si también tendrá que realizar modificaciones en el código no administrado para solucionar problemas como el empaquetado.

Migración y serialización

La serialización es el proceso de convertir el estado de un objeto en un formato que se pueda almacenar o transportar. El complemento de serialización es deserialización, que convierte una secuencia en un objeto. Juntos, estos procesos permiten almacenar los datos y transferirlos con facilidad.

.NET Framework representa dos tecnologías serializando:

  • La serialización binaria conserva fidelidad de tipo, que es útil para conservar el estado de un objeto entre las invocaciones diferentes de una aplicación. Por ejemplo, puede compartir un objeto entre distintas aplicaciones si lo serializa en el Portapapeles. Puede serializar un objeto en una secuencia, un disco, la memoria, a través de la red, etc. .NET Remoting usa la serialización para pasar objetos "por valor" de un equipo o dominio de aplicación a otro.
  • La serialización XML serializa solo propiedades públicas y campos y no conserva la fidelidad de tipo. Esto es útil si se desea proporcionar o utilizar los datos sin restringir la aplicación que utiliza los datos. Dado que XML es un estándar abierto, es una opción atractiva para compartir los datos por el web. SOAP es igualmente un estándar abierto, que lo convierte en una opción atractiva.

Consideraciones de migración

Cuando pensamos en la serialización, debemos tener en cuenta lo que estamos intentando lograr. Una pregunta que debe tener en cuenta a medida que migre a 64 bits es si tiene previsto compartir información serializada entre las distintas plataformas. En otras palabras, leerá (o deserializará) la información administrada de 64 bits almacenada por una aplicación administrada de 32 bits.

La respuesta ayudará a impulsar la complejidad de la solución.

  • Es posible que quiera escribir sus propias rutinas de serialización para tener en cuenta las plataformas.
  • Es posible que quiera restringir el uso compartido de información, a la vez que permite que cada plataforma lea y escriba sus propios datos.
  • Es posible que quiera revisar lo que está serializando y realizar modificaciones para ayudar a evitar algunos de los problemas.

Entonces, después de todo eso, ¿cuáles son las consideraciones con respecto a la serialización?

  • IntPtr tiene una longitud de 4 o 8 bytes según la plataforma. Si serializa la información, va a escribir datos específicos de la plataforma en la salida. Esto significa que puede y experimentará problemas si intenta compartir esta información.

Si considera nuestra discusión en la sección anterior sobre serialización y desplazamientos, es posible que aparezca una pregunta o dos sobre cómo la serialización aborda la información de empaquetado. En el caso de la serialización binaria, .NET usa internamente el acceso correcto no asignado a la secuencia de serialización mediante lecturas basadas en bytes y el control correcto de los datos.

Como acabamos de ver, el uso de la serialización no impide la migración a 64 bits. Si usa la serialización XML, tiene que convertir tipos administrados nativos durante el proceso de serialización, aislando las diferencias entre las plataformas. El uso de la serialización binaria proporciona una solución más completa, pero crea la situación en la que se deben tomar decisiones sobre cómo las distintas plataformas comparten información serializada.

Resumen

La migración a 64 bits viene y Microsoft ha estado trabajando para realizar la transición de aplicaciones administradas de 32 bits a 64 bits lo más sencilla posible.

Sin embargo, no es realista suponer que uno solo puede ejecutar código de 32 bits en un entorno de 64 bits y hacer que se ejecute sin mirar lo que está migrando.

Como se mencionó anteriormente, si tiene código administrado seguro del 100 %, realmente puede copiarlo en la plataforma de 64 bits y ejecutarlo correctamente en CLR de 64 bits.

Pero lo más probable es que la aplicación administrada esté implicada en cualquiera de las siguientes acciones:

  • Invocación de API de plataforma a través de p/invoke
  • Invocación de objetos COM
  • Uso del código no seguro
  • Uso de la serialización como mecanismo para compartir información
  • Uso de la serialización como forma de conservar el estado

Independientemente de lo que haga la aplicación, será importante hacer la tarea e investigar lo que hace el código y las dependencias que tiene. Una vez que haga esta tarea, tendrá que examinar sus opciones para hacer lo siguiente o todos los siguientes:

  • Migre el código sin cambios.
  • Realice cambios en el código para controlar los punteros de 64 bits correctamente.
  • Trabaje con otros proveedores, etc., para proporcionar versiones de 64 bits de sus productos.
  • Realice cambios en la lógica para controlar el cálculo de referencias o la serialización.

Puede haber casos en los que tome la decisión de no migrar el código administrado a 64 bits, en cuyo caso tiene la opción de marcar los ensamblados para que el cargador de Windows pueda hacer lo correcto al iniciarse. Tenga en cuenta que las dependencias de bajada tienen un impacto directo en la aplicación general.

Fxcop

También debe tener en cuenta las herramientas que están disponibles para ayudarle en la migración.

En la actualidad, Microsoft tiene una herramienta denominada FxCop, que es una herramienta de análisis de código que comprueba los ensamblados de código administrado de .NET para que se ajusten a las directrices de diseño de Microsoft .NET Framework. Usa reflexión, análisis de MSIL y análisis de grafos de llamadas para inspeccionar ensamblados de más de 200 defectos en las siguientes áreas: convenciones de nomenclatura, diseño de biblioteca, localización, seguridad y rendimiento. FxCop incluye tanto la GUI como las versiones de línea de comandos de la herramienta, así como un SDK, para crear sus propias reglas. Para obtener más información, consulte el sitio web de FxCop . Microsoft está en proceso de desarrollar reglas adicionales de FxCop que le proporcionarán información para ayudarle en los esfuerzos de migración.

También hay funciones de biblioteca administradas para ayudarle en tiempo de ejecución a determinar en qué entorno se ejecuta.

  • System.IntPtr.Size: para determinar si se ejecuta en modo de 32 o 64 bits
  • System.Reflection.Module.GetPEKind: para consultar mediante programación un .exe o .dll para ver si está pensado para ejecutarse solo en una plataforma específica o en WOW64.

No hay ningún conjunto específico de procedimientos para abordar todos los desafíos que podría enfrentar. Esta notas del producto está pensada para aumentar su concienciación sobre estos desafíos y presentarle posibles alternativas.