32 ビットマネージド コードから 64 ビットへの移行

 

Microsoft Corporation

更新日: 2005 年 5 月

適用対象:
   Microsoft .NET
   Microsoft .NET Framework 2.0

概要: 32 ビットマネージド アプリケーションの 64 ビットへの移行に関連する事項、移行に影響を与える可能性がある問題、および役立つツールについて説明します。 (17ページ印刷)

内容

はじめに
32 ビット環境のマネージド コード
64 ビット環境の CLR を入力する
移行とプラットフォーム呼び出し
移行と COM の相互運用性
移行と安全でないコード
移行とマーシャリング
移行とシリアル化
まとめ

はじめに

このホワイトペーパーでは、以下について説明します。

  • マネージド アプリケーションの 32 ビットから 64 ビットへの移行に関連するもの
  • 移行に影響を与える可能性がある問題
  • どのようなツールを使用して支援するか

この情報は規範的なものではありません。むしろ、64 ビットに移行するプロセス中に問題の影響を受けやすいさまざまな領域を理解することを目的としています。 この時点で、コードが 64 ビットで動作することを保証する手順の具体的な "クックブック" はありません。 このホワイトペーパーに記載されている情報は、さまざまな問題と確認すべき事項について理解します。

すぐにご覧のように、マネージド アセンブリが 100% 型セーフ コードでない場合は、アプリケーションとその依存関係を確認して、64 ビットへの移行に関する問題を特定する必要があります。 次のセクションで説明する項目の多くは、プログラミングの変更によって対処できます。 また、32 ビット環境と 64 ビット環境の両方で正しく実行するには、コードを更新する時間を確保する必要があります (両方で実行する場合)。

Microsoft .NET は、情報、ユーザー、システム、デバイスを接続するためのソフトウェア テクノロジのセットです。 2002 年の 1.0 リリース以降、組織はデプロイに成功しました。社内、独立系ソフトウェア ベンダー (ISV) による、またはいくつかの組み合わせによる、NET ベースのソリューション。 32 ビット環境の制限を押し上げる .NET アプリケーションには、いくつかの種類があります。 これらの課題には、より実際のアドレス指定可能なメモリの必要性と浮動小数点パフォーマンスの向上が含まれますが、これらに限定されません。 x64 と Itanium では、x86 よりも浮動小数点演算のパフォーマンスが向上します。 ただし、x64 または Itanium で得られる結果が、x86 で得られる結果と異なる可能性もあります。 64 ビット プラットフォームは、これらの問題に対処することを目的としています。

.NET Framework バージョン 2.0 のリリースでは、Microsoft では、x64 および Itanium 64 ビット プラットフォームで実行されるマネージド コードのサポートが含まれています。

マネージド コードは、.NET 共通言語ランタイム (CLR) が次のような一連のコア サービスを提供するのに十分な情報を提供する単なる "コード" です。

  • メタデータを使用したコードとデータの自己説明
  • スタック ウォーキング
  • セキュリティ
  • ガベージ コレクション
  • Just-In-Time コンパイル

マネージド コードに加えて、移行の問題を調査する際に理解することが重要な定義がいくつかあります。

マネージド データ: マネージド ヒープに割り当てられ、ガベージ コレクションを介して収集されるデータ。

アセンブリ- CLR がアプリケーションの内容を完全に理解し、アプリケーションによって定義されたバージョン管理と依存関係の規則を適用できるようにするデプロイの単位。

型セーフ コード: マネージド データのみを使用し、検証できないデータ型やサポートされていないデータ型の変換/強制型の操作 (つまり、非判別共用体または構造体/インターフェイス ポインター) を使用しないコード。 /clr:safe でコンパイルされた C#、Visual Basic .NET、および Visual C++ コードは、型セーフ コードを生成します。

安全でないコード: ポインターの宣言と操作、ポインターと整数型の間の変換の実行、変数のアドレスの取得などの下位レベルの操作を実行できるコード。 このような操作により、基になるオペレーティング システムとのインターフェイス、メモリ マップド デバイスへのアクセス、またはタイム クリティカルなアルゴリズムの実装が可能になります。 ネイティブ コードは安全ではありません。

32 ビット環境のマネージド コード

マネージド コードを 64 ビット環境に移行する場合の複雑さを理解するために、32 ビット環境でマネージド コードがどのように実行されるかを確認しましょう。

