Partager via


Migration de code managé 32 bits vers 64 bits

 

Microsoft Corporation

Mise à jour mai 2005

S’applique à :
   Microsoft .NET
   Microsoft .NET Framework 2.0

Résumé: Découvrez ce qui est impliqué dans la migration d’applications managées 32 bits vers 64 bits, les problèmes qui peuvent avoir un impact sur la migration et les outils disponibles pour vous aider. (17 pages imprimées)

Contenu

Introduction
Code managé dans un environnement 32 bits
Entrez le CLR pour l’environnement 64 bits
Migration et appel de plateforme
Migration et interopérabilité COM
Migration et code non sécurisé
Migration et marshaling
Migration et sérialisation
Résumé

Introduction

Ce livre blanc traite des points suivants :

  • Éléments impliqués dans la migration d’applications managées de 32 bits vers 64 bits
  • Problèmes susceptibles d’avoir un impact sur la migration
  • Quels outils sont disponibles pour vous aider

Ces informations ne sont pas destinées à être normatives; il est plutôt destiné à vous familiariser avec les différentes zones sensibles aux problèmes pendant le processus de migration vers 64 bits. À ce stade, il n’existe pas de « livre de recettes » spécifique d’étapes que vous pouvez suivre et vous assurer que votre code fonctionnera sur 64 bits. Les informations contenues dans ce livre blanc vous familiariseront avec les différents problèmes et ce qui doit être examiné.

Comme vous le verrez bientôt, si votre assembly managé n’est pas un code sécurisé de type 100 %, vous devrez passer en revue votre application et ses dépendances pour déterminer vos problèmes de migration vers 64 bits. La plupart des éléments que vous allez lire dans les sections suivantes peuvent être traités par le biais de modifications de programmation. Dans un certain nombre de cas, vous devez également réserver du temps pour mettre à jour votre code afin de s’exécuter correctement dans les environnements 32 bits et 64 bits, si vous souhaitez qu’il s’exécute dans les deux.

Microsoft .NET est un ensemble de technologies logicielles permettant de connecter des informations, des personnes, des systèmes et des appareils. Depuis sa version 1.0 en 2002, les organisations ont réussi à déployer . Solutions basées sur NET, qu’elles soient intégrées en interne, par des éditeurs de logiciels indépendants (ISV) ou une combinaison quelconque. Il existe plusieurs types d’applications .NET qui repoussent les limites de l’environnement 32 bits. Ces défis incluent, sans s’y limiter, la nécessité d’une mémoire plus réelle et adressable et la nécessité d’augmenter les performances à virgule flottante. x64 et Itanium offrent de meilleures performances pour les opérations à virgule flottante que celles que vous pouvez obtenir sur x86. Toutefois, il est également possible que les résultats que vous obtenez sur x64 ou Itanium soient différents des résultats que vous obtenez sur x86. La plateforme 64 bits vise à aider à résoudre ces problèmes.

Avec la publication de .NET Framework version 2.0, Microsoft inclut la prise en charge du code managé s’exécutant sur les plateformes x64 et Itanium 64 bits.

Le code managé est simplement « code » qui fournit suffisamment d’informations pour permettre au CLR (Common Language Runtime) .NET de fournir un ensemble de services de base, notamment :

  • Auto-description du code et des données via des métadonnées
  • Pile à pied
  • Sécurité
  • Nettoyage de la mémoire
  • Compilation juste-à-temps

En plus du code managé, il existe plusieurs autres définitions qui sont importantes à comprendre lorsque vous examinez les problèmes de migration.

Données managées : données allouées sur le tas managé et collectées via le garbage collection.

Assembly : unité de déploiement qui permet au CLR de comprendre pleinement le contenu d’une application et d’appliquer les règles de contrôle de version et de dépendance définies par l’application.

Code sécurisé de type : code qui utilise uniquement des données managées et aucun type de données non vérifiable ou opérations de conversion/coercition de type de données non prises en charge (c’est-à-dire, unions non discriminatoires ou pointeurs de structure/interface). Le code C#, Visual Basic .NET et Visual C++ compilé avec /clr:safe génère du code de type sécurisé.

Code non sécurisé : code qui est autorisé à effectuer des opérations de niveau inférieur comme la déclaration et l’exploitation sur des pointeurs, l’exécution de conversions entre les pointeurs et les types intégraux et la prise de l’adresse de variables. Ces opérations permettent d’interagir avec le système d’exploitation sous-jacent, d’accéder à un appareil mappé en mémoire ou d’implémenter un algorithme critique dans le temps. Le code natif est dangereux.

Code managé dans un environnement 32 bits

