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 リリース以降、組織は をデプロイすることに成功しました。NET ベースのソリューションは、社内、独立系ソフトウェア ベンダー (ISV)、またはいくつかの組み合わせによって組み込まれています。 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++ コードは、型セーフ コードを生成します。

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

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

マネージド コードを 64 ビット環境に移行する際の複雑さを理解するために、32 ビット環境でのマネージド コードの実行方法を確認しましょう。

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

相互運用性

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

64 ビットへの移行を検討する場合は、アプリケーションの依存関係を調べる必要があります。

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

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

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

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

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

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

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

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

単純な 64 ビット移行

100% 型の安全なコードである .NET アプリケーションを考えてみましょう。 このシナリオでは、32 ビット コンピューターで実行する .NET 実行可能ファイルを取得し、64 ビット システムに移動して正常に実行することができます。 これが機能する理由 アセンブリは 100% の型セーフであるため、ネイティブ コードまたは COM オブジェクトへの依存関係がなく、"安全でない" コードがないことを知っています。これは、アプリケーションが CLR の制御下で完全に実行されることを意味します。 CLR は、Just-In-Time (JIT) コンパイルの結果として生成されるバイナリ コードが 32 ビットと 64 ビットの間で異なるように保証しますが、実行されるコードは両方とも意味的に同じになります。 (.NET Framework バージョン 2.0 を Windows 2000 にインストールすることはできません。.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) 機能を使用することは、非マネージド コードまたはネイティブ コードの呼び出しを行っているマネージド コードを指します。 一般的なシナリオでは、このネイティブ コードはダイナミック リンク ライブラリ (DLL) であり、システム (Windows API など) の一部、アプリケーションの一部、またはサード パーティ製ライブラリのいずれかです。

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

Windows のデータ型

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

64 ビットの Microsoft Windows では、データ型のサイズでのパリティの前提は無効です。 ほとんどのアプリケーションではサイズを増やす必要がないため、すべてのデータ型を 64 ビット長にする場合、領域が無駄になります。 ただし、アプリケーションには 64 ビット データへのポインターが必要であり、選択したケースでは 64 ビットデータ型を持つ機能が必要です。 これらの考慮事項により、Windows チームは LLP64 (または P64) という抽象データ モデルを選択しました。 LLP64 データ モデルでは、ポインターのみが 64 ビットに拡張されます。他のすべての基本データ型 (整数と長) は、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 ビット環境で実行しているかどうかに関係なく実行時にチェックする場合は、この決定を行う 1 つの方法として IntPtr.Size を使用できます。

移行に関する注意事項

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

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

可用性

最初に決定する必要がある事項の 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バイトであることを覚えておいてください。 結局のところ、32 ビットの世界では、ポインターと整数が同じ長さ 4 バイトであると仮定して、かなりのコードが書き込まれています。 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 または Agnostic ではなく Win32 としてマークする必要があります。

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

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

可用性

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 Marshal クラスを通じてマニフェストされます。 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 には、独自のルールを作成するためのツールの GUI とコマンド ライン バージョンと SDK の両方が含まれています。 詳細については、 FxCop Web サイトを参照してください。 Microsoft は、移行作業に役立つ情報を提供する追加の FxCop ルールを開発中です。

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

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

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