マネージドまたはアンマネージドのアプリケーションを実行するように選択すると、Windows ローダーが呼び出され、アプリケーションを読み込んで実行する方法を決定する役割を担います。 このプロセスの一部では、実行可能ファイルのポータブル実行 (PE) ヘッダーの内部をピークして、CLR が必要かどうかを判断します。 既に推測したように、PE にはマネージド コードを示すフラグがあります。 この場合、Windows ローダーは、マネージド アプリケーションの読み込みと実行を担当する CLR を起動します。 (これは、実行する CLR のバージョンの決定、AppDomain 'sandbox' の設定など、多くの手順が必要であるため、プロセスの簡略化された説明です)。

相互運用性

マネージド アプリケーションを実行すると、CLR 相互運用性機能を使用してネイティブ API (Win32 API を含む) や COM オブジェクトと対話できます (適切なセキュリティアクセス許可を想定)。 ネイティブ プラットフォーム API の呼び出し、COM 要求の作成、構造体のマーシャリングのいずれであっても、32 ビット環境内で完全に実行する場合、開発者はデータ型のサイズとデータの配置について考慮する必要はありません。

64 ビットへの移行を検討するときは、アプリケーションの依存関係を調査することが不可欠です。

64 ビット環境の CLR を入力する

マネージド コードを 32 ビット環境と一致する 64 ビット環境で実行するために、.NET チームは Itanium および x64 64 ビット システム用の共通言語ランタイム (CLR) を開発しました。 CLR は、.NET 言語のいずれかで記述されたコードが 32 ビット環境と同様に相互運用できることを保証するために、共通言語インフラストラクチャ (CLI) と共通言語型システムの規則に厳密に準拠する必要がありました。 さらに、64 ビット環境用に移植および/または開発する必要があったその他の部分の一覧を次に示します。

  • 基底クラス ライブラリ (System.*)
  • Just-In-Time コンパイラ
  • デバッグのサポート
  • .NET Framework SDK

64 ビットマネージド コードのサポート

.NET Framework バージョン 2.0 では、Itanium および x64 64 ビット プロセッサがサポートされています。

  • Windows Server 2003 SP1
  • 将来のWindows 64 ビット クライアント リリース

(Windows 2000 に .NET Framework バージョン 2.0 をインストールすることはできません。.NET Framework バージョン 1.0 と 1.1 を使用して生成された出力ファイルは、64 ビット オペレーティング システムで WOW64 で実行されます)。

.NET Framework バージョン 2.0 を 64 ビット プラットフォームにインストールする場合、マネージド コードを 64 ビット モードで実行するために必要なすべてのインフラストラクチャをインストールするだけでなく、マネージド コードをWindowsオン Windows サブシステム(32 ビット モード)で実行するために必要なインフラストラクチャをインストールします。

単純な 64 ビット移行

100% 型の安全なコードである .NET アプリケーションを考えてみましょう。 このシナリオでは、32 ビット コンピューターで実行する .NET 実行可能ファイルを取得し、64 ビット システムに移動して正常に実行することができます。 これが機能する理由 アセンブリは 100% の型セーフであるため、ネイティブ コードまたは COM オブジェクトへの依存関係がなく、アプリケーションが CLR の制御下で完全に実行されることを意味する "安全でない" コードがないことを認識しています。 CLR では、Just-In-Time (JIT) コンパイルの結果として生成されるバイナリ コードは 32 ビットと 64 ビットで異なることがありますが、実行されるコードは両方とも意味的に同じになります。 (Windows 2000 に .NET Framework バージョン 2.0 をインストールすることはできません。.NET Framework バージョン 1.0 と 1.1 を使用して生成された出力ファイルは、64 ビット オペレーティング システムで WOW64 で実行されます)。

実際には、マネージド アプリケーションを読み込むという観点から、前のシナリオはもう少し複雑です。 前のセクションで説明したように、Windows ローダーは、アプリケーションを読み込んで実行する方法を決定する役割を担います。 ただし、32 ビット環境とは異なり、64 ビット Windows プラットフォームで実行されている場合は、ネイティブ 64 ビット モードまたは WoW64 のいずれかでアプリケーションを実行できる 2 つの環境があることを意味します。

