Migrieren von verwaltetem 32-Bit-Code zu 64-Bit

 

Microsoft Corporation

Aktualisiert mai 2005

Gilt für:
   Microsoft .NET
   Microsoft .NET Framework 2.0

Zusammenfassung: Erfahren Sie, was bei der Migration von verwalteten 32-Bit-Anwendungen zu 64-Bit-Anwendungen erforderlich ist, probleme, die sich auf die Migration auswirken können, und welche Tools zur Unterstützung für Sie verfügbar sind. (17 gedruckte Seiten)

Inhalte

Einführung
Verwalteter Code in einer 32-Bit-Umgebung
Geben Sie die CLR für die 64-Bit-Umgebung ein.
Migration und Plattformaufruf
Migration und COM-Interoperabilität
Migration und unsicherer Code
Migration und Marshalling
Migration und Serialisierung
Zusammenfassung

Einführung

In diesem Whitepaper werden Folgendes erläutert:

  • Was ist bei der Migration von verwalteten Anwendungen von 32-Bit zu 64-Bit erforderlich?
  • Probleme, die sich auf die Migration auswirken können
  • Welche Tools stehen ihnen zur Verfügung?

Diese Informationen sollen nicht präskriptiv sein; Sie soll Sie vielmehr mit den verschiedenen Bereichen vertraut machen, die während der Migration zu 64-Bit für Probleme anfällig sind. An diesem Punkt gibt es kein spezifisches "Kochbuch" mit Schritten, die Sie befolgen können, und stellen Sie sicher, dass Ihr Code auf 64-Bit funktioniert. Die in diesem Whitepaper enthaltenen Informationen machen Sie mit den verschiedenen Problemen und den zu überprüfenden Themen vertraut.

Wie Sie bald sehen werden, müssen Sie Ihre Anwendung und ihre Abhängigkeiten überprüfen, wenn Ihre verwaltete Assembly keinen 100 %igen sicheren Code aufweist, um Ihre Probleme bei der Migration zu 64-Bit zu ermitteln. Viele der Elemente, über die Sie in den nächsten Abschnitten lesen werden, können durch Programmieränderungen behandelt werden. In einer Reihe von Fällen müssen Sie auch Zeit für die Aktualisierung Ihres Codes festlegen, um in 32-Bit- und 64-Bit-Umgebungen ordnungsgemäß ausgeführt zu werden, wenn er in beiden Umgebungen ausgeführt werden soll.

Microsoft .NET ist eine Reihe von Softwaretechnologien zum Verbinden von Informationen, Personen, Systemen und Geräten. Seit der Version 1.0 im Jahr 2002 haben Organisationen erfolgreich bereitgestellt. NET-basierte Lösungen, unabhängig davon, ob sie intern, von unabhängigen Softwareanbietern (ISVs) oder einer Kombination erstellt wurden. Es gibt mehrere Arten von .NET-Anwendungen, die die Grenzen der 32-Bit-Umgebung verschieben. Zu diesen Herausforderungen gehören unter anderem die Notwendigkeit eines realer adressierbaren Speichers und die Notwendigkeit einer erhöhten Gleitkommaleistung. x64 und Itanium bieten eine bessere Leistung für Gleitkommavorgänge als mit x86. Es ist jedoch auch möglich, dass sich die Ergebnisse, die Sie auf x64 oder Itanium erhalten, von den Ergebnissen unterscheiden, die Sie auf x86 erhalten. Die 64-Bit-Plattform soll dabei helfen, diese Probleme zu beheben.

Mit der Veröffentlichung von .NET Framework Version 2.0 bietet Microsoft Unterstützung für verwalteten Code, der auf den x64- und Itanium-64-Bit-Plattformen ausgeführt wird.

Verwalteter Code ist einfach "Code", der genügend Informationen bereitstellt, damit die .NET Common Language Runtime (CLR) eine Reihe von Kerndiensten bereitstellen kann, einschließlich:

  • Selbstbeschreibung von Code und Daten über Metadaten
  • Stackwalk
  • Sicherheit
  • Garbage Collection
  • Just-in-Time-Kompilierung

Zusätzlich zu verwaltetem Code gibt es mehrere andere Definitionen, die bei der Untersuchung der Migrationsprobleme wichtig sind.

Verwaltete Daten: Daten, die auf dem verwalteten Heap zugeordnet und über die Garbage Collection gesammelt werden.

Assembly– die Bereitstellungseinheit, die es der CLR ermöglicht, den Inhalt einer Anwendung vollständig zu verstehen und die von der Anwendung definierten Versionsverwaltungs- und Abhängigkeitsregeln zu erzwingen.

Geben Sie sicheren Code ein: Code, der nur verwaltete Daten und keine nicht überprüfbaren Datentypen oder nicht unterstützte Konvertierungs-/Coercion-Vorgänge für Datentypen verwendet (d. a. nicht diskriminierte Vereinigungen oder Struktur-/Schnittstellenzeiger). C#-, Visual Basic .NET- und Visual C++-Code, der mit /clr:safe kompiliert wurde, generieren sicheren Codetyp.

Unsicherer Code: Code, mit dem Vorgänge auf niedrigerer Ebene ausgeführt werden können, z. B. deklarieren und auf Zeigern arbeiten, Konvertierungen zwischen Zeigern und integralen Typen ausführen und die Adresse von Variablen übernehmen. Solche Vorgänge ermöglichen die Interfacing mit dem zugrunde liegenden Betriebssystem, den Zugriff auf ein gerät mit Speicherzuordnung oder die Implementierung eines zeitkritischen Algorithmus. Nativer Code ist unsicher.

Verwalteter Code in einer 32-Bit-Umgebung