Pour comprendre les complexités associées à la migration du code managé vers l’environnement 64 bits, examinons la façon dont le code managé est exécuté dans un environnement 32 bits.

Lorsqu’une application, gérée ou non managée, est sélectionnée pour être exécutée, le chargeur Windows est appelé et est chargé de décider comment charger, puis exécuter l’application. Une partie de ce processus implique l’analyse à l’intérieur de l’en-tête d’exécution portable (PE) de l’exécutable pour déterminer si le CLR est nécessaire. Comme vous l’avez peut-être déjà deviné, il existe des indicateurs dans le PE qui indiquent du code managé. Dans ce cas, le chargeur Windows démarre le CLR qui est ensuite responsable du chargement et de l’exécution de l’application managée. (Il s’agit d’une description simplifiée du processus, car de nombreuses étapes sont impliquées, notamment la détermination de la version du CLR à exécuter, la configuration du « bac à sable » AppDomain, etc.)

Interopérabilité

Au fur et à mesure que l’application managée s’exécute, elle peut (en supposant les autorisations de sécurité appropriées) interagir avec les API natives (y compris l’API Win32) et les objets COM via les fonctionnalités d’interopérabilité CLR. Qu’il s’agisse d’appeler une API de plateforme native, d’effectuer une requête COM ou de marshaler une structure, lorsqu’il s’exécute entièrement dans l’environnement 32 bits, le développeur n’a pas à penser aux tailles de type de données et à l’alignement des données.

Lorsque vous envisagez la migration vers 64 bits, il est essentiel de rechercher les dépendances de votre application.

Entrez le CLR pour l’environnement 64 bits

Pour que le code managé s’exécute dans l’environnement 64 bits cohérent avec l’environnement 32 bits, l’équipe .NET a développé le Common Language Runtime (CLR) pour les systèmes Itanium et x64 64 bits. Le CLR devait strictement se conformer aux règles de l’infrastructure CLI (Common Language Infrastructure) et du Common Language Type System pour s’assurer que le code écrit dans l’un des langages .NET serait en mesure d’interagir comme ils le font dans l’environnement 32 bits. En outre, voici la liste des autres éléments qui devaient également être portés et/ou développés pour l’environnement 64 bits :

  • Bibliothèques de classes de base (System.*)
  • Compilateur juste-à-temps
  • Prise en charge du débogage
  • Kit de développement logiciel (SDK) .NET Framework

Prise en charge du code managé 64 bits

Le .NET Framework version 2.0 prend en charge les processeurs Itanium et x64 64 bits exécutant :

  • Windows Server 2003 SP1
  • Versions futures du client Windows 64 bits

(Vous ne pouvez pas installer le .NET Framework version 2.0 sur Windows 2000. Les fichiers de sortie produits à l’aide des versions 1.0 et 1.1 du .NET Framework s’exécutent sous WOW64 sur un système d’exploitation 64 bits.)

Lorsque vous installez .NET Framework version 2.0 sur la plateforme 64 bits, vous installez non seulement toute l’infrastructure nécessaire pour exécuter votre code managé en mode 64 bits, mais vous installez l’infrastructure nécessaire pour que votre code managé s’exécute dans le sous-système Windows sur Windows ou WoW64 (mode 32 bits).

Une migration 64 bits simple

Considérez une application .NET qui est un code sécurisé de type 100 %. Dans ce scénario, il est possible de prendre votre exécutable .NET que vous exécutez sur votre ordinateur 32 bits, de le déplacer vers le système 64 bits et de le faire exécuter correctement. Pourquoi cela fonctionne-t-il ? Étant donné que l’assembly est de type sécurisé à 100 %, nous savons qu’il n’existe aucune dépendance sur le code natif ou les objets COM et qu’il n’existe aucun code « non sécurisé », ce qui signifie que l’application s’exécute entièrement sous le contrôle du CLR. Le CLR garantit que, bien que le code binaire généré à la suite de la compilation juste-à-temps (JIT) soit différent entre 32 bits et 64 bits, le code qui s’exécute sera sémantiquement le même. (Vous ne pouvez pas installer le .NET Framework version 2.0 sur Windows 2000. Les fichiers de sortie générés à l’aide des versions 1.0 et 1.1 de .NET Framework s’exécutent sous WOW64 sur un système d’exploitation 64 bits.)

En réalité, le scénario précédent est un peu plus compliqué du point de vue du chargement de l’application managée. Comme indiqué dans la section précédente, le chargeur Windows est chargé de décider comment charger et exécuter l’application. Toutefois, contrairement à l’environnement 32 bits, l’exécution sur une plateforme Windows 64 bits signifie qu’il existe deux (2) environnements où l’application peut être exécutée, soit en mode 64 bits natif, soit dans WoW64.

