API セット ローダー操作

大事な

このトピックの情報は、Windows 10 以降のすべてのバージョンに適用されます。 ここでは、これらのバージョンを "Windows" と呼び、必要に応じて例外を呼び出します。

API セット は、ライブラリ バインド プロセスにモジュール名前空間リダイレクトを導入するために、ライブラリ ローダーの OS サポートに依存します。 API セット コントラクト名では、ファイルに名前は付けられません。 ローダーは、そのコントラクト名から実装を含むホスト バイナリへの実行時リダイレクトを実行します。

ローダーは、実行時に API セットに対する依存関係を検出すると、イメージ内の構成データを参照して、その API セットのホスト バイナリを識別します。 この構成データは、API セット スキーマと呼ばれます。 スキーマは OS のプロパティとしてアセンブルされ、API セットとバイナリ間のマッピングは、特定のデバイスに含まれるバイナリによって異なる場合があります。 スキーマは、実装をホストするモジュールの名前が変更、分割、またはリファクタリングされた場合でも、1 つのバイナリ内のインポートされた関数を異なるデバイスで正しくルーティングできるようにするものです。

インポートが実装に到達する方法

バイナリは、インポート テーブル内の名前によって決定される 2 つの方法で API セットの実装に到達できます。

  • 直接 API セットのインポート。 バイナリは、API セット コントラクト名をインポートします。 ローダーは、API セット スキーマを使用してその名前を現在のデバイス上のホスト バイナリに解決します。
  • レガシ モジュールのインポート。 バイナリは、従来のWindowsモジュール名 (samplefeature.dllなど) インポートします。 そのモジュールに付属するエディションでは、ローダーはそれに直接バインドします。 置き換えられたエディションでは、同じ名前を持つ リバース フォワーダー によってインポートが API セットにリダイレクトされます。これにより、ローダーはスキーマを介して解決されます。

インポート テーブルに含まれる名前は、通常、書き込むソースではなく、リンク先のライブラリによって決まります。 アンブレラ ライブラリWindows参照してください。

現在のバージョンのWindowsを対象とするコードには、API セット コントラクト名を使用します。 ローダーによってホストに直接解決され、フォワーダーは間にありません。 API セットが存在する前にリリースされたWindowsバージョンでも実行される単一のバイナリが必要な場合は、レガシ モジュール名をインポートします。 逆転送では、レガシ モジュールが置き換えられたエディションでバイナリが動作し続けます。

直接 API セットのインポート

解決策は、次の 3 つの手順で構成されます。

  1. バイナリは、API セット コントラクト名をインポートするか、 または LoadLibrary に渡します。
  2. ローダーは、現在のデバイスの API セット スキーマでコントラクトを検索し、スキーマがマップするホスト バイナリを検索します。
  3. ローダーは、そのホスト バイナリを読み込み、インポートされた関数をホストのエクスポートにバインドします。

マッピングはファイル システムではなくスキーマに存在するため、同じインポートを異なるデバイス上の異なるバイナリに解決できます。

デバイス api-win-core-samplefeature マップ
機能を含むデバイス samplefeature.dll
リファクタリングされた実装を出荷するデバイス samplefeaturecore.dll
機能を含まないデバイス マッピングなし

ここで使用するsamplefeature名は、架空のWindows コンポーネントのわかりやすい名前です。

使用するバイナリは、どのホストにバインドされたかを認識されません。 これがメカニズムのポイントです。コントラクトは安定していますが、それを実装するモジュールは、あるデバイスから次のデバイスに自由に変更できます。

コントラクト名のインポートは 1 回の操作で解決され、中間フォワーダー モジュールは含まれなくなります。 これは最も効率的な形式であり、API セットに対して記述されたコードの通常のパスです。

API セット名と .dll サフィックス

マッピングはディスクではなくスキーマに保持されるため、 .dll で終わる API セット名は、その名前のファイルを参照しません。 .dll 部分は名前付け規則にすぎません。モジュール名のスペルをインポート テーブルで行う方法から引き継がれるだけです。 API セット名は、物理 DLL ファイルのエイリアスまたは仮想名に似ています。

ローダー操作は、 api- または ext-で始まる名前を受け取ると、それを API セット ランタイム (スキーマを介してコントラクトを解決するローダーの拡張機能) にルーティングします。 API セット ランタイムは、ファイル名ではなく API セットの名前付け規則によって名前を解析するため、 .dll サフィックスは解決されるコントラクト名の一部ではありません。 インポート テーブルに表示される名前から作業している場合は、サフィックスを含めます。それ以外の場合は、オフにすることができます。

ローダーは、同じスキーマを使用して、コントラクト名、バージョン管理されたコントラクト名、コントラクトエイリアスの両方の形式を解決します。 これらの名前を管理する規則については、 API セットのコントラクト名に関する説明を参照してください。

名前の安定性が可用性と同じではない

API セット名は、同じ名前が認識された場所で常に同じコントラクトを識別するという意味で、Windowsデバイス間で安定しています。 これは名前空間のプロパティで、特定のデバイスに関する保証ではありません。