Um die Komplexität der Migration von verwaltetem Code in die 64-Bit-Umgebung zu verstehen, sehen wir uns an, wie verwalteter Code in einer 32-Bit-Umgebung ausgeführt wird.

Wenn eine verwaltete oder nicht verwaltete Anwendung für die Ausführung ausgewählt wird, wird der Windows-Ladeprogramm aufgerufen und entscheidet, wie die Anwendung geladen und dann ausgeführt werden soll. Ein Teil dieses Prozesses besteht darin, einen Blick in den PE-Header (Portable Execution) der ausführbaren Datei zu werfen, um zu ermitteln, ob die CLR erforderlich ist. Wie Sie vielleicht bereits erraten haben, gibt es Flags im PE, die auf verwalteten Code hinweisen. In diesem Fall startet der Windows-Ladeprogramm die CLR, die dann für das Laden und Ausführen der verwalteten Anwendung verantwortlich ist. (Dies ist eine vereinfachte Beschreibung des Prozesses, da viele Schritte erforderlich sind, einschließlich der Bestimmung der auszuführenden CLR-Version, dem Einrichten der "Sandbox" der AppDomain usw.)

Interoperabilität

Während die verwaltete Anwendung ausgeführt wird, kann sie (sofern entsprechende Sicherheitsberechtigungen vorausgesetzt) mit nativen APIs (einschließlich der Win32-API) und COM-Objekten über die CLR-Interoperabilitätsfunktionen interagieren. Unabhängig davon, ob sie eine native Plattform-API aufrufen, eine COM-Anforderung stellen oder eine Struktur marshallen: Bei vollständiger Ausführung innerhalb der 32-Bit-Umgebung ist der Entwickler davon isoliert, über Datentypgrößen und Datenausrichtung nachdenken zu müssen.

Wenn Sie die Migration zu 64-Bit in Betracht ziehen, müssen Sie untersuchen, welche Abhängigkeiten Ihre Anwendung aufweist.

Geben Sie die CLR für die 64-Bit-Umgebung ein.

Damit verwalteter Code in der 64-Bit-Umgebung ausgeführt werden kann, die mit der 32-Bit-Umgebung konsistent ist, hat das .NET-Team die Common Language Runtime (CLR) für die 64-Bit-Systeme Itanium und x64 entwickelt. Die CLR musste die Regeln der Common Language Infrastructure (CLI) und des Common Language Type System strikt einhalten, um sicher zu stellen, dass in einer der .NET-Sprachen geschriebener Code wie in der 32-Bit-Umgebung interoperieren kann. Darüber hinaus finden Sie im Folgenden eine Liste mit einigen anderen Teilen, die ebenfalls für die 64-Bit-Umgebung portiert und/oder entwickelt werden mussten:

  • Basisklassenbibliotheken (System.*)
  • Just-In-Time-Compiler
  • Debugunterstützung
  • .NET Framework SDK

Unterstützung für verwalteten 64-Bit-Code

Die .NET Framework Version 2.0 unterstützt die Itanium- und x64-64-Bit-Prozessoren, die ausgeführt werden:

  • Windows Server 2003 SP1
  • Zukünftige Windows 64-Bit-Clientversionen

(Sie können die .NET Framework Version 2.0 unter Windows 2000 nicht installieren. Ausgabedateien, die mit den .NET Framework Versionen 1.0 und 1.1 erstellt wurden, werden unter WOW64 unter einem 64-Bit-Betriebssystem ausgeführt.)

Wenn Sie die .NET Framework Version 2.0 auf der 64-Bit-Plattform installieren, installieren Sie nicht nur die gesamte Infrastruktur, die erforderlich ist, um Ihren verwalteten Code im 64-Bit-Modus auszuführen, sondern sie installieren auch die erforderliche Infrastruktur, damit Ihr verwalteter Code im Windows-unter-Windows-Subsystem oder im WoW64 (32-Bit-Modus) ausgeführt werden kann.

Eine einfache 64-Bit-Migration

Betrachten Sie eine .NET-Anwendung, die 100 % sicheren Code aufweist. In diesem Szenario ist es möglich, ihre ausführbare .NET-Datei, die Sie auf Ihrem 32-Bit-Computer ausführen, in das 64-Bit-System zu verschieben und erfolgreich auszuführen. Warum funktioniert das? Da die Assembly zu 100 % typsicher ist, wissen wir, dass keine Abhängigkeiten von nativem Code oder COM-Objekten bestehen und dass es keinen "unsicheren" Code gibt, was bedeutet, dass die Anwendung vollständig unter der Kontrolle der CLR ausgeführt wird. Die CLR garantiert, dass der binäre Code, der als Ergebnis der JIT-Kompilierung (Just-in-Time) generiert wird, sich zwischen 32-Bit und 64-Bit unterscheidet, der ausgeführte Code jedoch semantisch gleich ist. (Sie können die .NET Framework Version 2.0 unter Windows 2000 nicht installieren. Ausgabedateien, die mit .NET Framework Versionen 1.0 und 1.1 erstellt wurden, werden unter WOW64 unter einem 64-Bit-Betriebssystem ausgeführt.)

In der Realität ist das vorherige Szenario etwas komplizierter, wenn es darum geht, die verwaltete Anwendung zu laden. Wie im vorherigen Abschnitt erläutert, entscheidet das Windows-Ladeprogramm über das Laden und Ausführen der Anwendung. Im Gegensatz zur 32-Bit-Umgebung bedeutet die Ausführung auf einer 64-Bit-Windows-Plattform jedoch, dass es zwei (2) Umgebungen gibt, in denen die Anwendung ausgeführt werden könnte, entweder im nativen 64-Bit-Modus oder in WoW64.