Windows ローダーは、PE ヘッダーで検出された内容に基づいて決定を下す必要があります。 ご想像のとおり、マネージド コードには、このプロセスを支援する設定可能なフラグがあります。 (PE の設定を表示するには、corflags.exeを参照してください)。次の一覧は、意思決定プロセスに役立つ PE で見つかった情報を表します。

  • 64 ビット — 開発者が 64 ビット プロセスを対象とするアセンブリを構築したことを示します。
  • 32 ビット— 開発者が 32 ビット プロセスを対象とするアセンブリを構築したことを示します。 このインスタンスでは、アセンブリは WoW64 で実行されます。
  • 非依存 - 開発者が Visual Studio 2005 でアセンブリをビルドしたことを示します。コード名は "Whidbey" です。 64 ビットまたは 32 ビット モードでアセンブリを実行できること。 この場合、64 ビット Windows ローダーは 64 ビットでアセンブリを実行します。
  • レガシ — アセンブリを構築したツールが "pre-Whidbey" であることを示します。 この場合、アセンブリは WoW64 で実行されます。

メモPE には、アセンブリが特定のアーキテクチャを対象としているかどうかをWindows ローダーに通知する情報もあります。 この追加情報により、特定のアーキテクチャを対象とするアセンブリが別のアーキテクチャに読み込まれないようにします。

C#、Visual Basic .NET、C++ Whidbey コンパイラを使用すると、PE ヘッダーに適切なフラグを設定できます。 たとえば、C# と THIRD には /platform:{anycpu、x86、Itanium、x64} コンパイラ オプションがあります。

メモ アセンブリの PE ヘッダー内のフラグはコンパイル後に変更することは技術的には可能ですが、Microsoft ではこれを行うことをお勧めしません。

マネージド アセンブリでこれらのフラグがどのように設定されているかを知りたい場合は、.NET Framework SDK で提供される ILDASM ユーティリティを実行できます。 次の図は、"レガシ" アプリケーションを示しています。

アセンブリを Win64 としてマークする開発者は、アプリケーションのすべての依存関係が 64 ビット モードで実行されることを決定しました。 64 ビット プロセスでは、プロセスで 32 ビット コンポーネントを使用できません (また、32 ビット プロセスでは 64 ビット コンポーネントをプロセスに読み込めません)。 システムがアセンブリを 64 ビット プロセスに読み込む機能は、アセンブリが正しく実行されることを自動的に意味しないことに注意してください。

そのため、100% タイプ セーフなマネージド コードで構成されるアプリケーションを 64 ビット プラットフォームにコピー (または xcopy でデプロイ) し、JIT を作成し、64 ビット モードで .NET で正常に実行できるようになりました。

しかし、多くの場合、理想的ではない状況が見られ、このホワイト ペーパーの主な焦点は、移行に関連する問題の認識を高めることです。

100% のタイプ セーフではなく、.NET で 64 ビットで正常に実行できるアプリケーションを作成できます。 次のセクションで説明する潜在的な問題を念頭に置いて、アプリケーションを注意深く見て、64 ビットで正常に実行できるかどうかの判断を行うことが重要です。

移行とプラットフォーム呼び出し

.NET のプラットフォーム呼び出し (または p/invoke) 機能を使用すると、非マネージド コードまたはネイティブ コードを呼び出すマネージド コードを参照します。 一般的なシナリオでは、このネイティブ コードは、システム (Windows API など) の一部であるダイナミック リンク ライブラリ (DLL)、アプリケーションの一部、またはサード パーティ製ライブラリです。

非マネージド コードを使用することは、64 ビットへの移行に問題が発生することを明示的に意味するものではありません。むしろ、追加の調査が必要であることを示す指標と見なす必要があります。

Windowsのデータ型

すべてのアプリケーションとすべてのオペレーティング システムには、抽象データ モデルがあります。 多くのアプリケーションでは、このデータ モデルは明示的に公開されませんが、モデルはアプリケーションのコードの記述方法をガイドします。 32 ビット プログラミング モデル (ILP32 モデルと呼ばれます) では、整数、long、ポインターのデータ型の長さは 32 ビットです。 ほとんどの開発者は、このモデルを認識せずに使用しています。

