次の方法で共有


.NET パッケージにネイティブ ライブラリを含める

このドキュメントでは、.NET (5 以降) を対象とするプロジェクトで動作するパッケージを作成する方法について説明します。.NET マネージド アセンブリでは .NET マネージド言語プロジェクトのプラットフォーム呼び出し (P/Invoke) が使用され、.NET ランタイムがプローブ パスを使用してネイティブ ライブラリを読み込んで検索します。

パッケージで .NET Framework をサポートする場合は、.NET Framework を対象とするプロジェクトに関するセクションを参照してください。 一般に、最適なオプションは、独自の MSBuild プロパティとターゲット ファイルをパッケージ化し、ビルド ターゲットと発行ターゲットを自分で処理することです。 NuGet と .NET ランタイムの組み込み機能を使用しないため、MSBuild と関連するプロジェクト タイプのビルド システムについて十分な知識を有している必要があります。

NuGet には contentFiles/ 機能がありますが、これを使用することはお勧めしません。RID ごとの選択がサポートされていないので、.NET ランタイムのプローブ パス機能に参加しないためです。

背景

まず、パッケージを参照するプロジェクトを検討します。 アプリが特定のランタイム識別子 (RID) に対して発行されると、その RID の NuGet パッケージ アセットが出力ディレクトリにコピーされます。 アプリが特定の RID なしで発行されると、RID 固有のすべての内容がサブディレクトリに発行され、{project}.deps.json ファイルが作成されます。このファイルが .NET ランタイムに読み込まれ、プローブ パスとして使用するディレクトリが決定されます。

また、.NET 8 では、.deps.json ファイルから互換性のある RID を決定する方法と、SDK によって認識される RID に関して破壊的変更が導入されていることに注意してください。

NuGet パッケージ アセットの選択について

NuGet ファイルには、このシナリオに最も関連する 3 タイプのアセット (コンパイル アセット、ランタイム アセット、ネイティブ アセット) があります。 アセット タイプの完全な一覧については、PackageReference プロジェクトの依存関係アセットの制御に関するドキュメントを参照してください。

資産タイプ 簡単な説明
コンパイル コンパイラに渡されるマネージド アセンブリ。 存在する場合は ref/{tfm}/、それ以外の場合は lib/{tfm}/
runtime 出力ディレクトリにコピーされるマネージド アセンブリ。 存在する場合は runtimes/{rid}/lib/{tfm}/、それ以外の場合は lib/{tfm}/
native 出力ディレクトリにコピーされるネイティブ ライブラリ。 runtimes/{rid}/native/

いずれの場合も、NuGet は特定の RID とターゲット フレームワークに対して "最も互換性のある" ディレクトリを選択し、そのディレクトリ内のファイルのみがコピーされます。 そのため、継承された RID (たとえば any) に共通アセットを配置することはできません。他の RID がより一致する場合、any RID の内容が考慮されなくなるためです。

NuGet のアセット選択の実装を調べるには、NuGet.ClientManagedCodeConventions.cs ファイルを参照してください。

コンパイル アセット

コンパイル アセットはコンパイラに渡されるため、.NET アセンブリのみを含める必要があります。 NuGet では ref/{tfm}/ からコンパイル アセットが選択され、そのディレクトリが存在しない場合は lib/{tfm} にフォールバックされます。{tfm} は、パッケージを参照するプロジェクトに最も一致するターゲット フレームワークです。 ただし、ネイティブ ライブラリを含む .NET パッケージを作成するこのシナリオでは、ref/ を使用することをお勧めします。lib/ を使用すると、NuGet ではパッケージは packages.config を使用しているプロジェクトと互換性があると見なされますが、ネイティブ ファイルがコピーされないため、これらのプロジェクトは実行時に失敗します。

アセンブリが現在のコンパイルのターゲットとは異なるアーキテクチャをターゲットにしている場合、.NET コンパイラは警告を生成します。そのため、パッケージ コンシューマーは、ref/ または lib/ アセンブリが AnyCPU をターゲットとする場合に最適なエクスペリエンスを得られます。

P/Invoke メソッド定義だけでなく多くのマネージド コードがアセンブリに含まれている場合は、ref/ アセンブリを参照アセンブリにすることで領域を節約できますが、その際はパッケージの lib/ ではなく ref/ に含めるようにしてください。

したがって、異なる RID に異なるコンパイル時アセンブリを提供する機能はありません。すべての RID に同じ API サーフェスを提供し、ランタイム チェック (PlatformNotSupportedException のスローなど) を実行する必要があります。

ランタイム アセット