Der Windows-Ladevorgang muss nun entscheidungen treffen, basierend auf dem, was im PE-Header ermittelt wird. Wie Sie vielleicht erraten haben, gibt es im verwalteten Code einstellbare Flags, die diesen Prozess unterstützen. (Siehe corflags.exe, um die Einstellungen in einem PE anzuzeigen.) Die folgende Liste stellt Informationen dar, die im PE enthalten sind, die den Entscheidungsprozess erleichtern.

  • 64-Bit: Gibt an, dass der Entwickler die Assembly speziell für einen 64-Bit-Prozess erstellt hat.
  • 32-Bit: Gibt an, dass der Entwickler die Assembly speziell für einen 32-Bit-Prozess erstellt hat. In diesem instance wird die Assembly in WoW64 ausgeführt.
  • Agnostisch: Gibt an, dass der Entwickler die Assembly mit Visual Studio 2005 mit dem Codenamen "Whidbey" erstellt hat. oder neuere Tools, die die Assembly entweder im 64-Bit- oder im 32-Bit-Modus ausführen können. In diesem Fall führt der 64-Bit-Windows-Ladeprogramm die Assembly in 64-Bit aus.
  • Legacy: Gibt an, dass die Tools, mit denen die Assembly erstellt wurde, "pre-Whidbey" waren. In diesem fall wird die Assembly in WoW64 ausgeführt.

Hinweis Es gibt auch Informationen im PE, die dem Windows-Ladeprogramm mitteilt, ob die Assembly für eine bestimmte Architektur vorgesehen ist. Diese zusätzlichen Informationen stellen sicher, dass Assemblys, die für eine bestimmte Architektur bestimmt sind, nicht in einer anderen architektur geladen werden.

Mit den C#-, Visual Basic .NET- und C++-Whidbey-Compilern können Sie die entsprechenden Flags im PE-Header festlegen. Beispielsweise verfügen C# und THIRD über die Compileroption /platform:{anycpu, x86, Itanium, x64} .

Hinweis Obwohl es technisch möglich ist, die Flags im PE-Header einer Assembly nach der Kompilierung zu ändern, empfiehlt Microsoft dies nicht.

Wenn Sie wissen möchten, wie diese Flags für eine verwaltete Assembly festgelegt werden, können Sie das im .NET Framework SDK bereitgestellte ILDASM-Hilfsprogramm ausführen. Die folgende Abbildung zeigt eine Legacyanwendung.

Beachten Sie, dass ein Entwickler, der eine Assembly als Win64 markiert hat, ermittelt hat, dass alle Abhängigkeiten der Anwendung im 64-Bit-Modus ausgeführt werden. Ein 64-Bit-Prozess kann keine 32-Bit-Komponente im Prozess verwenden (und ein 32-Bit-Prozess kann keine 64-Bit-Komponente im Prozess laden). Beachten Sie, dass die Fähigkeit des Systems, die Assembly in einen 64-Bit-Prozess zu laden, nicht automatisch bedeutet, dass sie ordnungsgemäß ausgeführt wird.

Daher wissen wir jetzt, dass eine Anwendung, die aus verwaltetem Code besteht, der zu 100 % vom Typ sicher ist, auf eine 64-Bit-Plattform kopiert werden kann (oder xcopy bereitstellen) und sie JIT verwenden und erfolgreich mit .NET im 64-Bit-Modus ausgeführt werden kann.

Wir sehen jedoch häufig Situationen, die nicht ideal sind, und das bringt uns zum Standard Fokus dieses Artikels, der darin besteht, das Bewusstsein für die Probleme im Zusammenhang mit der Migration zu erhöhen.

Sie können über eine Anwendung verfügen, die nicht 100 % vom Typ sicher ist und dennoch erfolgreich in 64-Bit unter .NET ausgeführt werden kann. Es ist wichtig, dass Sie Ihre Anwendung sorgfältig betrachten, dabei die in den folgenden Abschnitten erläuterten potenziellen Probleme berücksichtigen und feststellen, ob Sie in 64-Bit erfolgreich ausgeführt werden können oder nicht.

Migration und Plattformaufruf

Die Verwendung der Plattformaufruffunktionen (oder p/invoke) von .NET bezieht sich auf verwalteten Code, der Aufrufe von nicht verwaltetem oder nativem Code durchführt. In einem typischen Szenario ist dieser native Code eine Dynamic Link Library (DLL), die teil des Systems (Windows-API usw.), Teil Ihrer Anwendung oder einer Bibliothek eines Drittanbieters ist.

Die Verwendung von nicht verwaltetem Code bedeutet nicht explizit, dass bei einer Migration zu 64-Bit Probleme auftreten. es sollte vielmehr als Indikator betrachtet werden, dass zusätzliche Untersuchungen erforderlich sind.

Datentypen in Windows

Jede Anwendung und jedes Betriebssystem verfügt über ein abstraktes Datenmodell. Viele Anwendungen machen dieses Datenmodell nicht explizit verfügbar, aber das Modell leitet die Art und Weise, wie der Code der Anwendung geschrieben wird. Im 32-Bit-Programmiermodell (bekannt als ILP32-Modell) sind die Datentypen Ganzzahl, Long und Zeiger 32 Bit lang. Die meisten Entwickler haben dieses Modell verwendet, ohne es zu merken.

In 64-Bit-Microsoft Windows ist diese Annahme der Parität bei Datentypgrößen ungültig. Wenn alle Datentypen eine Länge von 64 Bit aufweisen, wird Speicherplatz verschwendet, da die meisten Anwendungen die erhöhte Größe nicht benötigen. Anwendungen benötigen jedoch Zeiger auf 64-Bit-Daten, und sie benötigen in ausgewählten Fällen die Möglichkeit, 64-Bit-Datentypen zu haben. Diese Überlegungen veranlassten das Windows-Team, ein abstraktes Datenmodell namens LLP64 (oder P64) auszuwählen. Im LLP64-Datenmodell werden nur Zeiger auf 64 Bit erweitert. alle anderen grundlegenden Datentypen (integer und long) bleiben 32 Bit lang.

