プラットフォーム呼び出し用のソース生成

.NET 7 では、C# コードの LibraryImportAttribute を認識する P/Invokes のソース ジェネレーターが導入されています。

ソース生成を使用しない場合、.NET ランタイムの組み込み相互運用システムは、実行時に IL スタブ (JIT 化された IL 命令のストリーム) を生成し、マネージドからアンマネージドへの移行を容易にします。 次のコードは、このメカニズムを使用する P/Invoke の定義と呼び出しを示しています。

[DllImport(
    "nativelib",
    EntryPoint = "to_lower",
    CharSet = CharSet.Unicode)]
internal static extern string ToLower(string str);

// string lower = ToLower("StringToConvert");

IL スタブは、アンマネージド コードの呼び出し方法に影響を与える DllImportAttribute での設定 (SetLastError など) を考慮して、パラメーターと戻り値のマーシャリングとアンマネージ コードの呼び出しを処理します。 この IL スタブは実行時に生成されるため、Ahead-Of-Time (AOT) コンパイラまたは IL トリミングのシナリオでは使用できません。 IL の生成は、マーシャリングに関して考慮すべき重要なコストです。 このコストは、アプリケーションのパフォーマンスと、動的コード生成を許可しない可能性のある潜在的なターゲット プラットフォームのサポートの観点から測定できます。 ネイティブ AOT アプリケーション モデルは、すべてのコードを事前に直接ネイティブ コードにプリコンパイルすることで、動的コード生成の問題に対処します。 完全なネイティブ AOT シナリオを必要とするプラットフォームでは、DllImport の使用はオプションではないため、他のアプローチ (ソース生成など) を使用する方が適切です。 DllImport シナリオでのマーシャリング ロジックのデバッグも簡単な作業ではありません。

.NET 7 SDK に含まれ、既定で有効になる P/Invoke ソース ジェネレーターにより、staticpartial のメソッドにおいて LibraryImportAttribute が検索されて、マーシャリング コードのコンパイル時ソース生成がトリガーされるので、実行時に IL スタブを生成する必要がなくなり、P/Invoke をインライン化できるようになります。 また、組み込みシステムからソース ジェネレーターへの移行や、一般的な使用に役立つよう、アナライザーとコード修正ツールも含まれています。

基本的な使用

LibraryImportAttribute は、DllImportAttribute と同じように使用できるように設計されています。 LibraryImportAttribute を使用し、メソッドを extern ではなく partial としてマークすることで、P/Invoke ソース生成を使用するように前の例を変換できます。

[LibraryImport(
    "nativelib",
    EntryPoint = "to_lower",
    StringMarshalling = StringMarshalling.Utf16)]
internal static partial string ToLower(string str);

コンパイル中にソース ジェネレーターによって、string パラメーターと戻り値の UTF-16 としてのマーシャリングを処理する ToLower メソッドの実装の生成がトリガーされます。 マーシャリングのソース コードが生成されたので、デバッガーでそのロジックを実際に確認してステップ実行できます。

MarshalAs

ソース ジェネレーターでは、MarshalAsAttribute も尊重されます。 前記のコードは、次のように記述することもできます。

[LibraryImport(
    "nativelib",
    EntryPoint = "to_lower")]
[return: MarshalAs(UnmanagedType.LPWStr)]
internal static partial string ToLower(
    [MarshalAs(UnmanagedType.LPWStr)] string str);

MarshalAsAttribute の一部の設定はサポートされていません。 サポートされていない設定を使おうとすると、ソース ジェネレーターでエラーが発生します。 詳しくは、「DllImport との違い」をご覧ください。

呼び出し規約

呼び出し規則を指定するには、UnmanagedCallConvAttribute を使います。次はその例です。

[LibraryImport(
    "nativelib",
    EntryPoint = "to_lower",
    StringMarshalling = StringMarshalling.Utf16)]
[UnmanagedCallConv(
    CallConvs = new[] { typeof(CallConvStdcall) })]
internal static partial string ToLower(string str);

DllImport との違い

LibraryImportAttribute の目的は、ほとんどの場合に DllImportAttribute をいっそう簡単にすることですが、意図的な変更がいくつかあります。

  • CallingConvention には、LibraryImportAttribute に相当するものはありません。 代わりに UnmanagedCallConvAttribute を使用します。
  • CharSet (CharSet の場合) は、StringMarshalling (StringMarshalling の場合) に置き換えられています。 ANSI は削除され、UTF-8 が優れたオプションとして使用できるようになっています。
  • BestFitMappingThrowOnUnmappableChar に相当するものはありません。 これらのフィールドは、Windows で ANSI 文字列をマーシャリングする場合にのみ関連していました。 ANSI 文字列のマーシャリング用に生成されるコードは、BestFitMapping=false および ThrowOnUnmappableChar=false と同じように動作します。
  • ExactSpelling に相当するものはありません。 これは Windows 中心の設定であり、Windows オペレーティング システム以外では効果はありませんでした。 メソッド名または EntryPoint では、エントリ ポイント名の正確なスペルを指定する必要があります。 このフィールドは、Win32 のプログラミングで使用される A サフィックスにW関連して過去に使用されてきました。
  • PreserveSig に相当するものはありません。 このフィールドは Windows 中心の設定でした。 生成されるコードでは、シグネチャは常に直接変換されます。
  • AllowUnsafeBlocks を使って、プロジェクトをアンセーフとマークする必要があります。

また、MarshalAsAttribute での一部の設定、特定の型の既定のマーシャリング、その他の相互運用関連の属性についても、サポートに違いがあります。 詳しくは、互換性の違いに関するドキュメントをご覧ください。

関連項目