特定のコントラクトは、デバイスに存在しないか、ホストにマップされていない可能性があります。 名前に関する何もあなたに何も教えていません。 実装が実際に存在するかどうかを確認するには、「 API セットの可用性の検出」を参照してください。

解決に必要なもの

API セットを介して呼び出しを実装に到達するには、次のすべてを保持する必要があります。

  • コントラクトは、現在のデバイスのスキーマに存在します。
  • スキーマはコントラクトをホスト バイナリにマップし、そのホストを読み込むことができます。
  • ホストは、バイナリが呼び出している特定の関数をエクスポートします。

これらのいずれかが保持されていない場合、障害が発生する場所は、API セットのインポート方法によって異なります。

スタイルのインポート コントラクトを解決できない場合の動作
静的インポート プロセスの開始に失敗します。 ローダーは、コードを実行する前に静的インポートを解決します。
遅延読み込みインポート プロセスは正常に開始されます。 解決は API の最初の呼び出しに遅延されます。この呼び出しでは、コードでエラーを処理できます。

不足しているエクスポートは、不足しているコントラクトとは別に報告されます。ホストがエクスポートしない関数をインポートするバイナリが、エントリ ポイントの不足エラーで失敗します。

正常に読み込んだ場合に通知されない内容

解決により、 コントラクト がホストにバインドされます。 そのコントラクト内の個々の機能の状態は評価されません。

コントラクトは、個別に使用可能な機能を 名前付きグループに整理できます。 コントラクトを保持するコントラクトが正常に解決されても、デバイスでグループを使用できない可能性があります。これは、ローダーがコントラクトの粒度でバインドされ、バインド時にグループの状態が参照されないためです。 これは意図的です。ホストのバインドを拒否することは静的インポートに致命的であるため、ローダーは許容されるパスを受け取り、より細かい質問を呼び出し元に残します。

コードの結果は、読み込みの成功、または LoadLibrary 呼び出しの成功が、特定の機能が使用可能であることを示す証拠ではないということです。 可用性クエリを使用して、その質問を明示的に行います。 API セットの可用性の検出を参照してください。

省略可能な API セットと遅延読み込み

アプリケーションが存在しない可能性がある API セットを呼び出す場合、可用性チェックだけでは不十分です。静的インポートではプロセスの開始に失敗するため、実行がチェックに達することはありません。

オプションのコード パスに到達可能な状態を維持するには、 読み込みを遅延させるために省略可能な API を含むモジュールを構成するか、可用性クエリが成功した後に LoadLibraryGetProcAddress を使用してターゲットを動的に解決します。 両方の方法の詳細については、 API セットの可用性の検出に関するページを参照してください。

逆転送

API セット名は、デバイス間のモジュールに安定した名前空間を提供しますが、すべてのバイナリをこのシステムに変換することは必ずしも実用的ではありません。 アプリケーションは長年一般的に使用されていた可能性があり、そのバイナリを再コンパイルすることは不可能な場合があります。 一部のアプリケーションは、特定の API セットが導入される前に構築されたシステムでも実行を続ける必要があります。

そのため、元のモジュールを含まないエディションには、一連のリバース フォワーダー (Windows PC で最初に導入されたモジュール名を含む互換性バイナリ、およびエクスポートを API セットにリダイレクトする互換性バイナリ) が含まれます。

完全なデスクトップ エディションには元のモジュールが付属しているため、従来のモジュール名のインポートは、常にモジュールにバインドされます。 そのモジュールを置き換えたエディションでは、同じ名前を持つリバース フォワーダーがギャップをカバーします。

ローダー操作は次のように動作します。

  1. ローダーには、デバイスに存在しないレガシ Windows PC モジュール名への依存関係が表示されます。
  2. ローダーは、そのモジュール名を持つリバース フォワーダーを見つけて読み込みます。
  3. リバース フォワーダーは、インポートされた関数を API セットにリダイレクトします。
  4. ローダーは、このトピックで前述したように、スキーマを介してその API セットを解決します。

概念的には、マッピングは次のようになります。

インポートされた DLL: samplefeature.dll

  • 元のモジュールが含まれるエディションの 場合:samplefeature.dll
  • 置き換えられたエディションの場合: リバース フォワーダー ->api-win-core-samplefeature ->samplefeaturecore.dllsamplefeature.dll

このパスの制限はエクスポート カバレッジです。 リバース フォワーダーは、API セットが同等のエクスポートのみを実行するため、元のモジュールが行ったすべての関数を必ずしもエクスポートするとは限りません。 リバース フォワーダーが実行しない関数をインポートするバイナリは、エントリ ポイントの不足エラーで読み込みに失敗します。

逆転送は、成功した解決を実装が存在する証拠として扱わない理由でもあります。 レガシ モジュール名に対する GetProcAddress 呼び出しは、エラーを返すスタブに解決される有効な関数ポインターを返すことができます。 代わりに、可用性を明示的に照会します。 API セットの可用性の検出を参照してください。

手記

逆転送では、Win32 API サーフェスのサブセットのみが対象となります。 デスクトップ バージョンのWindowsを対象とするアプリケーションは、すべてのWindows デバイスで実行できません。 バイナリが現在のバージョンのWindowsを対象とする場合は、API セット コントラクト名がより直接的な選択肢になります。

こちらも参照ください