Die .NET CLR für 64-Bit-Plattformen verwendet dasselbe abstrakte LLP64-Datenmodell. In .NET gibt es einen nicht allgemein bekannten integralen Datentyp, der speziell für "Zeigerinformationen" bestimmt ist: IntPtr , dessen Größe von der Plattform abhängig ist (z. B. 32-Bit oder 64-Bit), auf der er ausgeführt wird. Betrachten Sie den folgenden Codeausschnitt:

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

Wenn Sie auf einer 32-Bit-Plattform ausgeführt werden, erhalten Sie die folgende Ausgabe auf der Konsole:

SizeOf IntPtr is: 4

Auf einer 64-Bit-Plattform erhalten Sie die folgende Ausgabe in der Konsole:

SizeOf IntPtr is: 8

Hinweis Wenn Sie zur Laufzeit überprüfen möchten, ob Sie in einer 64-Bit-Umgebung ausgeführt werden, können Sie die IntPtr.Size als eine Möglichkeit verwenden, diese Bestimmung vorzunehmen.

Migrationsüberlegungen

Berücksichtigen Sie beim Migrieren verwalteter Anwendungen, die p/invoke verwenden, die folgenden Elemente:

  • Verfügbarkeit einer 64-Bit-Version der DLL
  • Verwendung von Datentypen

Verfügbarkeit

Eines der ersten Punkte, die ermittelt werden muss, ist, ob der nicht verwaltete Code, von dem Ihre Anwendung eine Abhängigkeit aufweist, für 64-Bit verfügbar ist.

Wenn dieser Code intern entwickelt wurde, erhöht sich Ihre Erfolgsfähigkeit. Natürlich müssen Sie weiterhin Ressourcen zuweisen, um den nicht verwalteten Code in 64-Bit zu portieren, zusammen mit entsprechenden Ressourcen für Tests, Qualitätssicherung usw. (Dieses Whitepaper gibt keine Empfehlungen zu Entwicklungsprozessen, sondern versucht, darauf hinzuweisen, dass Ressourcen möglicherweise Aufgaben zugewiesen werden müssen, um Code zu portieren.)

Wenn dieser Code von einem Drittanbieter stammt, müssen Sie untersuchen, ob dieser Drittanbieter bereits den Code für 64-Bit verfügbar hat und ob der Drittanbieter bereit wäre, ihn verfügbar zu machen.

Das höhere Risiko entsteht, wenn der Drittanbieter diesen Code nicht mehr unterstützt oder wenn der Dritte nicht bereit ist, die Arbeit zu erledigen. Diese Fälle erfordern zusätzliche Untersuchungen zu verfügbaren Bibliotheken, die ähnliche Funktionen haben, ob der Dritte den Port selbst durchführen lässt usw.

Es ist wichtig zu beachten, dass eine 64-Bit-Version des abhängigen Codes möglicherweise geänderte Schnittstellensignaturen aufweist, die zusätzliche Entwicklungsarbeit bedeuten und Unterschiede zwischen den 32-Bit- und 64-Bit-Versionen der Anwendung auflösen.

Datentypen

Die Verwendung von p/invoke erfordert, dass der in .NET entwickelte Code einen Prototyp der Methode deklariert, auf die der verwaltete Code abzielt. Mit der folgenden C-Deklaration:

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

Beispiele für prototypische Methoden sind unten dargestellt:

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

Sehen wir uns diese Beispiele mit Blick auf Probleme mit der 64-Bit-Migration an:

Im ersten Beispiel wird die DoWork-Methode aufgerufen, die zwei (2) 32-Bit-Ganzzahlen übergibt, und wir erwarten, dass eine 32-Bit-Ganzzahl zurückgegeben wird. Obwohl wir auf einer 64-Bit-Plattform ausgeführt werden, ist eine ganze Zahl immer noch 32 Bit. Es gibt nichts in diesem speziellen Beispiel, das unsere Migrationsbemühungen behindern sollte.

Im zweiten Beispiel sind einige Änderungen am Code erforderlich, um erfolgreich in 64-Bit ausgeführt zu werden. Was wir hier tun, ist das Aufrufen der GetData-Methode und die Angabe, dass eine ganze Zahl zurückgegeben wird, wobei die Funktion jedoch tatsächlich einen int-Zeiger zurückgibt. Hier liegt unser Problem: Denken Sie daran, dass ganze Zahlen 32 Bits sind, aber in 64-Bit-Zeigern 8 Bytes sind. Wie sich herausstellt, wurde einiges an Code in der 32-Bit-Welt unter der Annahme geschrieben, dass ein Zeiger und eine ganze Zahl die gleiche Länge von 4 Bytes hatten. In der 64-Bit-Welt ist dies nicht mehr wahr.

In diesem letzten Fall kann das Problem behoben werden, indem sie die Methodendeklaration so ändert, dass anstelle des int ein IntPtr verwendet wird.

public unsafe static extern IntPtr GetData();

Diese Änderung funktioniert sowohl in der 32-Bit- als auch in der 64-Bit-Umgebung. Denken Sie daran, dass IntPtr plattformspezifisch ist.

Die Verwendung von p/invoke in Ihrer verwalteten Anwendung bedeutet nicht, dass eine Migration zur 64-Bit-Plattform nicht möglich ist. Es bedeutet auch nicht, dass es Probleme geben wird. Dies bedeutet, dass Sie die Abhängigkeiten von nicht verwaltetem Code überprüfen müssen, über den Ihre verwaltete Anwendung verfügt, und feststellen müssen, ob Probleme auftreten.