Le chargeur Windows doit maintenant prendre des décisions en fonction de ce qu’il découvre dans l’en-tête PE. Comme vous l’avez peut-être deviné, il existe des indicateurs définissables dans le code managé qui facilitent ce processus. (Consultez corflags.exe pour afficher les paramètres dans un PE.) La liste suivante représente les informations contenues dans le PE qui facilitent le processus de prise de décision.

  • 64 bits : indique que le développeur a créé l’assembly ciblant spécifiquement un processus 64 bits.
  • 32 bits : indique que le développeur a créé l’assembly ciblant spécifiquement un processus 32 bits. Dans cette instance l’assembly s’exécute dans WoW64.
  • Agnostic : indique que le développeur a créé l’assembly avec Visual Studio 2005, sous le nom de code « Whidbey ». ou des outils ultérieurs et que l’assembly peut exécuter en mode 64 bits ou 32 bits. Dans ce cas, le chargeur Windows 64 bits exécute l’assembly en 64 bits.
  • Hérité : indique que les outils qui ont généré l’assembly étaient « pré-Whidbey ». Dans ce cas particulier, l’assembly sera exécuté dans WoW64.

Note Il existe également des informations dans le PE qui indiquent au chargeur Windows si l’assembly est ciblé pour une architecture spécifique. Ces informations supplémentaires garantissent que les assemblys ciblés pour une architecture particulière ne sont pas chargés dans une autre architecture.

Les compilateurs Whidbey C#, Visual Basic .NET et C++ vous permettent de définir les indicateurs appropriés dans l’en-tête PE. Par exemple, C# et THIRD ont une option de compilateur /platform:{anycpu, x86, Itanium, x64} .

Note Bien qu’il soit techniquement possible de modifier les indicateurs dans l’en-tête PE d’un assembly après sa compilation, Microsoft ne recommande pas de le faire.

Si vous êtes curieux de savoir comment ces indicateurs sont définis sur un assembly managé, vous pouvez exécuter l’utilitaire ILDASM fourni dans le Kit de développement logiciel (SDK) .NET Framework. L’illustration suivante montre une application « héritée ».

Gardez à l’esprit qu’un développeur marquant un assembly comme Win64 a déterminé que toutes les dépendances de l’application s’exécuteraient en mode 64 bits. Un processus 64 bits ne peut pas utiliser un composant 32 bits en cours de traitement (et un processus 32 bits ne peut pas charger un composant 64 bits dans le processus). Gardez à l’esprit que la possibilité pour le système de charger l’assembly dans un processus 64 bits ne signifie pas automatiquement qu’il s’exécutera correctement.

Par conséquent, nous savons maintenant qu’une application composée de code managé de type sécurisé à 100 % peut être copiée (ou xcopy la déployer) sur une plateforme 64 bits et l’exécuter avec succès avec .NET en mode 64 bits.

Toutefois, nous voyons souvent des situations qui ne sont pas idéales, ce qui nous amène à la main’objectif de ce document, qui est de mieux faire connaître les problèmes liés à la migration.

Vous pouvez avoir une application qui n’est pas sécurisée à 100 % et qui peut toujours s’exécuter correctement dans 64 bits sous .NET. Il sera important pour vous d’examiner attentivement votre application, en gardant à l’esprit les problèmes potentiels abordés dans les sections suivantes et de déterminer si vous pouvez ou ne pouvez pas s’exécuter correctement en 64 bits.

Migration et appel de plateforme

L’utilisation des fonctionnalités d’appel de plateforme (ou p/invoke) de .NET fait référence au code managé qui effectue des appels à du code non managé ou natif. Dans un scénario classique, ce code natif est une bibliothèque de liens dynamiques (DLL) qui fait partie du système (API Windows, etc.), de votre application ou d’une bibliothèque tierce.

L’utilisation de code non managé ne signifie pas explicitement qu’une migration vers 64 bits rencontrera des problèmes ; il devrait plutôt être considéré comme un indicateur qu’une enquête supplémentaire est nécessaire.

Types de données dans Windows

Chaque application et chaque système d’exploitation ont un modèle de données abstrait. De nombreuses applications n’exposent pas explicitement ce modèle de données, mais le modèle guide la façon dont le code de l’application est écrit. Dans le modèle de programmation 32 bits (appelé modèle ILP32), les types de données entier, long et pointeur sont de 32 bits. La plupart des développeurs ont utilisé ce modèle sans le réaliser.