64 ビットの Microsoft Windowsでは、データ型のサイズにおけるパリティの前提は無効です。 ほとんどのアプリケーションではサイズを増やす必要がないため、すべてのデータ型を 64 ビット長にすると領域が無駄になります。 ただし、アプリケーションには 64 ビット データへのポインターが必要であり、選択したケースでは 64 ビットデータ型を持つ機能が必要です。 これらの考慮事項により、Windows チームは LLP64 (または P64) という抽象データ モデルを選択しました。 LLP64 データ モデルでは、ポインターのみが 64 ビットに拡張されます。その他のすべての基本データ型 (整数と long) は、長さが 32 ビットのままです。

64 ビット プラットフォームの .NET CLR では、同じ LLP64 抽象データ モデルが使用されます。 .NET には、"ポインター" 情報を保持するように特に指定されている整数データ型があります。サイズがプラットフォームに依存する IntPtr (32 ビットや 64 ビットなど) が実行されています。 次のコード スニペットを考えてみます。

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

32 ビット プラットフォームで実行すると、コンソールに次の出力が表示されます。

SizeOf IntPtr is: 4

64 ビット プラットフォームでは、コンソールに次の出力が表示されます。

SizeOf IntPtr is: 8

メモ 実行時に 64 ビット環境で実行しているかどうかを確認する場合は、 IntPtr.Size を 1 つの方法として使用してこの決定を行うことができます。

移行に関する注意事項

p/invoke を使用するマネージド アプリケーションを移行する場合は、次の点を考慮してください。

  • DLL の 64 ビット バージョンの可用性
  • データ型の使用

可用性

最初に決定する必要がある事項の 1 つは、アプリケーションが依存関係を持つ非マネージド コードを 64 ビットで使用できるかどうかです。

このコードが社内で開発された場合は、成功する能力が向上します。 もちろん、非マネージド コードを 64 ビットに移植するリソースと、テストや品質保証などの適切なリソースを割り当てる必要があります (このホワイトペーパーでは開発プロセスに関する推奨事項を示していません。代わりに、リソースをポート コードにタスクに割り当てる必要がある可能性があることを指摘しようとしています)。

このコードがサード パーティからのコードである場合は、このサード パーティが既に 64 ビット版のコードを使用できるかどうか、およびサード パーティが使用できるかどうかを調査する必要があります。

リスクの高い問題は、サード パーティがこのコードのサポートを提供しなくなった場合、またはサード パーティが作業を行わない場合に発生します。 これらのケースでは、サードパーティがお客様自身でポートを実行するかどうかなど、同様の機能を実行する利用可能なライブラリに関する追加の調査が必要です。

依存コードの 64 ビット バージョンでは、追加の開発作業を意味する可能性のあるインターフェイスシグネチャが変更される可能性があり、アプリケーションの 32 ビット バージョンと 64 ビット バージョンの違いを解決することが重要です。

データ型

p/invoke を使用するには、.NET で開発されたコードで、マネージド コードが対象とするメソッドのプロトタイプを宣言する必要があります。 次の C 宣言を指定します。

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

プロトタイプ化されたメソッドの例を次に示します。

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

64 ビット移行の問題に目を向けて、これらの例を確認しましょう。

最初の例では、2 つの (2) 32 ビット整数を渡す メソッド DoWork を呼び出し、32 ビット整数が返されることを想定しています。 64 ビット プラットフォームで実行されている場合でも、整数は 32 ビットのままです。 この特定の例では、移行作業を妨げるものは何もありません。

2 番目の例では、64 ビットで正常に実行されるようにコードを変更する必要があります。 ここで行っていることは、 GetData メソッドを呼び出し、整数が返されることを期待していますが、関数が実際には int ポインターを返すことです。 ここで私たちの問題があります:整数は32ビットですが、64ビットポインタでは8バイトであることを覚えておいてください。 結局のところ、ポインターと整数が同じ長さ(4バイト)であると仮定して、32ビットの世界のかなりのコードが書き込まれています。 64 ビットの世界では、これはもはや当てはまりません。

この最後のケースでは、int の代わりに IntPtr を使用するようにメソッド宣言を変更することで問題を解決できます。

public unsafe static extern IntPtr GetData();

この変更は、32 ビット環境と 64 ビット環境の両方で機能します。 IntPtr はプラットフォーム固有であることを思い出してください。