Migration und COM-Interoperabilität

COM-Interoperabilität ist eine angenommene Funktion der .NET-Plattform. Wie in der vorherigen Diskussion zum Plattformaufruf bedeutet die Verwendung von COM-Interoperabilität, dass verwalteter Code Aufrufe für nicht verwalteten Code durchführt. Im Gegensatz zum Plattformaufruf bedeutet COM-Interoperabilität jedoch auch, dass nicht verwalteter Code verwalteten Code so aufrufen kann, als ob es sich um eine COM-Komponente handelt.

Auch hier bedeutet die Verwendung von nicht verwaltetem COM-Code nicht, dass bei einer Migration zu 64-Bit Probleme auftreten. es sollte vielmehr als Indikator betrachtet werden, dass zusätzliche Untersuchungen erforderlich sind.

Migrationsüberlegungen

Es ist wichtig zu verstehen, dass mit der Veröffentlichung von .NET Framework Version 2.0 keine Unterstützung für architekturübergreifende Interoperabilität vorhanden ist. Um es kurz zu machen, können Sie die COM-Interoperabilität zwischen 32-Bit und 64-Bit im selben Prozess nicht nutzen. Sie können jedoch die COM-Interoperabilität zwischen 32-Bit und 64-Bit nutzen, wenn Sie über einen com-Server außerhalb des Prozesses verfügen. Wenn Sie keinen out-of-Process-COM-Server verwenden können, sollten Sie Ihre verwaltete Assembly als Win32 und nicht als Win64 oder Als Agnostic kennzeichnen, damit Ihr Programm in WoW64 ausgeführt wird, sodass es mit dem 32-Bit-COM-Objekt zusammenarbeiten kann.

Im Folgenden werden die verschiedenen Überlegungen zur Nutzung der COM-Interoperabilität erläutert, bei denen verwalteter Code COM-Aufrufe in einer 64-Bit-Umgebung durchführt. Sie haben folgende Möglichkeiten:

  • Verfügbarkeit einer 64-Bit-Version der DLL
  • Verwendung von Datentypen
  • Typbibliotheken

Verfügbarkeit

Die Diskussion im Abschnitt p/invoke zur Verfügbarkeit einer 64-Bit-Version des abhängigen Codes ist auch für diesen Abschnitt relevant.

Datentypen

Die Diskussion im Abschnitt p/invoke zu Datentypen einer 64-Bit-Version des abhängigen Codes ist auch für diesen Abschnitt relevant.

Typbibliotheken

Im Gegensatz zu Assemblys können Typbibliotheken nicht als "neutral" gekennzeichnet werden. Sie müssen entweder als Win32 oder Win64 gekennzeichnet sein. Darüber hinaus muss die Typbibliothek für jede Umgebung registriert werden, in der die COM ausgeführt wird. Verwenden Sie tlbimp.exe, um eine 32-Bit- oder 64-Bit-Assembly aus einer Typbibliothek zu generieren.

Die Verwendung von COM-Interoperabilität in Ihrer verwalteten Anwendung bedeutet nicht, dass eine Migration zur 64-Bit-Plattform nicht möglich ist. Es bedeutet auch nicht, dass es Probleme geben wird. Dies bedeutet, dass Sie die Abhängigkeiten ihrer verwalteten Anwendung überprüfen und ermitteln müssen, ob Probleme auftreten.

Migration und unsicherer Code

Die C#-Kernsprache unterscheidet sich insbesondere von C und C++ durch das Weglassen von Zeigern als Datentyp. Stattdessen bietet C# Verweise und die Möglichkeit, Objekte zu erstellen, die von einem Garbage Collector verwaltet werden. In der C#-Kernsprache ist es einfach nicht möglich, eine nicht initialisierte Variable, einen "dangling"-Zeiger oder einen Ausdruck zu haben, der ein Array außerhalb seiner Grenzen indiziert. Ganze Kategorien von Fehlern, die regelmäßig C- und C++-Programme plagen, werden somit beseitigt.

Während praktisch jedes Zeigertypkonstrukt in C oder C++ eine Verweistyp-Entsprechung in C# aufweist, gibt es Situationen, in denen der Zugriff auf Zeigertypen erforderlich wird. Beispielsweise ist die Interfacing mit dem zugrunde liegenden Betriebssystem, der Zugriff auf ein gerät mit Speicherzuordnung oder die Implementierung eines zeitkritischen Algorithmus ohne Zugriff auf Zeiger möglicherweise nicht möglich oder praktisch. Um diese Anforderung zu beheben, bietet C# die Möglichkeit, unsicheren Code zu schreiben.

In unsicherem Code ist es möglich, Zeiger zu deklarieren und zu betreiben, Konvertierungen zwischen Zeigern und integralen Typen durchzuführen, die Adresse von Variablen zu übernehmen usw. In gewisser Weise ist das Schreiben von unsicherem Code ähnlich wie das Schreiben von C-Code in einem C#-Programm.

Unsicherer Code ist in der Tat ein "sicheres" Feature aus der Sicht von Entwicklern und Benutzern. Unsicherer Code muss mit dem Modifizierer unsicher gekennzeichnet sein, sodass Entwickler möglicherweise nicht versehentlich unsichere Features verwenden können.

Migrationsüberlegungen

Um die potenziellen Probleme mit unsicherem Code zu besprechen, sehen wir uns das folgende Beispiel an. Unser verwalteter Code führt Aufrufe an eine nicht verwaltete DLL aus. Insbesondere gibt es eine Methode namens GetDataBuffer , die 100 Elemente zurückgibt (in diesem Beispiel geben wir eine feste Anzahl von Elementen zurück). Jedes dieser Elemente besteht aus einer ganzen Zahl und einem Zeiger. Der folgende Beispielcode ist ein Auszug aus dem verwalteten Code, der die unsichere Funktion zeigt, die für die Verarbeitung dieser zurückgegebenen Daten verantwortlich ist.

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