Dans Microsoft Windows 64 bits, cette hypothèse de parité dans les tailles de type de données n’est pas valide. Rendre tous les types de données de 64 bits de longueur gaspillerait de l’espace, car la plupart des applications n’ont pas besoin de la taille accrue. Toutefois, les applications ont besoin de pointeurs vers des données 64 bits, et elles ont besoin de la possibilité d’avoir des types de données 64 bits dans les cas sélectionnés. Ces considérations ont amené l’équipe Windows à sélectionner un modèle de données abstrait appelé LLP64 (ou P64). Dans le modèle de données LLP64, seuls les pointeurs sont étendus à 64 bits ; tous les autres types de données de base (entier et long) restent de 32 bits.

Le CLR .NET pour les plateformes 64 bits utilise le même modèle de données abstrait LLP64. Dans .NET, il existe un type de données intégral, peu connu, spécifiquement désigné pour contenir les informations de pointeur : IntPtr dont la taille dépend de la plateforme (par exemple, 32 bits ou 64 bits) sur laquelle il s’exécute. Prenez l'exemple de l'extrait de code suivant :

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

Lors de l’exécution sur une plateforme 32 bits, vous obtenez la sortie suivante sur la console :

SizeOf IntPtr is: 4

Sur une plateforme 64 bits, vous obtiendrez la sortie suivante sur la console :

SizeOf IntPtr is: 8

Note Si vous souhaitez case activée au moment de l’exécution, que vous soyez ou non en cours d’exécution dans un environnement 64 bits, vous pouvez utiliser IntPtr.Size comme moyen d’effectuer cette détermination.

Considérations relatives à la migration

Lors de la migration d’applications managées qui utilisent p/invoke, tenez compte des éléments suivants :

  • Disponibilité d’une version 64 bits de la DLL
  • Utilisation des types de données

Disponibilité

L’une des premières choses à déterminer est de savoir si le code non managé sur lequel votre application a une dépendance est disponible pour 64 bits.

Si ce code a été développé en interne, votre capacité à réussir est augmentée. Bien sûr, vous devrez toujours allouer des ressources pour porter le code non managé vers 64 bits, ainsi que des ressources appropriées pour le test, l’assurance qualité, etc. (Ce livre blanc ne fait pas de recommandations sur les processus de développement; il tente plutôt de souligner que des ressources peuvent avoir besoin d’être allouées aux tâches pour le code de port.)

Si ce code provient d’un tiers, vous devez vérifier si ce tiers dispose déjà du code disponible pour la version 64 bits et si le tiers est prêt à le rendre disponible.

Le problème à risque plus élevé se produit si le tiers ne prend plus en charge ce code ou si le tiers n’est pas disposé à effectuer le travail. Ces cas nécessitent des recherches supplémentaires sur les bibliothèques disponibles qui font des fonctionnalités similaires, si le tiers laissera le client effectuer le port lui-même, etc.

Il est important de garder à l’esprit qu’une version 64 bits du code dépendant peut avoir des signatures d’interface modifiées qui peuvent entraîner un travail de développement supplémentaire et pour résoudre les différences entre les versions 32 bits et 64 bits de l’application.

Types de données

L’utilisation de p/invoke nécessite que le code développé dans .NET déclare un prototype de la méthode ciblée par le code managé. Compte tenu de la déclaration C suivante :

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

Voici des exemples de méthodes prototypes :