ランタイム アセットは、ビルド時と発行時に出力ディレクトリにコピーされた .NET マネージド アセンブリであり、.NET SDK によって RID 固有のすべてのアセット情報を含む deps.json が生成されます。 NuGet では runtimes/{rid}/lib/{tfm}/ からランタイム アセットが選択され、存在しない場合は lib/{tfm}/ にフォールバックされます。 最初に最適な {rid} ディレクトリが選択され、次に {tfm} が選択されます。 ただし、ネイティブ ライブラリを含む .NET パッケージを作成するこのシナリオでは、runtimes/ を使用することをお勧めします。lib/ を使用すると、NuGet ではパッケージは packages.config を使用しているプロジェクトと互換性があると見なされますが、ネイティブ アセットがコピーされないため、これらのプロジェクトは実行時に失敗します。

ネイティブ アセット

ネイティブ アセットもビルド時と発行時に出力ディレクトリにコピーされ、deps.json ファイルに存在します。 NuGet では、runtimes/{rid}/native/ ディレクトリからネイティブ アセットが選択されます。

Note

出力ディレクトリにコピーするとき、.NET SDK では runtimes/{rid}/native/ 下のディレクトリ構造がフラット化されます。

組み合わせる

バイナリ ネイティブ ライブラリをビルドする方法は、このドキュメントの範囲外です。 すべてのネイティブ バイナリと .NET アセンブリが完成し、パックするためにマシンにコピーされていることを前提としています。

NuGet の MSBuild pack ターゲットを使用してパッキングする場合、MSBuild 項目の Pack="true" PackagePath="{path}" メタデータを使用して、任意のファイルを任意のパッケージ パスに含めることができます。 たとえば、<None Include="../cross-compile/linux-x64/libcontoso.so" Pack="true" PackagePath="runtimes/linux-x64/native/" /> のようにします。

[DllImport("contoso")] を使用して、Windows の contoso.dll、OSX の libcontoso.dylib、Linux の libcontoso.so に .NET API を提供するパッケージ Contoso.Native を検討します。 パッケージの内容は以下にする必要があります (Open Packaging Conventions で必要なファイルを除く)。

例 1

Contoso.Native が AnyCPU としてビルドされている場合:

Contoso.Native.1.0.0.nuspec
ref/net8.0/Contoso.Native.dll
runtimes/any/lib/net8.0/Contoso.Native.dll
runtimes/linux-arm64/native/libcontoso.so
runtimes/linux-x64/native/libcontoso.so
runtimes/osx-arm64/native/libcontoso.dylib
runtimes/osx-x64/native/libcontoso.dylib
runtimes/win-arm64/native/contoso.dll
runtimes/win-x64/native/contoso.dll

Contoso.Native.dllref/ コピーと runtimes/any/lib コピーは 1 つの lib/net8.0/Contoso.Native.dll ファイルに重複除去できますが、これはお勧めしません。NuGet では、このパッケージは packages.config を使用しているプロジェクトと互換性があると見なされるため、ネイティブ contoso.dll が見つからない場合、これらのプロジェクトは実行時に失敗します。

例 2

アーキテクチャ間で Contoso.Native に違いがある場合の例です。 たとえば、#if を使用して異なる RID に対して異なるコードをコンパイルする場合や、AnyCPU 以外の値で <PlatformTarget> (または -platform: がコンパイラに渡されることになるその他のもの) を使用する場合などがありますが、これらに限定されません。

Contoso.Native.1.0.0.nuspec
ref/net8.0/Contoso.Native.dll
runtimes/linux-arm64/lib/net8.0/Contoso.Native.dll
runtimes/linux-arm64/native/libcontoso.so
runtimes/linux-x64/lib/net8.0/Contoso.Native.dll
runtimes/linux-x64/native/libcontoso.so
runtimes/osx-arm64/lib/net8.0/Contoso.Native.dll
runtimes/osx-arm64/native/libcontoso.dylib
runtimes/osx-x64/lib/net8.0/Contoso.Native.dll
runtimes/osx-x64/native/libcontoso.dylib
runtimes/win-arm64/lib/net8.0/Contoso.Native.dll
runtimes/win-arm64/native/contoso.dll
runtimes/win-x64/lib/net8.0/Contoso.Native.dll
runtimes/win-x64/native/contoso.dll

例 3

Contoso.Native に、CPU アーキテクチャごとではなくオペレーティング システムごとの条件付きコンパイルが含まれる場合の例です。

Contoso.Native.1.0.0.nuspec
ref/net8.0/Contoso.Native.dll
runtimes/linux/lib/net8.0/Contoso.Native.dll
runtimes/linux-arm64/native/libcontoso.so
runtimes/linux-x64/native/libcontoso.so
runtimes/osx/lib/net8.0/Contoso.Native.dll
runtimes/osx-arm64/native/libcontoso.dylib
runtimes/osx-x64/native/libcontoso.dylib
runtimes/win/lib/net8.0/Contoso.Native.dll
runtimes/win-arm64/native/contoso.dll
runtimes/win-x64/native/contoso.dll

.NET Framework を対象とするプロジェクト