Hinweis Dieses besondere Beispiel hätte ohne die Verwendung von unsicherem Code erreicht werden können. Genauer gesagt gibt es andere Techniken wie Marshalling, die hätten verwendet werden können. Zu diesem Zweck verwenden wir jedoch unsicheren Code.

UnsafeFn durchläuft die 100 Elemente und summiert die ganzzahligen Daten. Während wir einen Datenpuffer durchlaufen, muss der Code sowohl die ganze Zahl als auch den Zeiger durchlaufen. In der 32-Bit-Umgebung funktioniert dieser Code einwandfrei. Wie wir jedoch bereits erläutert haben, sind Zeiger in der 64-Bit-Umgebung 8 Bytes, und daher funktioniert das Codesegment (siehe unten) nicht ordnungsgemäß, da es eine gängige Programmiertechnik verwendet, z. B. die Behandlung eines Zeigers als äquivalent zu einer ganzen Zahl.

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

Damit dieser Code sowohl in der 32-Bit- als auch in der 64-Bit-Umgebung funktioniert, muss der Code wie folgt geändert werden.

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

Wie wir gerade gesehen haben, gibt es Instanzen, in denen die Verwendung von unsicherem Code erforderlich ist. In den meisten Fällen ist dies aufgrund der Abhängigkeit des verwalteten Codes von einer anderen Schnittstelle erforderlich. Unabhängig von den Gründen, aus welchen Gründen unsicherer Code vorhanden ist, muss er im Rahmen des Migrationsprozesses überprüft werden.

Das oben verwendete Beispiel ist relativ einfach, und die Korrektur, damit das Programm in 64-Bit funktioniert, war einfach. Offensichtlich gibt es viele Beispiele für unsicheren Code, der komplexer ist. Einige erfordern eine eingehende Überprüfung und vielleicht ein Schrittweises Zurücktreten und Überdenken des Ansatzes, den der verwaltete Code verwendet.

Um zu wiederholen, was Sie bereits gelesen haben: Wenn Sie unsicheren Code in Ihrer verwalteten Anwendung verwenden, bedeutet dies nicht, dass eine Migration zur 64-Bit-Plattform nicht möglich ist. Es bedeutet auch nicht, dass es Probleme geben wird. Dies bedeutet, dass Sie den gesamten unsicheren Code Ihrer verwalteten Anwendung überprüfen und feststellen müssen, ob Probleme auftreten.

Migration und Marshalling

Marshalling bietet eine Sammlung von Methoden zum Zuweisen von nicht verwaltetem Arbeitsspeicher, zum Kopieren nicht verwalteter Speicherblöcke und zum Konvertieren verwalteter In nicht verwaltete Typen sowie andere verschiedene Methoden, die bei der Interaktion mit nicht verwaltetem Code verwendet werden.

Marshalling wird über die .NET Marshal-Klasse manifestiert. Die statischen oder in Visual Basic freigegebenen Methoden, die für die Marshal-Klasse definiert sind, sind für die Arbeit mit nicht verwalteten Daten unerlässlich. Fortgeschrittene Entwickler, die benutzerdefinierte Marshaller erstellen, die eine Brücke zwischen den verwalteten und nicht verwalteten Programmiermodellen bereitstellen müssen, verwenden in der Regel die meisten der definierten Methoden.

Migrationsüberlegungen

Marshalling stellt einige der komplexeren Herausforderungen im Zusammenhang mit der Migration von Anwendungen zu 64-Bit. Angesichts der Art dessen, was der Entwickler mit Marshalling erreichen möchte, nämlich der Übertragung strukturierter Informationen an, von oder zu verwaltetem und nicht verwaltetem Code, werden wir sehen, dass wir Informationen bereitstellen, manchmal auf niedriger Ebene, um das System zu unterstützen.

In Bezug auf das Layout gibt es zwei spezifische Deklarationen, die vom Entwickler gemacht werden können; Diese Deklarationen werden in der Regel mithilfe von Codierungsattributen erstellt.

LayoutKind.Sequential

Sehen wir uns die Definition an, die in der Hilfe zum .NET Framework SDK angegeben ist:

"Die Member des Objekts werden sequenziell in der Reihenfolge angeordnet, in der sie angezeigt werden, wenn sie in den nicht verwalteten Speicher exportiert werden. Die Member sind gemäß der in StructLayoutAttribute.Pack angegebenen Verpackung angeordnet und können unkonstruiert sein."

Uns wird mitgeteilt, dass das Layout spezifisch für die Reihenfolge ist, in der es definiert ist. Dann müssen wir nur sicherstellen, dass die verwalteten und nicht verwalteten Deklarationen ähnlich sind. Aber uns wird auch gesagt, dass packen auch ein wichtiger Bestandteil ist. An diesem Punkt werden Sie nicht überrascht sein zu erfahren, dass ohne explizites Eingreifen des Entwicklers ein Standardpaketwert vorhanden ist. Wie Sie vielleicht bereits erraten haben, ist der Standardpaketwert zwischen 32-Bit- und 64-Bit-Systemen nicht identisch.

Die -Anweisung in der Definition zu nicht zusammenhängenden Membern bezieht sich auf die Tatsache, dass Daten, die im Arbeitsspeicher angeordnet sind, aufgrund von Standardpaketgrößen nicht bei Byte 0, Byte 1, Byte2 usw. liegen dürfen. Stattdessen ist das erste Element bei Byte 0, aber das zweite Element kann bei Byte 4 sein. Das System führt diese Standardverpackung aus, damit der Computer Zugriff auf Mitglieder erhält, ohne probleme mit falscher Ausrichtung umgehen zu müssen.

