Condividi tramite


Migrazione di codice gestito a 32 bit a 64 bit

 

Microsoft Corporation

Aggiornamento maggio 2005

Si applica a:
   Microsoft .NET
   Microsoft .NET Framework 2.0

Riepilogo: Scopri cosa è coinvolto nella migrazione di applicazioni gestite a 32 bit a 64 bit, problemi che possono influire sulla migrazione e sugli strumenti disponibili per assistenza. (17 pagine stampate)

Contenuto

Introduzione
Codice gestito in un ambiente a 32 bit
Immettere CLR per l'ambiente a 64 bit
Migrazione e richiamare piattaforma
Migrazione e interoperabilità COM
Migrazione e codice non sicuro
Migrazione e marshalling
Migrazione e serializzazione
Riepilogo

Introduzione

Questo white paper illustra:

  • Cosa è coinvolto nella migrazione di applicazioni gestite da 32 bit a 64 bit
  • Problemi che possono influire sulla migrazione
  • Quali strumenti sono disponibili per aiutarti

Queste informazioni non devono essere prescrittive; piuttosto è destinato a acquisire familiarità con le diverse aree che sono soggette a problemi durante il processo di migrazione a 64 bit. A questo punto non è presente alcun "cookbook" specifico dei passaggi che è possibile seguire e assicurarsi che il codice funzioni su 64 bit. Le informazioni contenute in questo white paper ti consentirà di acquisire familiarità con i diversi problemi e quali devono essere esaminati.

Come si vedrà presto, se l'assembly gestito non è di tipo 100% codice sicuro, sarà necessario esaminare l'applicazione e le relative dipendenze per determinare i problemi con la migrazione a 64 bit. Molti degli elementi che verranno letti nelle sezioni successive possono essere risolti tramite le modifiche di programmazione. In un certo numero di casi è anche necessario mettere da parte il tempo per aggiornare il codice per eseguire correttamente in entrambi gli ambienti a 32 bit e a 64 bit, se si vuole che venga eseguito in entrambi.

Microsoft .NET è un set di tecnologie software per la connessione di informazioni, persone, sistemi e dispositivi. Dalla versione 1.0 del 2002, le organizzazioni hanno avuto esito positivo nella distribuzione di . Soluzioni basate su NET, indipendentemente dai fornitori di software indipendenti o da alcune combinazioni. Esistono diversi tipi di applicazioni .NET che spingono i limiti dell'ambiente a 32 bit. Queste sfide includono, ma non sono limitate, la necessità di una memoria più reale e la necessità di aumentare le prestazioni a virgola mobile. X64 e Itanium offrono prestazioni migliori per le operazioni a virgola mobile che è possibile ottenere su x86. Tuttavia, è anche possibile che i risultati ottenuti su x64 o Itanium siano diversi dai risultati ottenuti su x86. La piattaforma a 64 bit mira a risolvere questi problemi.

Con la versione di .NET Framework versione 2.0, Microsoft include il supporto per il codice gestito in esecuzione nelle piattaforme x64 e Itanium a 64 bit.

Il codice gestito è semplicemente "codice" che fornisce informazioni sufficienti per consentire a .NET Common Language Runtime (CLR) di fornire un set di servizi di base, tra cui:

  • Descrizione automatica del codice e dei dati tramite metadati
  • Camminare in pila
  • Security
  • Garbage Collection
  • Compilazione JUST-in-Time

Oltre al codice gestito sono disponibili diverse altre definizioni che sono importanti per comprendere durante l'analisi dei problemi di migrazione.

Dati gestiti: i dati allocati nell'heap gestito e raccolti tramite Garbage Collection.

Assembly: l'unità di distribuzione che consente a CLR di comprendere completamente il contenuto di un'applicazione e di applicare le regole di controllo delle versioni e delle dipendenze definite dall'applicazione.

Digitare codice sicuro: il codice che usa solo i dati gestiti e non verificabili tipi di dati o operazioni di conversione/coercizione dei tipi di dati non supportati, ovvero unioni non discriminate o puntatori di struttura/interfaccia. Codice C#, Visual Basic .NET e Visual C++ compilato con il codice / clr:safe generate type safe.

Codice non sicuro: il codice che è autorizzato a eseguire operazioni di questo livello inferiore, come dichiarare e operare su puntatori, eseguendo conversioni tra puntatori e tipi integrali e prendendo l'indirizzo delle variabili. Tali operazioni consentono l'interfaccia con il sistema operativo sottostante, l'accesso a un dispositivo mappato alla memoria o l'implementazione di un algoritmo time-critical. Il codice nativo non è sicuro.

Codice gestito in un ambiente a 32 bit