.NET は時間をかけて進化しており、特定の機能を使用するとプロジェクトとパッケージの互換性がなくなることが起きます。 前述の内容は、パッケージを使用するプロジェクトが、.NET 5 以降、またはパッケージの内容を可能な限り簡略化するために最大限の機能を使用する .NET Core (netcoreapp) を対象としていることを前提としています。 特に、.NET Standard を対象とするプロジェクトはこのドキュメントに関係がありません。これらのプロジェクトはクラス ライブラリのみであり、直接実行することはできません。 ただし、.NET Framework を対象とするプロジェクトでは、SDK スタイルまたは SDK 以外のスタイルのプロジェクトを使用できます。SDK 以外のスタイルのプロジェクトでは、PackageReference または packages.config を使用して NuGet パッケージを使用できます。 これらの組み合わせによってサポートはすべて異なるレベルとなり、パッケージを使用して .NET Framework プロジェクトをサポートするパッケージ作成者は制約を受けます。

お勧めの方法は、パッケージを 3 つのパッケージに分割することです。 1 つ目はメタパッケージ (内容やアセットを含まない、他のパッケージへの依存関係のみのパッケージ) で、ターゲット フレームワークに応じて他の 2 つのパッケージのいずれかに依存関係を持ちます。 2 つ目のパッケージには、前述のように、.NET 5 以降の ref/* アセットと runtimes/* アセットが含まれます。 最後のパッケージには .NET Framework アセットが含まれ、ref/* または runtime/* の規則は使用されません。 これは、ビルド システムはこれらの NuGet 規則を (部分的に) サポートしているのにランタイムの動作が異なることが原因であり、プロジェクトの互換性を最大限に高めるためには、これを回避する方が簡単です。

.NET Framework ネイティブ ライブラリを含むパッケージは、buildTransitive/<tfm>/<package ID>.[props|targets] 下にカスタムの MSBuild ターゲットおよびプロパティ ファイルが含まれている必要があります。 たとえば、buildTransitive/net472/Contoso.Native.targets のようにします。 packages.config プロジェクトをサポートするには、buildTransitive ファイルをインポートする build/<tfm>/<package ID>.[props|targets] も用意します。 この MSBuild スクリプトは、ネイティブ ライブラリをサブディレクトリにコピーする必要があります。 あるいは、ネイティブ ライブラリのファイル名は CPU アーキテクチャごとに異なる必要があるため、ネイティブ ライブラリをすべて同じディレクトリにコピーすることもできます。

最後に、P/Invoke を使用してネイティブ ライブラリを呼び出すマネージド ライブラリには、現在の実行可能ファイルが実行されているプラットフォーム (x86、x64、または ARM64) を決定するためのランタイム チェックが必要です。その後、正しいファイル名または適切なサブディレクトリでネイティブ ライブラリの P/Invoke を行います。

.NET Framework を対象とする SDK スタイル プロジェクト

.NET SDK が .NET Framework を対象とするプロジェクトをビルドすると、RuntimeIdentifier または PlatformTarget のいずれかが設定されている場合、.NET SDK は他のプロパティを適切な値に設定し、(NuGet の規則に従う) パッケージ runtimes/<rid> の内容が出力ディレクトリにコピーされます。 プロジェクトに RuntimeIdentifierPlatformTarget のいずれも設定されていないが、パッケージに RID 固有の内容が含まれている場合、.NET SDK は PlatformTargetx86 に設定します。 したがって、.NET Framework を対象とする SDK スタイル プロジェクトは、RID 固有の内容を含むパッケージがない場合にのみ、既定で AnyCPU を使用します。

dotnet build -r <rid> または publish の同等のもの (たとえば dotnet publish -r win-arm64) を使用すると、特定のプラットフォーム用に明示的にビルドしたり発行したりすることができます。

AnyCPU プロジェクトをサポートするには、パッケージ作成者はパッケージの runtimes/ の規則を使用してはなりません。前述のように MSBuild ターゲット/プロパティ ファイルを使用する必要があります。

PackageReference を使用する .NET Framework を対象とする SDK 以外のスタイルのプロジェクト

runtimes/* の規則を使用するパッケージで SDK 以外のスタイルの PackageReference プロジェクトをテストすると、次の動作が発生します。 プロジェクトの構成が "Any CPU" プラットフォームを対象とする場合、ビルドはパッケージから RID 固有のアセットをコピーせず、実行時にアプリが失敗します。 Visual Studio の構成マネージャーを使用すると、.NET プロジェクトを "Any CPU" ではなく、x64、x86、または ARM64 を対象とするように変更できます。この場合、アプリは実行時に正常に動作します。

AnyCPU プロジェクトをサポートするには、パッケージ作成者は、前述のように MSBuild ターゲット/プロパティ ファイルを使用する必要があります。

packages.config

NuGet の元の復元スタイル packages.config は、ref/* または runtimes/* の規則をサポートしていません。 そのため、パッケージ作成者は、まだ packages.config を使用しているプロジェクトをサポートするためには、独自の MSBuild プロパティおよびターゲット ファイルを作成する必要があります。