Hier ist ein Bereich, den wir genau auf die Verpackung achten müssen, und gleichzeitig versuchen, das System in seinem bevorzugten Modus agieren zu lassen.

Im Folgenden finden Sie ein Beispiel für eine Struktur, die in verwaltetem Code definiert ist, sowie die entsprechende Struktur, die in nicht verwaltetem Code definiert ist. Beachten Sie, wie in diesem Beispiel das Festlegen des Packwerts in beiden Umgebungen veranschaulicht wird.

[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

Sehen wir uns die Definition an, die in der .NET FrameworkSDK-Hilfe bereitgestellt wird:

"Die genaue Position jedes Elements eines Objekts im nicht verwalteten Speicher wird explizit gesteuert. Jedes Element muss das FieldOffsetAttribute verwenden, um die Position dieses Felds innerhalb des Typs anzugeben."

Uns wird hier mitgeteilt, dass der Entwickler genaue Offsets bereitstellen wird, um die Marshallung von Informationen zu unterstützen. Daher ist es wichtig, dass der Entwickler die Informationen im FieldOffset-Attribut korrekt angibt.

Wo liegen also die potenziellen Probleme? Beachten Sie, dass die Feldoffsets mit der Größe der voranschreibigen Datenmembergröße definiert sind. Beachten Sie, dass nicht alle Datentypgrößen zwischen 32 Bit und 64 Bit gleich sind. Insbesondere sind Zeiger entweder 4 oder 8 Bytes lang.

Wir haben jetzt einen Fall, in dem wir möglicherweise unseren verwalteten Quellcode aktualisieren müssen, um die spezifischen Umgebungen zu verwenden. Das folgende Beispiel zeigt eine Struktur, die einen Zeiger enthält. Obwohl wir den Zeiger als IntPtr festgelegt haben, gibt es immer noch einen Unterschied beim Wechsel zu 64-Bit.

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

Für 64-Bit müssen wir den Feldoffset für den letzten Datenmember in der Struktur anpassen, da er tatsächlich bei Offset 12 statt 8 beginnt.

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

Die Verwendung von Marshalling ist eine Realität, wenn eine komplexe Interoperabilität zwischen verwaltetem und nicht verwaltetem Code erforderlich ist. Die Nutzung dieser leistungsstarken Funktion ist kein Indikator dafür, dass Sie Ihre 32-Bit-Anwendung zur 64-Bit-Umgebung migrieren können. Aufgrund der Komplexität, die mit der Verwendung von Marshalling verbunden ist, ist dies jedoch ein Bereich, in dem sorgfältige Detailgenauigkeit erforderlich ist.

Die Analyse Ihres Codes zeigt an, ob separate Binärdateien für jede der Plattformen erforderlich sind und ob Sie auch Änderungen an Ihrem nicht verwalteten Code vornehmen müssen, um Probleme wie das Verpacken zu beheben.

Migration und Serialisierung

Unter Serialisierung wird das Konvertieren des Zustands eines Objekts in eine Form verstanden, die erhalten oder transportiert werden kann. Das Gegenstück zur Serialisierung ist die Deserialisierung, die einen Stream in ein Objekt konvertiert. Zusammen ermöglichen es diese Prozesse, dass Daten einfach gespeichert und übertragen werden.

.NET Framework zeichnet sich durch zwei Serialisierungstechnologien aus:

  • Bei der binären Serialisierung wird die Typtreue beibehalten. Dies ist nützlich, um den Zustand eines Objekts zwischen verschiedenen Aufrufen einer Anwendung zu speichern. So können Sie z. B. ein Objekt für unterschiedliche Anwendungen freigeben, indem Sie es in die Zwischenablage serialisieren. Sie können ein Objekt in einen Stream, einen Datenträger, den Arbeitsspeicher, über das Netzwerk usw. serialisieren. .NET Remoting verwendet die Serialisierung, um Objekte "nach Wert" von einem Computer oder einer Anwendungsdomäne an eine andere zu übergeben.
  • Bei der XML-Serialisierung werden nur öffentliche Eigenschaften und Felder serialisiert, und die Typtreue wird nicht beibehalten. Dies ist hilfreich, wenn Daten bereitgestellt oder benutzt werden sollen, ohne die Anwendung, welche die Daten verwendet, zu beschränken. Da XML ein offener Standard ist, stellt XML eine attraktive Möglichkeit für den Datenaustausch im Internet dar. Ebenso ist SOAP ein offener Standard und damit auch eine attraktive Alternative.

Migrationsüberlegungen

Wenn wir über Serialisierung nachdenken, müssen wir daran denken, was wir erreichen möchten. Eine Frage, die Sie bei der Migration zu 64 Bit berücksichtigen sollten, ist, ob Sie beabsichtigen, serialisierte Informationen zwischen den verschiedenen Plattformen zu teilen. Anders ausgedrückt: Die verwaltete 64-Bit-Anwendung liest (oder deserialisiert) informationen, die von einer verwalteten 32-Bit-Anwendung gespeichert sind.

Ihre Antwort trägt dazu bei, die Komplexität Ihrer Lösung zu erhöhen.

  • Möglicherweise möchten Sie Ihre eigenen Serialisierungsroutinen schreiben, um die Plattformen zu berücksichtigen.
  • Möglicherweise möchten Sie die Freigabe von Informationen einschränken und gleichzeitig jeder Plattform erlauben, ihre eigenen Daten zu lesen und zu schreiben.
  • Möglicherweise möchten Sie die Serialisierung erneut überprüfen und Änderungen vornehmen, um einige der Probleme zu vermeiden.

Was sind nach all dem die Überlegungen in Bezug auf die Serialisierung?

  • IntPtr ist je nach Plattform entweder 4 oder 8 Bytes lang. Wenn Sie die Informationen serialisieren, schreiben Sie plattformspezifische Daten in die Ausgabe. Dies bedeutet, dass Probleme auftreten können und werden, wenn Sie versuchen, diese Informationen zu teilen.

Wenn Sie unsere Diskussion im vorherigen Abschnitt über Marshalling und Offsets in Betracht ziehen, stellen Sie sich möglicherweise eine oder zwei Fragen dazu, wie die Serialisierung Verpackungsinformationen behandelt. Für die binäre Serialisierung verwendet .NET intern den richtigen nicht ausgerichteten Zugriff auf den Serialisierungsdatenstrom, indem bytebasierte Lesevorgänge verwendet und die Daten ordnungsgemäß verarbeitet werden.

Wie wir gerade gesehen haben, verhindert die Verwendung der Serialisierung nicht die Migration zu 64-Bit. Wenn Sie die XML-Serialisierung verwenden, müssen Sie während des Serialisierungsprozesses von und in native verwaltete Typen konvertieren, um Sie von den Unterschieden zwischen den Plattformen zu isolieren. Die Verwendung der binären Serialisierung bietet Ihnen eine umfassendere Lösung, schafft jedoch die Situation, in der Entscheidungen darüber getroffen werden müssen, wie die verschiedenen Plattformen serialisierte Informationen gemeinsam nutzen.

Zusammenfassung

Die Migration zu 64-Bit wird kommen, und Microsoft hat daran gearbeitet, den Übergang von verwalteten 32-Bit-Anwendungen zu 64-Bit-Anwendungen so einfach wie möglich zu gestalten.

Es ist jedoch unrealistisch anzunehmen, dass man 32-Bit-Code einfach in einer 64-Bit-Umgebung ausführen und ausführen kann, ohne sich anzusehen, was Sie migrieren.

Wie bereits erwähnt, können Sie mit 100 % sicherem verwaltetem Code einfach auf die 64-Bit-Plattform kopieren und erfolgreich unter der 64-Bit-CLR ausführen.

Wahrscheinlich ist die verwaltete Anwendung jedoch an einem oder allen der folgenden Komponenten beteiligt:

  • Aufrufen von Plattform-APIs über p/invoke
  • Aufrufen von COM-Objekten
  • Verwenden von unsicheren Code
  • Verwenden von Marshalling als Mechanismus zum Teilen von Informationen
  • Verwenden der Serialisierung als Methode zum Beibehalten des Zustands

Unabhängig davon, welche dieser Dinge Ihre Anwendung ausführt, ist es wichtig, Ihre Hausaufgaben zu machen und zu untersuchen, was Ihr Code tut und welche Abhängigkeiten Sie haben. Nachdem Sie diese Hausaufgaben gemacht haben, müssen Sie sich Ihre Auswahl ansehen, um eine oder alle der folgenden Aufgaben zu erledigen:

  • Migrieren Sie den Code ohne Änderungen.
  • Nehmen Sie Änderungen am Code vor, um 64-Bit-Zeiger ordnungsgemäß zu behandeln.
  • Arbeiten Sie mit anderen Anbietern usw. zusammen, um 64-Bit-Versionen ihrer Produkte bereitzustellen.
  • Nehmen Sie Änderungen an Ihrer Logik vor, um Marshalling und/oder Serialisierung zu verarbeiten.

Es kann Fälle geben, in denen Sie sich entscheiden, den verwalteten Code nicht zu 64-Bit zu migrieren. In diesem Fall haben Sie die Möglichkeit, Ihre Assemblys zu markieren, damit das Windows-Ladeprogramm beim Start die richtige Vorgehensweise ausführen kann. Beachten Sie, dass Downstreamabhängigkeiten direkte Auswirkungen auf die gesamte Anwendung haben.

Fxcop

Sie sollten auch die Verfügbaren Tools kennen, die Sie bei der Migration unterstützen.

Heute verfügt Microsoft über ein Tool namens FxCop, ein Codeanalysetool, das verwaltete .NET-Codeassemblys auf Konformität mit den Microsoft .NET Framework Design Guidelines überprüft. Es verwendet Reflektion, MSIL-Analyse und Aufrufdiagrammanalyse, um Assemblys auf mehr als 200 Fehler in den folgenden Bereichen zu untersuchen: Benennungskonventionen, Bibliotheksentwurf, Lokalisierung, Sicherheit und Leistung. FxCop enthält sowohl GUI- als auch Befehlszeilenversionen des Tools sowie ein SDK, um Ihre eigenen Regeln zu erstellen. Weitere Informationen finden Sie auf der FxCop-Website . Microsoft ist gerade dabei, zusätzliche FxCop-Regeln zu entwickeln, die Ihnen Informationen zur Verfügung stellen, die Sie bei Ihrer Migration unterstützen.

Es gibt auch verwaltete Bibliotheksfunktionen, die Sie zur Laufzeit bei der Ermittlung der Umgebung unterstützen, in der Sie ausgeführt werden.

  • System.IntPtr.Size– um zu bestimmen, ob Sie im 32-Bit- oder 64-Bit-Modus ausgeführt werden
  • System.Reflection.Module.GetPEKind: programmgesteuerte Abfragen eines .exe oder .dll, um festzustellen, ob es nur auf einer bestimmten Plattform oder unter WOW64 ausgeführt werden soll

Es gibt keine spezifischen Verfahren, um alle Herausforderungen zu bewältigen, auf die Sie stoßen könnten. Dieses Whitepaper soll Ihr Bewusstsein für diese Herausforderungen schärfen und Ihnen mögliche Alternativen vorstellen.