[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();

Examinons ces exemples en nous penchant sur les problèmes de migration 64 bits :

Le premier exemple appelle la méthode DoWork en passant deux (2) entiers 32 bits et nous nous attendons à ce qu’un entier 32 bits soit retourné. Même si nous sommes en cours d’exécution sur une plateforme 64 bits, un entier est toujours de 32 bits. Il n’y a rien dans cet exemple particulier qui soit susceptible d’entraver nos efforts de migration.

Le deuxième exemple nécessite des modifications du code pour s’exécuter correctement en 64 bits. Ce que nous faisons ici, c’est appeler la méthode GetData et déclarer que nous nous attendons à ce qu’un entier soit retourné, mais où la fonction retourne réellement un pointeur int. C’est là que réside notre problème : n’oubliez pas que les entiers sont de 32 bits, mais que dans les pointeurs 64 bits, ils sont de 8 octets. Il s’avère que tout un peu de code dans le monde 32 bits a été écrit en supposant qu’un pointeur et un entier étaient de la même longueur, 4 octets. Dans le monde 64 bits, cela n’est plus vrai.

Dans ce dernier cas, le problème peut être résolu en modifiant la déclaration de méthode pour utiliser un IntPtr à la place de l’int.

public unsafe static extern IntPtr GetData();

Cette modification fonctionnera dans les environnements 32 bits et 64 bits. N’oubliez pas qu’IntPtr est spécifique à la plateforme.

L’utilisation de p/invoke dans votre application managée ne signifie pas que la migration vers la plateforme 64 bits ne sera pas possible. Cela ne signifie pas non plus qu’il y aura des problèmes. Cela signifie que vous devez passer en revue les dépendances de votre application managée vis-à-vis du code non managé et déterminer s’il y aura des problèmes.

Migration et interopérabilité COM

L’interopérabilité COM est une fonctionnalité supposée de la plateforme .NET. À l’instar de la discussion précédente sur l’appel de plateforme, l’utilisation de l’interopérabilité COM signifie que le code managé effectue des appels au code non managé. Toutefois, contrairement à l’appel de plateforme, l’interopérabilité COM signifie également avoir la possibilité pour le code non managé d’appeler du code managé comme s’il s’agissait d’un composant COM.

Là encore, l’utilisation d’un code COM non managé ne signifie pas qu’une migration vers 64 bits aura des problèmes ; il devrait plutôt être considéré comme un indicateur qu’une enquête supplémentaire est nécessaire.

Considérations relatives à la migration

Il est important de comprendre qu’avec la version 2.0 de .NET Framework, l’interopérabilité inter-architecture n’est pas prise en charge. Pour être plus succinct, vous ne pouvez pas utiliser l’interopérabilité COM entre 32 bits et 64 bits dans le même processus. Toutefois, vous pouvez utiliser l’interopérabilité COM entre 32 bits et 64 bits si vous avez un serveur COM hors processus. Si vous ne pouvez pas utiliser un serveur COM hors processus, vous devez marquer votre assembly managé comme Win32 plutôt que Win64 ou Agnostic afin que votre programme s’exécute dans WoW64 afin qu’il puisse interagir avec l’objet COM 32 bits.

Voici une description des différentes considérations qui doivent être prises en compte pour utiliser l’interopérabilité COM où le code managé effectue des appels COM dans un environnement 64 bits. Plus précisément :

  • Disponibilité d’une version 64 bits de la DLL
  • Utilisation des types de données
  • Bibliothèques de types

Disponibilité

La discussion dans la section p/invoke concernant la disponibilité d’une version 64 bits du code dépendant est également pertinente pour cette section.

Types de données

La discussion dans la section p/invoke concernant les types de données d’une version 64 bits du code dépendant est également pertinente pour cette section.

Bibliothèques de types

Contrairement aux assemblys, les bibliothèques de types ne peuvent pas être marquées comme « neutres » ; ils doivent être marqués comme Win32 ou Win64. En outre, la bibliothèque de types doit être inscrite pour chaque environnement dans lequel com s’exécutera. Utilisez tlbimp.exe pour générer un assembly 32 bits ou 64 bits à partir d’une bibliothèque de types.

L’utilisation de l’interopérabilité COM dans votre application managée ne signifie pas que la migration vers la plateforme 64 bits ne sera pas possible. Cela ne signifie pas non plus qu’il y aura des problèmes. Cela signifie que vous devez passer en revue les dépendances de votre application managée et déterminer s’il y aura des problèmes.

Migration et code non sécurisé

Le langage C# principal diffère particulièrement de C et C++ par son omission des pointeurs en tant que type de données. Au lieu de cela, C# fournit des références et la possibilité de créer des objets gérés par un récupérateur de mémoire. Dans le langage C# principal, il n’est tout simplement pas possible d’avoir une variable non initialisée, un pointeur « pendant » ou une expression qui indexe un tableau au-delà de ses limites. Des catégories entières de bogues qui affectent régulièrement les programmes C et C++ sont ainsi éliminées.

Alors que pratiquement toutes les constructions de type pointeur en C ou C++ ont un équivalent de type référence en C#, il existe des situations où l’accès aux types pointeurs devient une nécessité. Par exemple, l’interaction avec le système d’exploitation sous-jacent, l’accès à un appareil mappé en mémoire ou l’implémentation d’un algorithme critique dans le temps peuvent ne pas être possibles ou pratiques sans accéder aux pointeurs. Pour répondre à ce besoin, C# offre la possibilité d’écrire du code non sécurisé.

Dans le code non sécurisé, il est possible de déclarer et d’utiliser des pointeurs, d’effectuer des conversions entre des pointeurs et des types intégraux, de prendre l’adresse des variables, etc. Dans un sens, l’écriture de code non sécurisé est similaire à l’écriture de code C dans un programme C#.

Le code non sécurisé est en fait une fonctionnalité « sécurisée » du point de vue des développeurs et des utilisateurs. Le code non sécurisé doit être clairement marqué avec le modificateur non sécurisé, afin que les développeurs ne puissent pas utiliser accidentellement des fonctionnalités dangereuses.

Considérations relatives à la migration

Pour discuter des problèmes potentiels liés au code non sécurisé, nous allons explorer l’exemple suivant. Notre code managé effectue des appels à une DLL non managée. En particulier, il existe une méthode appelée GetDataBuffer qui retourne 100 éléments (pour cet exemple, nous renvoyons un nombre fixe d’éléments). Chacun de ces éléments se compose d’un entier et d’un pointeur. L’exemple de code ci-dessous est un extrait du code managé montrant la fonction dangereuse responsable de la gestion de ces données retournées.

[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;
}

Note Cet exemple particulier aurait pu être réalisé sans l’utilisation de code non sécurisé. Plus précisément, il existe d’autres techniques telles que le marshaling qui auraient pu être utilisées. Mais à cette fin, nous utilisons du code non sécurisé.

UnsafeFn effectue une boucle dans les 100 éléments et additionne les données entières. Comme nous parcourons une mémoire tampon de données, le code doit passer à la fois sur l’entier et le pointeur. Dans l’environnement 32 bits, ce code fonctionne correctement. Toutefois, comme nous l’avons vu précédemment, les pointeurs sont de 8 octets dans l’environnement 64 bits et, par conséquent, le segment de code (illustré ci-dessous) ne fonctionnera pas correctement, car il utilise une technique de programmation courante, par exemple, en traitant un pointeur comme équivalent à un entier.

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

Pour que ce code fonctionne à la fois dans l’environnement 32 bits et 64 bits, il est nécessaire de modifier le code comme suit.

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

Comme nous venons de le voir, il existe des cas où l’utilisation de code non sécurisé est nécessaire. Dans la plupart des cas, elle est nécessaire en raison de la dépendance du code managé sur une autre interface. Quelles que soient les raisons pour lesquelles le code non sécurisé existe, il doit être examiné dans le cadre du processus de migration.

L’exemple que nous avons utilisé ci-dessus est relativement simple et le correctif permettant de faire fonctionner le programme en 64 bits était simple. Il existe clairement de nombreux exemples de code non sécurisé qui sont plus complexes. Certaines d’entre elles nécessitent une révision approfondie et peut-être revenir en arrière et repenser l’approche utilisée par le code managé.

Pour répéter ce que vous avez déjà lu, l’utilisation de code non sécurisé dans votre application managée ne signifie pas que la migration vers la plateforme 64 bits ne sera pas possible. Cela ne signifie pas non plus qu’il y aura des problèmes. Cela signifie que vous devez passer en revue tout le code non sécurisé de votre application managée et déterminer s’il y aura des problèmes.

Migration et marshaling

Marshaling fournit une collection de méthodes permettant d’allouer de la mémoire non managée, de copier des blocs de mémoire non managés et de convertir des types managés en types non managés, ainsi que d’autres méthodes diverses utilisées lors de l’interaction avec du code non managé.

Le marshaling se manifeste par le biais de la classe Marshal .NET. Statiques ou partagées en Visual Basic, les méthodes définies sur la classe Marshal sont essentielles pour travailler avec des données non managées. Les développeurs avancés qui créent des marshalers personnalisés qui doivent fournir un pont entre les modèles de programmation managés et non managés utilisent généralement la plupart des méthodes définies.

Considérations relatives à la migration

Le marshaling pose certains des défis les plus complexes associés à la migration des applications vers la version 64 bits. Compte tenu de la nature de ce que le développeur tente d’accomplir avec le marshaling, à savoir le transfert d’informations structurées vers, depuis ou vers du code managé et non managé, nous verrons que nous fournissons des informations, parfois de bas niveau, pour aider le système.

En termes de disposition, il existe deux déclarations spécifiques qui peuvent être faites par le développeur ; ces déclarations sont généralement effectuées à l’aide d’attributs de codage.

LayoutKind.Sequential

Examinons la définition telle qu’elle est fournie dans l’aide du Kit de développement logiciel (SDK) .NET Framework :

« Les membres de l’objet sont disposés séquentiellement, dans l’ordre dans lequel ils apparaissent lorsqu’ils sont exportés vers la mémoire non managée. Les membres sont disposés en fonction de l’emballage spécifié dans StructLayoutAttribute.Pack, et peuvent être non incohérents. »

On nous dit que la disposition est spécifique à l’ordre dans lequel elle est définie. Ensuite, il nous suffit de nous assurer que les déclarations managées et non managées sont similaires. Mais on nous dit aussi que l’emballage est aussi un ingrédient essentiel. À ce stade, vous ne serez pas surpris d’apprendre que sans intervention explicite du développeur, il existe une valeur pack par défaut. Comme vous l’avez peut-être déjà deviné, la valeur pack par défaut n’est pas la même entre les systèmes 32 bits et 64 bits.

L’instruction de la définition concernant les membres non incohérents fait référence au fait que, étant donné qu’il existe des tailles de pack par défaut, les données qui sont disposées en mémoire ne peuvent pas être à 0 octet, octet 1, octet 2, etc. Au lieu de cela, le premier membre sera à l’octet 0, mais le deuxième membre peut être à l’octet 4. Le système effectue cette compression par défaut pour permettre à la machine d’accéder aux membres sans avoir à gérer les problèmes d’alignement incorrect.

Voici un domaine dans lequel nous devons prêter une attention particulière à l’emballage, et en même temps, essayer de laisser le système agir dans son mode préféré.

Voici un exemple de structure telle que définie dans le code managé, ainsi que de la structure correspondante définie dans du code non managé. Vous devez prendre bonne note de la façon dont cet exemple illustre la définition de la valeur du pack dans les deux environnements.

[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

Examinons la définition telle qu’elle est fournie dans l’aide de .NET FrameworkSDK :

« La position précise de chaque membre d’un objet dans la mémoire non managée est explicitement contrôlée. Chaque membre doit utiliser l’attribut FieldOffsetAttribute pour indiquer la position de ce champ dans le type. »

On nous dit ici que le développeur fournira des décalages exacts pour faciliter le regroupement de l’information. Il est donc essentiel que le développeur spécifie correctement les informations dans l’attribut FieldOffset .

Alors, où sont les problèmes potentiels? En gardant à l’esprit que les décalages de champ sont définis en fonction de la taille de membre de données en cours, il est important de se rappeler que toutes les tailles de type de données ne sont pas égales entre 32 bits et 64 bits. Plus précisément, les pointeurs ont une longueur de 4 ou 8 octets.

Nous avons maintenant un cas où nous devrons peut-être mettre à jour notre code source managé pour cibler les environnements spécifiques. L’exemple ci-dessous montre une structure qui inclut un pointeur. Même si nous avons fait du pointeur un IntPtr, il y a toujours une différence lors du passage à 64 bits.

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

Pour 64 bits, nous devons ajuster le décalage de champ pour le dernier membre de données dans la structure, car il commence réellement à 12 au lieu de 8.

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

L’utilisation du marshaling est une réalité lorsque l’interopérabilité complexe entre le code managé et le code non managé est requise. L’utilisation de cette fonctionnalité puissante n’indique pas que vous pouvez migrer votre application 32 bits vers l’environnement 64 bits. Toutefois, en raison de la complexité associée à l’utilisation du marshaling, il s’agit d’un domaine où une attention particulière aux détails est requise.

L’analyse de votre code indique si des fichiers binaires distincts sont requis pour chacune des plateformes et si vous devrez également apporter des modifications à votre code non managé pour résoudre des problèmes tels que l’empaquetage.

Migration et sérialisation

La sérialisation correspond au processus de conversion de l'état d'un objet en un formulaire persistant ou transportable. Le complément de la sérialisation est la désérialisation, qui convertit un flux de données en un objet. Ces deux processus permettent de stocker et de transférer facilement des données.

Le .NET Framework comprend deux technologies de sérialisation :

  • La sérialisation binaire préserve le respect des types, qui permet de conserver l'état d'un objet entre plusieurs appels d'une application. Par exemple, vous pouvez partager un objet entre plusieurs applications en le sérialisant dans le Presse-papiers. Vous pouvez sérialiser un objet vers un flux, un disque, la mémoire, le réseau, et ainsi de suite. La communication à distance .NET utilise la sérialisation pour passer des objets « par valeur » d’un ordinateur ou d’un domaine d’application à un autre.
  • La sérialisation XML sérialise uniquement des propriétés et des champs publics mais ne conserve pas le respect des types. Ceci est utile lorsque vous souhaitez fournir ou consommer des données sans restreindre l'application qui les utilise. XML étant une norme ouverte, elle constitue une option intéressante pour partager des données via le Web. Le protocole SOAP est également une norme ouverte et représente par conséquent une option avantageuse.

Considérations relatives à la migration

Quand nous pensons à la sérialisation, nous devons garder à l’esprit ce que nous essayons d’atteindre. Une question à garder à l’esprit lors de la migration vers 64 bits est de savoir si vous envisagez de partager des informations sérialisées entre les différentes plateformes. En d’autres termes, l’application managée 64 bits va-t-elle lire (ou désérialiser) les informations stockées par une application managée 32 bits.

Votre réponse contribuera à la complexité de votre solution.

  • Vous pouvez écrire vos propres routines de sérialisation pour prendre en compte les plateformes.
  • Vous souhaiterez peut-être restreindre le partage d’informations, tout en permettant à chaque plateforme de lire et d’écrire ses propres données.
  • Vous souhaiterez peut-être revenir sur ce que vous sérialisez et apporter des modifications pour éviter certains problèmes.

Après tout cela, quelles sont les considérations relatives à la sérialisation?

  • IntPtr a une longueur de 4 ou 8 octets selon la plateforme. Si vous sérialisez les informations, vous écrivez des données spécifiques à la plateforme dans la sortie. Cela signifie que vous pouvez et rencontrerez des problèmes si vous tentez de partager ces informations.

Si vous considérez notre discussion dans la section précédente sur le marshaling et les décalages, vous pouvez poser une ou deux questions sur la façon dont la sérialisation traite les informations d’empaquetage. Pour la sérialisation binaire, .NET utilise en interne l’accès non aligné correct au flux de sérialisation en utilisant des lectures basées sur des octets et en gérant correctement les données.

Comme nous venons de le voir, l’utilisation de la sérialisation n’empêche pas la migration vers la version 64 bits. Si vous utilisez la sérialisation XML, vous devez effectuer une conversion à partir de et vers des types managés natifs pendant le processus de sérialisation, ce qui vous isole des différences entre les plateformes. L’utilisation de la sérialisation binaire vous offre une solution plus riche, mais crée une situation où des décisions doivent être prises concernant la façon dont les différentes plateformes partagent des informations sérialisées.

Résumé

La migration vers la version 64 bits est à venir et Microsoft a travaillé pour simplifier la transition des applications managées 32 bits vers la version 64 bits aussi simple que possible.

Toutefois, il est irréaliste de supposer que l’on peut simplement exécuter du code 32 bits dans un environnement 64 bits et le faire exécuter sans examiner ce que vous migrez.

Comme mentionné précédemment, si vous avez du code managé sécurisé de type 100 %, vous pouvez simplement le copier sur la plateforme 64 bits et l’exécuter correctement sous le CLR 64 bits.

Mais il est plus que probable que l’application gérée soit impliquée dans tout ou partie des éléments suivants :

  • Appel des API de plateforme via p/invoke
  • Appel d’objets COM
  • Utilisation de code non sécurisé
  • Utilisation du marshaling comme mécanisme de partage d’informations
  • Utilisation de la sérialisation comme moyen de conserver l’état

Quelles que soient les actions de votre application, il sera important de faire vos devoirs et d’examiner ce que fait votre code et les dépendances dont vous disposez. Une fois que vous avez fait ces devoirs, vous devrez examiner vos choix pour effectuer tout ou partie des opérations suivantes :

  • Migrez le code sans modification.
  • Apportez des modifications à votre code pour gérer correctement les pointeurs 64 bits.
  • Collaborez avec d’autres fournisseurs, etc., pour fournir des versions 64 bits de leurs produits.
  • Apportez des modifications à votre logique pour gérer le marshaling et/ou la sérialisation.

Il peut arriver que vous preniez la décision de ne pas migrer le code managé vers 64 bits, auquel cas vous avez la possibilité de marquer vos assemblys afin que le chargeur Windows puisse faire la bonne chose au démarrage. N’oubliez pas que les dépendances en aval ont un impact direct sur l’application globale.

Fxcop

Vous devez également connaître les outils disponibles pour vous aider dans votre migration.

Aujourd’hui, Microsoft dispose d’un outil appelé FxCop qui est un outil d’analyse du code qui vérifie la conformité des assemblys de code managé .NET aux instructions de conception de Microsoft .NET Framework. Il utilise la réflexion, l’analyse MSIL et l’analyse des graphe d’appel pour inspecter les assemblys pour détecter plus de 200 défauts dans les domaines suivants : conventions d’affectation de noms, conception de bibliothèque, localisation, sécurité et performances. FxCop inclut à la fois l’interface graphique graphique et les versions de ligne de commande de l’outil, ainsi qu’un KIT de développement logiciel (SDK) pour créer vos propres règles. Pour plus d’informations, consultez le site Web de FxCop . Microsoft est en train de développer des règles FxCop supplémentaires qui vous fourniront des informations pour vous aider dans vos efforts de migration.

Il existe également des fonctions de bibliothèque managée pour vous aider à déterminer l’environnement dans lequel vous vous exécutez au moment de l’exécution.

  • System.IntPtr.Size : pour déterminer si vous exécutez en mode 32 bits ou 64 bits
  • System.Reflection.Module.GetPEKind : pour interroger par programmation un .exe ou un .dll pour voir s’il est destiné à s’exécuter uniquement sur une plateforme spécifique ou sous WOW64

Il n’existe pas d’ensemble spécifique de procédures pour relever tous les défis que vous pourriez relever. Ce livre blanc est destiné à vous sensibiliser à ces défis et à vous présenter des alternatives possibles.