マネージド アプリケーションで p/invoke を使用しても、64 ビット プラットフォームへの移行が不可能になるわけではありません。 また、問題が発生するという意味もありません。 つまり、マネージド アプリケーションが持つ非マネージド コードへの依存関係を確認し、問題が発生するかどうかを判断する必要があります。

移行と COM の相互運用性

COM 相互運用性は、.NET プラットフォームの想定される機能です。 プラットフォーム呼び出しに関する前の説明と同様に、COM 相互運用性を利用することは、マネージド コードが非マネージド コードを呼び出していることを意味します。 ただし、プラットフォーム呼び出しとは異なり、COM 相互運用性は、非マネージド コードが COM コンポーネントであるかのようにマネージド コードを呼び出す機能を持つことを意味します。

繰り返しますが、非マネージド COM コードを使用しても、64 ビットへの移行に問題があるわけではありません。むしろ、追加の調査が必要であることを示す指標と見なす必要があります。

移行に関する注意事項

.NET Framework バージョン 2.0 のリリースでは、アーキテクチャ間の相互運用性はサポートされていないことを理解しておくことが重要です。 簡潔にするために、同じプロセスで 32 ビットと 64 ビットの間の COM 相互運用性を利用することはできません。 ただし、アウトプロセス COM サーバーがある場合は、32 ビットと 64 ビットの間の COM 相互運用性を利用できます。 アウトプロセス COM サーバーを使用できない場合は、32 ビット COM オブジェクトと相互運用できるように、プログラムを WoW64 で実行するために、Win64 または非依存ではなく、マネージド アセンブリを Win32 としてマークする必要があります。

次に、マネージド コードが 64 ビット環境で COM 呼び出しを行う COM 相互運用性を利用するために必要なさまざまな考慮事項について説明します。 具体的には次のとおりです。

  • DLL の 64 ビット バージョンの可用性
  • データ型の使用
  • タイプ ライブラリ

可用性

依存コードの 64 ビット バージョンの可用性に関する p/invoke セクションの説明も、このセクションに関連しています。

データ型

依存コードの 64 ビット バージョンのデータ型に関する p/invoke セクションの説明も、このセクションに関連しています。

タイプ ライブラリ

アセンブリとは異なり、タイプ ライブラリを "neutral" としてマークすることはできません。Win32 または Win64 としてマークする必要があります。 さらに、COM が実行される環境ごとにタイプ ライブラリを登録する必要があります。 tlbimp.exeを使用して、タイプ ライブラリから 32 ビットまたは 64 ビットのアセンブリを生成します。

マネージド アプリケーションで COM 相互運用性を使用しても、64 ビット プラットフォームへの移行が不可能になるわけではありません。 また、問題が発生するという意味もありません。 つまり、マネージド アプリケーションの依存関係を確認し、問題が発生するかどうかを判断する必要があります。

移行と安全でないコード

C# のコア言語は、ポインターをデータ型として省略する点で、C および C++ とは特に異なります。 代わりに、C# には参照と、ガベージ コレクターによって管理されるオブジェクトを作成する機能が用意されています。 C# のコア言語では、初期化されていない変数、"ダングリング" ポインター、または配列の境界を超えてインデックスを作成する式を持つことは不可能です。 そのため、C および C++ プログラムを日常的に悩ましているバグのカテゴリ全体が排除されます。

C または C++ のすべてのポインター型コンストラクトには、C# で対応する参照型がありますが、ポインター型へのアクセスが必要になる状況があります。 たとえば、基になるオペレーティング システムとのインターフェイス、メモリ マップト デバイスへのアクセス、タイム クリティカルなアルゴリズムの実装は、ポインターにアクセスしないと不可能または実用的でない場合があります。 このニーズに対処するために、C# は安全でないコードを記述する機能を提供します。

安全でないコードでは、ポインターを宣言して操作したり、ポインターと整数型の間の変換を実行したり、変数のアドレスを受け取ったりすることができます。 ある意味では、安全でないコードを記述することは、C# プログラム内で C コードを記述するのとよく似ています。

安全でないコードは、実際には開発者とユーザーの両方の観点から見た "安全な" 機能です。 安全でないコード は修飾子 unsafe で明確にマークする必要があるため、開発者は安全でない機能を誤って使用することはできません。

移行に関する注意事項