Per comprendere le complessità coinvolte nella migrazione del codice gestito all'ambiente a 64 bit, è possibile esaminare il modo in cui il codice gestito viene eseguito in un ambiente a 32 bit.

Quando un'applicazione, gestita o non gestita, viene selezionata per essere eseguita, il caricatore di Windows viene richiamato ed è responsabile della decisione su come caricare e quindi eseguire l'applicazione. La parte di questo processo comporta il peking all'interno dell'intestazione di esecuzione portabile (PE) dell'eseguibile per determinare se è necessario CLR. Come si potrebbe già indovinare, sono presenti flag nell'PE che indicano il codice gestito. In questo caso, il caricatore Di Windows avvia CLR che è quindi responsabile del caricamento e dell'esecuzione dell'applicazione gestita. Si tratta di una descrizione semplificata del processo in quanto sono presenti molti passaggi coinvolti, tra cui la determinazione della versione di CLR da eseguire, la configurazione dell'appDomain 'sandbox' e così via.

Interoperabilità

Durante l'esecuzione dell'applicazione gestita, può (presupponendo autorizzazioni di sicurezza appropriate) interagire con le API native (inclusa l'API Win32) e gli oggetti COM tramite le funzionalità di interoperabilità CLR. Se si chiama un'API della piattaforma nativa, si effettua una richiesta COM o si esegue un marshalling di una struttura, quando si esegue completamente all'interno dell'ambiente a 32 bit, lo sviluppatore è isolato dalla necessità di pensare alle dimensioni dei tipi di dati e all'allineamento dei dati.

Quando si considera la migrazione a 64 bit, sarà essenziale cercare quali dipendenze ha l'applicazione.

Immettere CLR per l'ambiente a 64 bit

Per consentire l'esecuzione del codice gestito nell'ambiente a 64 bit coerente con l'ambiente a 32 bit, il team .NET ha sviluppato Common Language Runtime (CLR) per i sistemi Itanium e x64 a 64 bit. CLR doveva rispettare rigorosamente le regole dell'infrastruttura common language (CLI) e Common Language Type System per assicurarsi che il codice scritto in uno qualsiasi dei linguaggi .NET sia in grado di interoperabilità come fanno nell'ambiente a 32 bit. Inoltre, di seguito è riportato un elenco di alcune delle altre parti che avevano anche la porta e/o sviluppato per l'ambiente a 64 bit:

  • Librerie di classi di base (System.*)
  • Compilatore JUST-In-Time
  • Supporto per il debug
  • .NET Framework SDK

Supporto del codice gestito a 64 bit

.NET Framework versione 2.0 supporta i processori Itanium e x64 a 64 bit in esecuzione:

  • Windows Server 2003 SP1
  • Versioni future del client Windows a 64 bit

Non è possibile installare .NET Framework versione 2.0 in Windows 2000. I file di output prodotti usando .NET Framework versioni 1.0 e 1.1 verranno eseguiti in WOW64 in un sistema operativo a 64 bit.

Quando si installa .NET Framework versione 2.0 nella piattaforma a 64 bit, non si installa solo tutta l'infrastruttura necessaria per eseguire il codice gestito in modalità a 64 bit, ma si installa l'infrastruttura necessaria per l'esecuzione del codice gestito nel sottosistema Windows-on-Windows o WoW64 (modalità a 32 bit).

Migrazione a 64 bit semplice

Si consideri un'applicazione .NET che è codice sicuro del 100%. In questo scenario è possibile eseguire il file eseguibile .NET eseguito nel computer a 32 bit e spostarlo nel sistema a 64 bit ed eseguirlo correttamente. Perché funziona? Poiché l'assembly è sicuro del 100%, sappiamo che non esistono dipendenze da oggetti COM o codice nativo e che non esiste codice "non sicuro" che significa che l'applicazione viene eseguita interamente sotto il controllo di CLR. CLR garantisce che, mentre il codice binario generato come risultato della compilazione JIT (Just-in-time) sarà diverso tra 32 bit e 64 bit, il codice che esegue sarà semanticamente lo stesso. Non è possibile installare .NET Framework versione 2.0 in Windows 2000. I file di output prodotti con .NET Framework versioni 1.0 e 1.1 verranno eseguiti in WOW64 in un sistema operativo a 64 bit.

In realtà lo scenario precedente è un po'più complicato dal punto di vista del caricamento dell'applicazione gestita. Come illustrato nella sezione precedente, il caricatore di Windows è responsabile della decisione su come caricare ed eseguire l'applicazione. Tuttavia, a differenza dell'ambiente a 32 bit, l'esecuzione in una piattaforma Windows a 64 bit significa che esistono due ambienti (2) in cui l'applicazione può essere eseguita, in modalità nativa a 64 bit o in WoW64.

Il caricatore di Windows ora deve prendere decisioni in base a ciò che individua nell'intestazione PE. Come si potrebbe aver indovinato, nel codice gestito sono presenti flag impostabili che aiutano con questo processo. Vedere corflags.exe per visualizzare le impostazioni in un PE. L'elenco seguente rappresenta le informazioni disponibili nella PE che aiutano nel processo decisionale.

  • 64 bit: indica che lo sviluppatore ha creato l'assembly specificamente destinato a un processo a 64 bit.
  • 32 bit: indica che lo sviluppatore ha creato l'assembly specificamente destinato a un processo a 32 bit. In questa istanza l'assembly verrà eseguito in WoW64.
  • Agnostic: indica che lo sviluppatore ha creato l'assembly con Visual Studio 2005, denominato code-named "Whidbey". o strumenti successivi e che l'assembly può essere eseguito in modalità a 64 bit o a 32 bit. In questo caso, il caricatore Windows a 64 bit eseguirà l'assembly in 64 bit.
  • Legacy: indica che gli strumenti creati dall'assembly erano "pre-Whidbey". In questo caso specifico l'assembly verrà eseguito in WoW64.

Nota Sono disponibili anche informazioni nel PE che indica al caricatore di Windows se l'assembly è destinato a un'architettura specifica. Queste informazioni aggiuntive garantiscono che gli assembly destinati a un'architettura specifica non vengano caricati in uno diverso.

I compilatori C#, Visual Basic .NET e C++ Whidbey consentono di impostare i flag appropriati nell'intestazione PE. Ad esempio, C# e THIRD hanno un'opzione del compilatore /platform:{anycpu, x86, Itanium, x64} .

Nota Sebbene sia tecnicamente possibile modificare i flag nell'intestazione PE di un assembly dopo la compilazione, Microsoft non consiglia di eseguire questa operazione.

Se si è curiosi di sapere come vengono impostati questi flag in un assembly gestito, è possibile eseguire l'utilità ILDASM fornita in .NET Framework SDK. La figura seguente mostra un'applicazione "legacy".

Tenere presente che uno sviluppatore contrassegna un assembly come Win64 ha determinato che tutte le dipendenze dell'applicazione verranno eseguite in modalità a 64 bit. Un processo a 64 bit non può usare un componente a 32 bit nel processo e un processo a 32 bit non può caricare un componente a 64 bit nel processo. Tenere presente che la capacità del sistema di caricare l'assembly in un processo a 64 bit non significa automaticamente che verrà eseguita correttamente.

Ora sappiamo che un'applicazione costituita da codice gestito che è sicura del 100% può essere copiata (o distribuita in xcopy ) in una piattaforma a 64 bit ed eseguirla correttamente con .NET in modalità a 64 bit.

Tuttavia, spesso vediamo situazioni che non sono ideali e che ci porta all'attenzione principale di questo documento, che è quello di aumentare la consapevolezza dei problemi correlati alla migrazione.

È possibile disporre di un'applicazione che non è sicura del 100% e che è ancora in grado di eseguire correttamente in .NET. Sarà importante esaminare attentamente l'applicazione, tenendo presente i potenziali problemi illustrati nelle sezioni seguenti e stabilire se è possibile o non è possibile eseguire correttamente in 64 bit.

Migrazione e richiamare piattaforma

L'uso delle funzionalità di richiamo della piattaforma (o p/invoke) di .NET fa riferimento al codice gestito che esegue chiamate a codice non gestito o nativo. In uno scenario tipico questo codice nativo è una libreria di collegamento dinamica (DLL) che fa parte del sistema (API Windows e così via), parte dell'applicazione o di una libreria di terze parti.

L'uso di codice non gestito non significa in modo esplicito che una migrazione a 64 bit avrà problemi; piuttosto dovrebbe essere considerato un indicatore che è necessaria un'indagine aggiuntiva.

Tipi di dati in Windows

Ogni applicazione e ogni sistema operativo ha un modello di dati astratta. Molte applicazioni non espongono esplicitamente questo modello di dati, ma il modello guida il modo in cui viene scritto il codice dell'applicazione. Nel modello di programmazione a 32 bit (noto come modello ILP32), i tipi di dati integer, long e puntatore sono 32 bit di lunghezza. La maggior parte degli sviluppatori ha usato questo modello senza renderlo conto.

In Microsoft Windows a 64 bit questa ipotesi di parità nelle dimensioni dei tipi di dati non è valida. La creazione di tutti i tipi di dati a 64 bit di lunghezza potrebbe sprecare spazio, perché la maggior parte delle applicazioni non necessita di dimensioni maggiori. Tuttavia, le applicazioni richiedono puntatori a dati a 64 bit e hanno bisogno della possibilità di avere tipi di dati a 64 bit nei casi selezionati. Queste considerazioni hanno portato il team di Windows a selezionare un modello di dati astratto denominato LLP64 (o P64). Nel modello di dati LLP64, solo i puntatori si espandono a 64 bit; tutti gli altri tipi di dati di base (intero e lungo) rimangono a 32 bit di lunghezza.

.NET CLR per piattaforme a 64 bit usa lo stesso modello di dati astratta LLP64. In .NET esiste un tipo di dati integrale, non ampiamente noto, che è specificamente designato per contenere le informazioni 'puntatore': IntPtr le cui dimensioni dipendono dalla piattaforma (ad esempio, a 32 bit o a 64 bit) in esecuzione. Si consideri il frammento di codice seguente:

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

Quando viene eseguito in una piattaforma a 32 bit, si otterrà l'output seguente nella console:

SizeOf IntPtr is: 4

In una piattaforma a 64 bit si otterrà l'output seguente nella console:

SizeOf IntPtr is: 8

Nota Se si vuole controllare in fase di esecuzione se si esegue o meno in un ambiente a 64 bit, è possibile usare IntPtr.Size come un modo per fare questa determinazione.

Considerazioni sulla migrazione

Quando si esegue la migrazione di applicazioni gestite che usano p/invoke, prendere in considerazione gli elementi seguenti:

  • Disponibilità di una versione a 64 bit della DLL
  • Uso dei tipi di dati

Disponibilità

Uno dei primi elementi da determinare è se il codice non gestito dell'applicazione ha una dipendenza è disponibile per 64 bit.

Se questo codice è stato sviluppato in casa, la possibilità di successo è aumentata. Naturalmente, sarà comunque necessario allocare risorse per trasferire il codice non gestito a 64 bit insieme alle risorse appropriate per il test, la garanzia della qualità e così via. Questo white paper non effettua raccomandazioni sui processi di sviluppo. Invece, sta tentando di segnalare che le risorse potrebbero essere allocate alle attività al codice di porta.

Se questo codice proviene da terze parti, sarà necessario esaminare se questa terza parte dispone già del codice disponibile per 64 bit e se la terza parte sarebbe disposta a renderla disponibile.

Il problema di rischio più elevato si verificherà se la terza parte non fornisce più supporto per questo codice o se la terza parte non è disposta a eseguire il lavoro. Questi casi richiedono ulteriori ricerche sulle librerie disponibili che eseguono funzionalità simili, se la terza parte lascerà che il cliente faccia la porta stessa e così via.

È importante tenere presente che una versione a 64 bit del codice dipendente può avere firme di interfaccia modificate che potrebbero significare un lavoro di sviluppo aggiuntivo e risolvere le differenze tra le versioni a 32 bit e a 64 bit dell'applicazione.

Tipi di dati

L'uso di p/invoke richiede che il codice sviluppato in .NET dichiari un prototipo del metodo destinato al codice gestito. Data la dichiarazione C seguente:

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

Di seguito sono riportati esempi di metodi prototipi:

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

Esaminiamo questi esempi con un occhio ai problemi di migrazione a 64 bit:

Il primo esempio chiama il metodo DoWork passando in due (2) interi a 32 bit e si prevede che venga restituito un intero a 32 bit. Anche se è in esecuzione in una piattaforma a 64 bit, un intero è ancora a 32 bit. Non c'è nulla in questo particolare esempio che dovrebbe impedire i nostri sforzi di migrazione.

Il secondo esempio richiede alcune modifiche al codice per l'esecuzione corretta in 64 bit. Ciò che stiamo facendo qui sta chiamando il metodo GetData e dichiarato che si prevede che venga restituito un intero, ma dove la funzione restituisce effettivamente un puntatore int. Questo problema è il nostro problema: tenere presente che gli interi sono 32 bit, ma in puntatori a 64 bit sono 8 byte. Come si scopre, un po ' di codice nel mondo a 32 bit è stato scritto presupponendo che un puntatore e un intero erano la stessa lunghezza, 4 byte. Nel mondo a 64 bit questo non è più vero.

In questo ultimo caso il problema può essere risolto modificando la dichiarazione del metodo in modo che usi un IntPtr al posto dell'int.

public unsafe static extern IntPtr GetData();

Questa modifica funzionerà sia negli ambienti a 32 bit che a 64 bit. Tenere presente che IntPtr è specifico della piattaforma.

L'uso di p/invoke nell'applicazione gestita non significa che la migrazione alla piattaforma a 64 bit non sarà possibile. Né significa che ci saranno problemi. Ciò significa che è necessario esaminare le dipendenze sul codice non gestito che l'applicazione gestita ha e determinare se ci saranno problemi.

Migrazione e interoperabilità COM

L'interoperabilità COM è una capacità presupposta della piattaforma .NET. Analogamente alla precedente discussione su platform invoke, l'uso dell'interoperabilità COM significa che il codice gestito esegue chiamate al codice non gestito. Diversamente dalla piattaforma invoke, tuttavia, l'interoperabilità COM significa anche avere la possibilità di chiamare codice gestito come se fosse un componente COM.

Ancora una volta, l'uso del codice COM non gestito non significa che una migrazione a 64 bit avrà problemi; piuttosto dovrebbe essere considerato un indicatore che è necessaria un'indagine aggiuntiva.

Considerazioni sulla migrazione

È importante comprendere che con la versione di .NET Framework versione 2.0 non è disponibile alcun supporto per l'interoperabilità tra architetture. Per essere più succinti, non è possibile usare l'interoperabilità COM tra 32 bit e 64 bit nello stesso processo. Tuttavia, è possibile usare l'interoperabilità COM tra 32 bit e 64 bit se si dispone di un server COM out-of-process. Se non è possibile usare un server COM out-of-process, si vuole contrassegnare l'assembly gestito come Win32 anziché Win64 o Agnostic per avere il programma eseguito in WoW64 in modo che possa interagire con l'oggetto COM a 32 bit.

Di seguito è riportata una discussione sulle diverse considerazioni che devono essere fornite per usare l'interoperabilità COM in cui il codice gestito effettua chiamate COM in un ambiente a 64 bit. ovvero:

  • Disponibilità di una versione a 64 bit della DLL
  • Uso dei tipi di dati
  • Librerie dei tipi

Disponibilità

La discussione nella sezione p/invoke relativa alla disponibilità di una versione a 64 bit del codice dipendente è rilevante anche per questa sezione.

Tipi di dati

La discussione nella sezione p/invoke relativa ai tipi di dati di una versione a 64 bit del codice dipendente è rilevante anche per questa sezione.

Librerie dei tipi

A differenza degli assembly, le librerie di tipi non possono essere contrassegnate come "neutrali". devono essere contrassegnati come Win32 o Win64. Inoltre, la libreria dei tipi deve essere registrata per ogni ambiente in cui verrà eseguito com. Usare tlbimp.exe per generare un assembly a 32 bit o a 64 bit da una libreria di tipi.

L'uso dell'interoperabilità COM nell'applicazione gestita non significa che la migrazione alla piattaforma a 64 bit non sarà possibile. Né significa che ci saranno problemi. Ciò che significa è che è necessario esaminare le dipendenze che l'applicazione gestita ha e determinare se ci saranno problemi.

Migrazione e codice non sicuro

Il linguaggio C# principale differisce in particolare da C e C++ nell'omissione dei puntatori come tipo di dati. C# fornisce invece riferimenti e la possibilità di creare oggetti gestiti da un Garbage Collector. Nel linguaggio C# core non è semplicemente possibile avere una variabile non inizializzata, un puntatore "dangling" o un'espressione che indicizza una matrice oltre i limiti. Tutte le categorie di bug che riguardano in modo routine i programmi C e C++ vengono eliminati.

Sebbene praticamente ogni costrutto di tipo puntatore in C o C++ abbia una controparte del tipo di riferimento in C#, esistono situazioni in cui l'accesso ai tipi di puntatore diventa una necessità. Ad esempio, l'interfaccia con il sistema operativo sottostante, l'accesso a un dispositivo mappato alla memoria o l'implementazione di un algoritmo time-critical potrebbe non essere possibile o pratico senza accesso ai puntatori. Per risolvere questa necessità, C# offre la possibilità di scrivere codice non sicuro.

Nel codice non sicuro è possibile dichiarare e operare sui puntatori, per eseguire conversioni tra puntatori e tipi integrali, per prendere l'indirizzo delle variabili e così via. In un certo senso, la scrittura di codice non sicuro è molto simile alla scrittura di codice C all'interno di un programma C#.

Il codice non sicuro è infatti una funzionalità "sicura" dal punto di vista degli sviluppatori e degli utenti. Il codice non sicuro deve essere chiaramente contrassegnato con il modificatore unsafe, in modo che gli sviluppatori non possano usare accidentalmente funzionalità non sicure.

Considerazioni sulla migrazione

Per discutere i potenziali problemi relativi al codice non sicuro, vedere l'esempio seguente. Il codice gestito effettua chiamate a una DLL non gestita. In particolare, esiste un metodo denominato GetDataBuffer che restituisce 100 elementi (per questo esempio viene restituito un numero fisso di elementi). Ognuno di questi elementi è costituito da un intero e un puntatore. Il codice di esempio seguente è un estratto dal codice gestito che mostra la funzione non sicura responsabile della gestione dei dati restituiti.

[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 Questo esempio specifico potrebbe essere stato eseguito senza l'uso di codice non sicuro. In particolare, esistono altre tecniche, ad esempio il marshalling che potrebbe essere stato usato. Ma a questo scopo si usa codice non sicuro.

UnsafeFn esegue il ciclo di 100 elementi e somma i dati interi. Man mano che si sta eseguendo un buffer di dati, il codice deve eseguire il passaggio sia sull'intero che sul puntatore. Nell'ambiente a 32 bit questo codice funziona correttamente. Tuttavia, come illustrato in precedenza, i puntatori sono 8 byte nell'ambiente a 64 bit e pertanto il segmento di codice (illustrato di seguito) non funzionerà correttamente, poiché sta facendo uso di una tecnica di programmazione comune, ad esempio, trattando un puntatore come equivalente a un intero.

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

Per consentire al codice di funzionare sia nell'ambiente a 32 bit che a 64 bit, è necessario modificare il codice nel codice seguente.

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

Come abbiamo appena visto, esistono istanze in cui è necessario usare il codice non sicuro. Nella maggior parte dei casi è necessario in seguito alla dipendenza del codice gestito da un'altra interfaccia. Indipendentemente dai motivi per cui esiste il codice non sicuro, deve essere esaminato come parte del processo di migrazione.

L'esempio usato sopra è relativamente semplice e la correzione per rendere il programma funzionante in 64 bit era semplice. Chiaramente ci sono molti esempi di codice non sicuro che sono più complessi. Alcuni richiederanno una revisione approfondita e forse un passo indietro e ripensamento dell'approccio usato dal codice gestito.

Per ripetere quanto già letto: l'uso di codice non sicuro nell'applicazione gestita non significa che la migrazione alla piattaforma a 64 bit non sarà possibile. Né significa che ci saranno problemi. Ciò che significa è che è necessario esaminare tutto il codice non sicuro che l'applicazione gestita ha e determinare se ci saranno problemi.

Migrazione e marshalling

Il marshalling fornisce una raccolta di metodi per l'allocazione della memoria non gestita, la copia di blocchi di memoria non gestiti e la conversione gestita in tipi non gestiti, nonché altri metodi vari usati durante l'interazione con il codice non gestito.

Il marshalling viene manifesto tramite la classe Marshallal .NET. I metodi statici o condivisi in Visual Basic, definiti nella classe Marshallal sono essenziali per l'uso dei dati non gestiti. Gli sviluppatori avanzati creano marshaller personalizzati che devono fornire un ponte tra i modelli di programmazione gestiti e non gestiti in genere usano la maggior parte dei metodi definiti.

Considerazioni sulla migrazione

Il marshalling pone alcune delle sfide più complesse associate alla migrazione delle applicazioni a 64 bit. Dato la natura di ciò che lo sviluppatore sta tentando di eseguire con il marshalling, ovvero il trasferimento di informazioni strutturate a, da o da e verso il codice gestito e non gestito, si noterà che stiamo fornendo informazioni, a volte di basso livello, per assistere il sistema.

In termini di layout sono disponibili due dichiarazioni specifiche che possono essere effettuate dallo sviluppatore; queste dichiarazioni vengono in genere effettuate tramite l'uso degli attributi di codifica.

LayoutKind.Sequential

Esaminare la definizione fornita nella Guida di .NET Framework SDK:

"I membri dell'oggetto vengono disposti in sequenza, nell'ordine in cui vengono visualizzati quando vengono esportati in memoria non gestita. I membri vengono disposti in base all'imballaggio specificato in StructLayoutAttribute.Pack e possono essere non contigui."

Viene detto che il layout è specifico dell'ordine in cui è definito. Quindi, tutto ciò che dobbiamo fare è assicurarsi che le dichiarazioni gestite e non gestite siano simili. Ma, stiamo anche dicendo che la confezione è un ingrediente critico, anche. A questo punto non sarai sorpreso di imparare che senza intervento esplicito da parte dello sviluppatore, esiste un valore del pacchetto predefinito. Come si potrebbe già indovinare, il valore predefinito del pacchetto non è lo stesso tra i sistemi a 32 bit e a 64 bit.

L'istruzione nella definizione relativa ai membri non non contigui fa riferimento al fatto che, poiché sono presenti dimensioni predefinite dei pacchetti, i dati disposti in memoria potrebbero non essere in corrispondenza di byte 0, byte 1, byte2 e così via. Invece, il primo membro sarà a byte 0, ma il secondo membro potrebbe essere a byte 4. Il sistema esegue questa compressione predefinita per consentire al computer di accedere ai membri senza dover gestire problemi di non allineamento.

Ecco un'area che dobbiamo prestare attenzione alla confezione, e allo stesso tempo, cercare di consentire al sistema di agire nella sua modalità preferita.

Di seguito è riportato un esempio di struttura definita nel codice gestito, nonché la struttura corrispondente definita nel codice non gestito. È consigliabile prendere nota del modo in cui questo esempio illustra l'impostazione del valore del pacchetto in entrambi gli ambienti.

[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

Esaminare la definizione fornita nella Guida .NET FrameworkSDK:

"La posizione precisa di ogni membro di un oggetto in memoria non gestita è controllata in modo esplicito. Ogni membro deve usare FieldOffsetAttribute per indicare la posizione del campo all'interno del tipo."

Abbiamo detto qui che lo sviluppatore fornirà compensazioni esatte per aiutare il marshalling delle informazioni. È quindi essenziale che lo sviluppatore specifichi correttamente le informazioni nell'attributo FieldOffset .

Quindi, dove sono i potenziali problemi? Tenere presente che gli offset dei campi sono definiti conoscendo le dimensioni del membro dei dati in corso, è importante ricordare che non tutte le dimensioni dei tipi di dati sono uguali tra 32 bit e 64 bit. In particolare, i puntatori sono 4 o 8 byte di lunghezza.

È ora necessario aggiornare il codice sorgente gestito per indirizzare gli ambienti specifici. L'esempio seguente mostra una struttura che include un puntatore. Anche se è stato fatto il puntatore di Un IntPtr c'è ancora una differenza quando si passa a 64 bit.

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

Per 64 bit è necessario modificare l'offset del campo per l'ultimo membro dati nella struttura perché inizia in realtà all'offset 12 anziché a 8.

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

L'uso del marshalling è una realtà quando è necessaria un'interoperabilità complessa tra codice gestito e non gestito. L'uso di questa potente funzionalità non è un indicatore che è possibile eseguire la migrazione dell'applicazione a 32 bit all'ambiente a 64 bit. Tuttavia, a causa delle complessità associate all'uso del marshalling, si tratta di un'area in cui è necessaria un'attenzione attenta ai dettagli.

L'analisi del codice indicherà se sono necessari file binari separati per ognuna delle piattaforme e se è necessario apportare modifiche al codice non gestito per risolvere i problemi come la compressione.

Migrazione e serializzazione

La serializzazione è il processo di conversione dello stato di un oggetto in un form che può essere mantenuto o trasportato. Il complemento della serializzazione è la deserializzazione, che converte un flusso in un oggetto. Insieme, questi processi consentono di archiviare e trasferire i dati in modo semplice.

.NET Framework dispone di due tecnologie di serializzazione:

  • La serializzazione binaria mantiene la fedeltà dei tipi, utile per il mantenimento dello stato di un oggetto tra chiamate diverse di un'applicazione. È possibile, ad esempio, condividere un oggetto tra diverse applicazioni serializzandolo negli Appunti. La serializzazione di un oggetto può essere effettuata in un flusso, in un disco, in memoria, in rete e così via. .NET Remoting usa la serializzazione per passare oggetti "per valore" da un computer o da un dominio applicazione a un altro.
  • La serializzazione XML serializza solo i campi e le proprietà pubbliche e non mantiene la fedeltà dei tipi. Ciò risulta utile se si vuole fornire o utilizzare dati senza limitare l'applicazione che utilizza i dati. Poiché XML è uno standard aperto, questa rappresenta una scelta interessante ai fini della condivisione di dati attraverso il Web. Analogamente, SOAP è uno standard aperto che rappresenta una scelta altrettanto interessante.

Considerazioni sulla migrazione

Quando pensiamo alla serializzazione dobbiamo tenere presente ciò che stiamo cercando di ottenere. Una domanda da tenere presente quando si esegue la migrazione a 64 bit è se si intende condividere informazioni serializzate tra le diverse piattaforme. In altre parole, le informazioni sull'applicazione gestita a 64 bit vengono letti (o deserializzare) archiviate da un'applicazione gestita a 32 bit.

La risposta aiuterà a guidare la complessità della soluzione.

  • È possibile scrivere routine di serializzazione personalizzate per tenere conto delle piattaforme.
  • È possibile limitare la condivisione delle informazioni, consentendo comunque a ogni piattaforma di leggere e scrivere i propri dati.
  • È possibile rivedere ciò che si sta serializzando e apportare modifiche per evitare alcuni dei problemi.

Quindi, dopo tutto ciò, quali sono le considerazioni rispetto alla serializzazione?

  • IntPtr è 4 o 8 byte in lunghezza a seconda della piattaforma. Se si serializzano le informazioni, si scrivono dati specifici della piattaforma nell'output. Ciò significa che è possibile e si verificano problemi se si tenta di condividere queste informazioni.

Se si considera la nostra discussione nella sezione precedente sul marshalling e gli offset, è possibile che si verifichi una domanda o due su come la serializzazione indirizza le informazioni di compressione. Per la serializzazione binaria .NET usa internamente l'accesso corretto al flusso di serializzazione usando letture basate su byte e gestisce correttamente i dati.

Come abbiamo appena visto, l'uso della serializzazione non impedisce la migrazione a 64 bit. Se si usa la serializzazione XML, è necessario convertire da e in tipi gestiti nativi durante il processo di serializzazione, isolando le differenze tra le piattaforme. L'uso della serializzazione binaria offre una soluzione più completa, ma crea la situazione in cui è necessario prendere decisioni su come le diverse piattaforme condividono le informazioni serializzate.

Riepilogo

La migrazione a 64 bit è in arrivo e Microsoft sta lavorando per eseguire la transizione da applicazioni gestite a 32 bit al più semplice possibile.

Tuttavia, non è realistico presupporre che sia possibile eseguire solo codice a 32 bit in un ambiente a 64 bit ed eseguirlo senza esaminare le operazioni di migrazione.

Come accennato in precedenza, se si dispone di codice gestito sicuro del 100%, è davvero possibile copiarlo nella piattaforma a 64 bit ed eseguirlo correttamente in CLR a 64 bit.

Ma più probabilmente l'applicazione gestita sarà coinvolta in uno o tutti i seguenti:

  • Richiamo delle API della piattaforma tramite p/invoke
  • Richiamo di oggetti COM
  • Uso di codice non sicuro
  • Uso del marshalling come meccanismo per la condivisione di informazioni
  • Uso della serializzazione come modo per rendere persistente lo stato

Indipendentemente da quali operazioni l'applicazione sta facendo, sarà importante eseguire i compiti e analizzare le operazioni che il codice sta facendo e quali dipendenze si hanno. Dopo aver eseguito questo lavoro, dovrai esaminare le tue scelte per eseguire qualsiasi o tutte le operazioni seguenti:

  • Eseguire la migrazione del codice senza modifiche.
  • Apportare modifiche al codice per gestire correttamente i puntatori a 64 bit.
  • Collaborare con altri fornitori, ecc., per fornire versioni a 64 bit dei loro prodotti.
  • Apportare modifiche alla logica per gestire il marshalling e/o la serializzazione.

Potrebbero esserci casi in cui si decide di non eseguire la migrazione del codice gestito a 64 bit, in questo caso è possibile contrassegnare gli assembly in modo che il caricatore di Windows possa eseguire la procedura corretta all'avvio. Tenere presente che le dipendenze downstream hanno un impatto diretto sull'applicazione complessiva.

Fxcop

È anche consigliabile essere consapevoli degli strumenti disponibili per facilitare la migrazione.

Oggi Microsoft ha uno strumento denominato FxCop che è uno strumento di analisi del codice che controlla gli assembly di codice gestito .NET per la conformità alle linee guida per la progettazione di Microsoft .NET Framework. Usa la reflection, l'analisi MSIL e l'analisi del grafo delle chiamate per controllare gli assembly per oltre 200 difetti nelle aree seguenti: convenzioni di denominazione, progettazione della libreria, localizzazione, sicurezza e prestazioni. FxCop include sia le versioni GUI che della riga di comando dello strumento, nonché un SDK, per creare regole personalizzate. Per altre informazioni, vedere il sito Web FxCop . Microsoft sta sviluppando regole FxCop aggiuntive che forniscono informazioni utili per facilitare le attività di migrazione.

Esistono anche funzioni della libreria gestita per facilitare l'esecuzione in fase di esecuzione per determinare l'ambiente in esecuzione.

  • System.IntPtr.Size: per determinare se si esegue in modalità a 32 bit o a 64 bit
  • System.Reflection.Module.GetPEKind: per eseguire query a livello di codice su un .exe o .dll per verificare se è destinato a essere eseguito solo in una piattaforma specifica o in WOW64

Non esiste un set specifico di procedure per risolvere tutte le sfide che è possibile affrontare. Questo white paper è destinato a aumentare la consapevolezza di queste sfide e presentarvi con possibili alternative.