安全でないコードに関する潜在的な問題について説明するために、次の例を見てみましょう。 マネージド コードでは、アンマネージ DLL を呼び出します。 特に、100 個の項目を返す GetDataBuffer というメソッドがあります (この例では、固定数の項目を返しています)。 これらの各項目は、整数とポインターで構成されます。 次のサンプル コードは、この返されたデータを処理する安全でない関数を示すマネージド コードからの抜粋です。

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

メモ この特定の例は、安全でないコードを使用せずに実現できました。 具体的には、使用された可能性のあるマーシャリングなどの他の手法があります。 しかし、この目的のために、安全でないコードを使用しています。

UnsafeFn は 100 個の項目をループ処理し、整数データを合計します。 データのバッファーを移動する場合、コードは整数とポインターの両方をステップオーバーする必要があります。 32 ビット環境では、このコードは正常に動作します。 ただし、前に説明したように、ポインターは 64 ビット環境では 8 バイトであるため、コード セグメント (以下に示す) は正しく機能しません。たとえば、ポインターを整数と同等として扱うなどの一般的なプログラミング手法を使用しているためです。

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

このコードを 32 ビット環境と 64 ビット環境の両方で動作させるには、コードを次のように変更する必要があります。

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

先ほど説明したように、安全でないコードを使用する必要がある場合があります。 ほとんどの場合、マネージド コードが他のインターフェイスに依存しているために必要です。 安全でないコードが存在する理由に関係なく、移行プロセスの一環として確認する必要があります。

上で使用した例は比較的単純で、プログラムを 64 ビットで動作させる修正は簡単でした。 安全でないコードの例が多く、より複雑であることは明らかです。 一部のユーザーは、詳細なレビューを必要とし、マネージド コードが使用しているアプローチをステップバックして再考する必要があります。

既に読んだことを繰り返すために、マネージド アプリケーションで安全でないコードを使用しても、64 ビット プラットフォームへの移行が不可能になるわけではありません。 また、問題が発生するという意味もありません。 つまり、マネージド アプリケーションに含まれる安全でないコードをすべて確認し、問題があるかどうかを判断する必要があります。

移行とマーシャリング

マーシャリングには、アンマネージ メモリの割り当て、アンマネージ メモリ ブロックのコピー、アンマネージ型へのマネージド変換、およびアンマネージ コードの操作時に使用されるその他のその他のメソッドのコレクションが用意されています。

マーシャリングは、.NET マーシャリング クラスを通じてマニフェストされます。 Visual Basicで静的または共有される、Marshal クラスで定義されたメソッドは、アンマネージ データを操作するために不可欠です。 マネージド プログラミング モデルとアンマネージド プログラミング モデルの間にブリッジを提供する必要があるカスタム マーシャラーを構築する高度な開発者は、通常、定義されているほとんどのメソッドを使用します。

移行に関する注意事項

マーシャリングは、アプリケーションの 64 ビットへの移行に関連するより複雑な課題の一部です。 開発者がマーシャリングを使用して実行しようとしていること、つまり、マネージド コードとアンマネージド コードとの間で構造化された情報を転送する性質を考えると、システムを支援するために情報 (低レベル) を提供していることがわかります。

レイアウトに関しては、開発者が行うことができる 2 つの特定の宣言があります。これらの宣言は、通常、コーディング属性を使用して行われます。

LayoutKind.Sequential

.NET Framework SDK ヘルプで提供されている定義を確認してみましょう。

"オブジェクトのメンバーは、アンマネージド メモリにエクスポートされるときに表示される順序で順番にレイアウトされます。 メンバーは、 StructLayoutAttribute.Pack で指定されたパッキングに従ってレイアウトされ、連続しない場合があります。

レイアウトは定義された順序に固有であると言われています。 次に、マネージド宣言とアンマネージド宣言が類似していることを確認するだけです。 しかし、梱包も重要な成分であると言われています。 この時点で、開発者による明示的な介入がなければ、既定のパック値があることを知っても驚くことはありません。 既に推測したように、既定のパック値は 32 ビット システムと 64 ビット システムでは同じではありません。

非連続メンバーに関する定義のステートメントは、既定のパック サイズがあるため、メモリにレイアウトされているデータがバイト 0、バイト 1、バイト 2 などではない可能性があるという事実を参照しています。代わりに、最初 のメンバーはバイト 0 になりますが、2 番目のメンバーはバイト 4 である可能性があります。 システムは、この既定のパッキングを実行して、マシンが不適切な問題に対処することなくメンバーにアクセスできるようにします。

パッキングに細心の注意を払う必要がある領域を次に示します。同時に、システムが優先モードで動作するようにします。

マネージド コードで定義されている構造体と、アンマネージ コードで定義されている対応する構造体の例を次に示します。 この例では、両方の環境でパック値を設定する方法を注意深くメモしておく必要があります。

[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

.NET FrameworkSDK ヘルプで提供されている定義を確認してみましょう。

"アンマネージ メモリ内のオブジェクトの各メンバーの正確な位置は、明示的に制御されます。 各メンバーは 、FieldOffsetAttribute を使用して、型内でそのフィールドの位置を示す必要があります。

ここでは、開発者が情報のマーシャリングに役立つ正確なオフセットを提供すると言われています。 そのため、開発者が FieldOffset 属性の情報を正しく指定することが不可欠です。

では、潜在的な問題はどこにありますか? フィールド オフセットは、続行するデータ メンバー サイズのサイズを知って定義されていることに注意してください。すべてのデータ型サイズが 32 ビットと 64 ビットの間で等しいわけではないことに注意してください。 具体的には、ポインターの長さは 4 バイトまたは 8 バイトです。

特定の環境を対象にマネージド ソース コードを更新する必要がある場合があります。 次の例は、ポインターを含む構造体を示しています。 ポインターを IntPtr にしても、64 ビットに移行する場合は依然として違いがあります。

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

64 ビットの場合、構造体の最後のデータ メンバーのフィールド オフセットは、実際には 8 ではなくオフセット 12 で始まるので調整する必要があります。

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

マーシャリングの使用は、マネージド コードとアンマネージド コードの複雑な相互運用性が必要な場合に現実です。 この強力な機能を利用することは、32 ビット アプリケーションを 64 ビット環境に移行できることを示すものではありません。 ただし、マーシャリングの使用に伴う複雑さのため、これは詳細に注意を払う必要がある領域です。

コードの分析では、各プラットフォームに個別のバイナリが必要かどうかと、パッキングなどの問題に対処するためにアンマネージド コードを変更する必要があるかどうかを示します。

移行とシリアル化

シリアル化とは、オブジェクトの状態を永続化または転送できる形式に変換するプロセスのことです。 シリアル化を補完するプロセスとして、ストリームをオブジェクトに変換する逆シリアル化があります。 これらのプロセスを組み合わせて使用することで、データを簡単に格納および転送できます。

.NET Framework には、次の 2 つのシリアル化技術が用意されています。

  • バイナリ シリアル化は、型そのものを正確に維持するため、アプリケーションを次回起動するまでの間、オブジェクトの状態を維持するのに役立ちます。 たとえば、クリップボードを出力先としてオブジェクトをシリアル化することによって、そのオブジェクトを異なるアプリケーション間で共有できます。 オブジェクトをシリアル化して、ストリーム、ディスク、メモリ、ネットワーク上などに出力できます。 .NET リモート処理では、シリアル化を使用して、あるコンピューターまたはアプリケーション ドメインから別のコンピューターまたはアプリケーション ドメインに "値別" のオブジェクトを渡します。
  • XML シリアル化では、パブリック プロパティとパブリック フィールドのみがシリアル化され、型そのものは維持されません。 これは、データを使用するアプリケーションに制限を加えずに、データを提供または処理する場合に有効です。 XML はオープン標準であるため、Web 経由でデータを共有する場合には有用な選択肢となります。 SOAP も同様のオープン標準であるため、有用な選択肢です。

移行に関する注意事項

シリアル化について考えるときは、達成しようとしていることを念頭に置く必要があります。 64 ビットに移行する際に留意すべき 1 つの質問は、異なるプラットフォーム間でシリアル化された情報を共有するかどうかです。 言い換えると、64 ビットマネージド アプリケーションは、32 ビットマネージド アプリケーションによって格納された情報を読み取る (または逆シリアル化する) ことになります。

答えは、ソリューションの複雑さを促進するのに役立ちます。

  • プラットフォームを考慮して独自のシリアル化ルーチンを記述することもできます。
  • 各プラットフォームで独自のデータの読み取りと書き込みを許可しながら、情報の共有を制限することもできます。
  • シリアル化しているものを見直し、いくつかの問題を回避するために変更を加える必要がある場合があります。

結局のところ、シリアル化に関する考慮事項は何ですか?

  • IntPtr は、プラットフォームに応じて 4 バイトまたは 8 バイトの長さです。 情報をシリアル化すると、プラットフォーム固有のデータが出力に書き込まれます。 これは、この情報を共有しようとすると問題が発生する可能性があることを意味します。

マーシャリングとオフセットに関する前のセクションの説明を検討すると、シリアル化がパッキング情報にどのように対処するかについて 1 つまたは 2 つの質問が表示される場合があります。 バイナリシリアル化の場合、.NET では、バイトベースの読み取りとデータの正しい処理を使用して、シリアル化ストリームへの正しいアライメントされていないアクセスが内部的に使用されます。

先ほど説明したように、シリアル化を使用しても、64 ビットへの移行が妨げることはありません。 XML シリアル化を使用する場合は、シリアル化プロセス中にネイティブマネージド型との間で変換する必要があり、プラットフォーム間の違いから分離されます。 バイナリシリアル化を使用すると、より豊富なソリューションが提供されますが、さまざまなプラットフォームがシリアル化された情報をどのように共有するかに関する決定を下す必要がある状況が生まれます。

まとめ

64 ビットへの移行が予定されており、Microsoft は 32 ビットマネージド アプリケーションから 64 ビットへの移行を可能な限り簡単に行えるように取り組んでいます。

ただし、64 ビット環境で 32 ビット コードを実行するだけで、移行する内容を確認せずに実行できる場合は、非現実的です。

前述のように、100% 型のセーフ マネージド コードがある場合は、実際には 64 ビット プラットフォームにコピーし、64 ビット CLR で正常に実行できます。

ただし、マネージド アプリケーションが次のいずれかまたはすべてのアプリケーションに関与する可能性は高くなります。

  • p/invoke を使用したプラットフォーム API の呼び出し
  • COM オブジェクトの呼び出し
  • 安全でないコードを使用する
  • マーシャリングを情報共有のメカニズムとして使用する
  • 状態を永続化する方法としてシリアル化を使用する

アプリケーションが実行しているこれらの処理に関係なく、宿題を行い、コードの実行内容と依存関係を調査することが重要です。 この宿題を行った後は、次のいずれかまたはすべてを行う選択を確認する必要があります。

  • 変更なしでコードを移行します。
  • コードを変更して、64 ビット ポインターを正しく処理します。
  • 他のベンダーなどと協力して、製品の 64 ビット バージョンを提供します。
  • マーシャリングやシリアル化を処理するようにロジックを変更します。

マネージド コードを 64 ビットに移行しないことを決定する場合があります。その場合、Windows ローダーが起動時に適切な処理を実行できるようにアセンブリをマークするオプションがあります。 ダウンストリームの依存関係は、アプリケーション全体に直接影響を与える点に注意してください。

Fxcop

また、移行に役立つツールにも注意する必要があります。

現在、Microsoft には FxCop というツールがあります。これは、.NET マネージド コード アセンブリが Microsoft .NET Framework 設計ガイドラインに準拠していることを確認するコード分析ツールです。 リフレクション、MSIL 解析、および呼び出しグラフ分析を使用して、名前付け規則、ライブラリの設計、ローカリゼーション、セキュリティ、パフォーマンスの 200 を超える欠陥がないかアセンブリを検査します。 FxCop には、独自のルールを作成するためのツールと SDK の GUI バージョンとコマンド ライン バージョンの両方が含まれています。 詳細については、 FxCop Web サイトを参照してください。 Microsoft は、移行作業に役立つ情報を提供する追加の FxCop ルールを開発中です。

また、実行時に実行している環境を特定するのに役立つ管理ライブラリ関数もあります。

  • System.IntPtr.Size - 32 ビット モードまたは 64 ビット モードで実行されているかどうかを判断します
  • System.Reflection.Module.GetPEKind - プログラムによって.exeまたは.dllにクエリを実行して、特定のプラットフォームまたは WOW64 の下でのみ実行することを意図しているかどうかを確認する

発生する可能性のあるすべての課題に対処するための特定の手順のセットはありません。 このホワイトペーパーは、これらの課題に対する認識を高め、可能な代替手段を提示することを目的としています。