web-dev-qa-db-ja.com

NuGetパッケージからプロジェクト出力ディレクトリにネイティブファイルを追加する

ネイティブwin32 dllにピンボークする.Netアセンブリ用のNuGetパッケージを作成しようとしています。アセンブリとネイティブDLLの両方を、プロジェクト参照に追加されたアセンブリと共にパックする必要があり(この部分では問題ありません)、ネイティブdllをプロジェクト出力ディレクトリまたは他の相対ディレクトリにコピーする必要があります。

私の質問は:

  1. Visual Studioを参照リストに追加しようとせずにネイティブDLLをパックするにはどうすればよいですか?
  2. ネイティブdllをコピーするためにinstall.ps1を作成する必要がありますか?その場合、パッケージコンテンツをコピーするためにどのようにアクセスできますか?
110

ターゲットファイルでCopyターゲットを使用して必要なライブラリをコピーしても、それらのファイルはプロジェクトを参照する他のプロジェクトにコピーされず、結果としてDllNotFoundExceptionになります。ただし、MSBuildはすべてのNoneファイルを参照プロジェクトにコピーするため、None要素を使用して、はるかに単純なターゲットファイルでこれを行うことができます。

<Project xmlns="http://schemas.Microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <NativeLibs Include="$(MSBuildThisFileDirectory)**\*.dll" />
    <None Include="@(NativeLibs)">
      <Link>%(RecursiveDir)%(FileName)%(Extension)</Link>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>

ターゲットファイルを、必要なネイティブライブラリとともにnugetパッケージのbuildディレクトリに追加します。ターゲットファイルには、dllディレクトリのすべての子ディレクトリにあるすべてのbuildファイルが含まれます。したがって、x86マネージアセンブリで使用されるネイティブライブラリのx64およびAny CPUバージョンを追加するには、次のようなディレクトリ構造になります。

  • ビルド
    • x86
      • NativeLib.dll
      • NativeLibDependency.dll
    • x64
      • NativeLib.dll
      • NativeLibDependency.dll
    • MyNugetPackageID.targets
  • lib
    • net40
      • ManagedAssembly.dll

同じx86およびx64ディレクトリが、ビルド時にプロジェクトの出力ディレクトリに作成されます。サブディレクトリが必要ない場合は、**%(RecursiveDir)を削除し、代わりにbuildディレクトリに必要なファイルを直接含めることができます。他の必要なコンテンツファイルも同じ方法で追加できます。

ターゲットファイルにNoneとして追加されたファイルは、Visual Studioで開いたときにプロジェクトに表示されません。 nupkgでContentフォルダーを使用しない理由がわからない場合は、CopyToOutputDirectory要素を設定する方法がないためです powershellスクリプトを使用せずに (Visual Studio内でのみ実行され、コマンドプロンプト、ビルドサーバーまたは他のIDEで、 project.json/xproj DNXプロジェクトではサポートされていません )であり、ファイルのLinkを使用するのが好きですプロジェクト内のファイル。

更新:これはContentではなくNoneでも動作するはずですが、msbuildにはバグがあるため、参照元のプロジェクトにファイルがコピーされることはありません1ステップ削除(例:proj1-> proj2-> proj3、proj3はproj1のNuGetパッケージからファイルを取得しませんが、proj2は取得します)。

117
kjbartel

自動的にコピーする必要があるマネージアセンブリと非マネージ共有ライブラリ(x86サブディレクトリに配置する必要があります)の両方を含むEmguCV NuGetパッケージをビルドしようとしたときに、最近同じ問題が発生しました各ビルド後にビルド出力ディレクトリに。

NuGetとMSBuildのみに依存する、私が思いついたソリューションを次に示します。

  1. マネージアセンブリをパッケージの/libディレクトリ(明らかな部分)に配置し、非マネージド共有ライブラリと関連ファイル(例:.pdbパッケージ)を/buildサブディレクトリ( NuGet docs )で説明されています。

  2. すべての非管理対象の名前を変更します*.dllファイルの末尾を別の名前に変更します。たとえば、*.dl_は、NuGetが間違った場所に配置された疑いのあるアセンブリについてうめき声を発しないようにします(「問題:libフォルダー外のアセンブリ。」)。

  3. カスタムの<PackageName>.targetsファイルを/buildサブディレクトリに追加し、次の内容のようなものを追加します(説明については以下を参照してください)。

    <?xml version="1.0" encoding="utf-8"?>
    <Project ToolsVersion="4.0" xmlns="http://schemas.Microsoft.com/developer/msbuild/2003">
      <ItemGroup>
        <AvailableItemName Include="NativeBinary" />
      </ItemGroup>
      <ItemGroup>
        <NativeBinary Include="$(MSBuildThisFileDirectory)x86\*">
          <TargetPath>x86</TargetPath>
        </NativeBinary>
      </ItemGroup>
      <PropertyGroup>
        <PrepareForRunDependsOn>
          $(PrepareForRunDependsOn);
          CopyNativeBinaries
        </PrepareForRunDependsOn>
      </PropertyGroup>
      <Target Name="CopyNativeBinaries" DependsOnTargets="CopyFilesToOutputDirectory">
        <Copy SourceFiles="@(NativeBinary)"
              DestinationFiles="@(NativeBinary->'$(OutDir)\%(TargetPath)\%(Filename).dll')"
              Condition="'%(Extension)'=='.dl_'">
          <Output TaskParameter="DestinationFiles" ItemName="FileWrites" />
        </Copy>
        <Copy SourceFiles="@(NativeBinary)"
              DestinationFiles="@(NativeBinary->'$(OutDir)\%(TargetPath)\%(Filename).%(Extension)')"
              Condition="'%(Extension)'!='.dl_'">
          <Output TaskParameter="DestinationFiles" ItemName="FileWrites" />
        </Copy>
      </Target>
    </Project>
    

上記の.targetsファイルは、ターゲットプロジェクトファイルのNuGetパッケージのインストール時に挿入され、ネイティブライブラリを出力ディレクトリにコピーします。

  • <AvailableItemName Include="NativeBinary" />は、プロジェクトの新しい項目「ビルドアクション」を追加します(Visual Studio内の「ビルドアクション」ドロップダウンでも使用可能になります)。

  • <NativeBinary Include="...は、/build/x86に配置されたネイティブライブラリを現在のプロジェクトに追加し、それらのファイルを出力ディレクトリにコピーするカスタムターゲットにアクセスできるようにします。

  • <TargetPath>x86</TargetPath>は、カスタムメタデータをファイルに追加し、ネイティブファイルを実際の出力ディレクトリのx86サブディレクトリにコピーするようカスタムターゲットに指示します。

  • <PrepareForRunDependsOn ...ブロックは、ビルドが依存するターゲットのリストにカスタムターゲットを追加します。詳細については、 Microsoft.Common.targets ファイルを参照してください。

  • カスタムターゲットCopyNativeBinariesには、2つのコピータスクが含まれています。最初のものは、拡張子を元の*.dl_に戻しながら、*.dllファイルを出力ディレクトリにコピーします。 2番目のものは、残りを単純に(たとえば*.pdbファイル)同じ場所にコピーします。これは、単一のコピータスクと、パッケージのインストール中にすべての*.dl_ファイルの名前を*.dllに変更する必要がある install.ps1 スクリプトで置き換えることができます。

ただし、このソリューションでは、NuGetパッケージを最初に含むプロジェクトを参照している別のプロジェクトの出力ディレクトリにネイティブバイナリをコピーすることはできません。 「最終」プロジェクトでもNuGetパッケージを参照する必要があります。

30
buygrush

.targetsプロジェクトにネイティブDLLを挿入に使用する代替手段を以下に示します。

  • Build action = None
  • Copy to Output Directory = Copy if newer

この手法の主な利点は、ネイティブDLLが依存プロジェクトbin/フォルダーに一時的にコピーされることです。

.nuspecファイルのレイアウトを参照してください。

Screen capture of NuGet Package Explorer

.targetsファイルは次のとおりです。

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.Microsoft.com/developer/msbuild/2003">
    <ItemGroup>
        <None Include="$(MSBuildThisFileDirectory)\..\MyNativeLib.dll">
            <Link>MyNativeLib.dll</Link>
            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
        </None>
    </ItemGroup>
</Project>

これは、元のプロジェクトの一部であるかのようにMyNativeLib.dllを挿入します(ただし、奇妙なことに、ファイルはVisual Studioに表示されません)。

<Link>フォルダー内の宛先ファイル名を設定するbin/エレメントに注目してください。

26
Benoit Blanchon

他の誰かがこれに出くわした場合。

.targetsファイル名 しなければならない NuGetパッケージIDと等しい

それ以外は機能しません。

クレジットは以下に移動します: https://sushihangover.github.io/nuget-and-msbuild-targets/

ここに実際に記載されているように、もっと徹底的に読むべきでした。年齢を取って..

カスタム<PackageName>.targetsを追加します

15

それは少し遅れていますが、私はそのための核となるパッケージを作成しました。

アイデアは、nugetパッケージに特別なフォルダーを追加することです。 LibとContentを既に知っていると思います。私が作成したnugetパッケージは、Outputという名前のフォルダーを探し、そこにあるすべてのものをプロジェクトの出力フォルダーにコピーします。

あなたがしなければならない唯一のことは、パッケージにnuget依存関係を追加することです http://www.nuget.org/packages/Baseclass.Contrib.Nuget.Output/

それについてのブログ記事を書きました: http://www.baseclass.ch/blog/Lists/Beitraege/Post.aspx?ID=6&mobile=

12
Daniel Romero

純粋にC#のソリューションがあり、これはかなり使いやすく、NuGetの制限を気にする必要はありません。次の手順を実行します:

ネイティブライブラリをプロジェクトに含め、そのビルドアクションプロパティをEmbedded Resourceに設定します。

このライブラリをPInvokeするクラスに次のコードを貼り付けます。

private static void UnpackNativeLibrary(string libraryName)
{
    var Assembly = Assembly.GetExecutingAssembly();
    string resourceName = $"{Assembly.GetName().Name}.{libraryName}.dll";

    using (var stream = Assembly.GetManifestResourceStream(resourceName))
    using (var memoryStream = new MemoryStream(stream.CanSeek ? (int)stream.Length : 0))
    {
        stream.CopyTo(memoryStream);
        File.WriteAllBytes($"{libraryName}.dll", memoryStream.ToArray());
    }
}

次のような静的コンストラクターからこのメソッドを呼び出しますUnpackNativeLibrary("win32");。必要な直前にライブラリーをディスクにアンパックします。もちろん、ディスクのその部分に対する書き込み権限があることを確認する必要があります。

1
Ondrej Janacek

これは古い質問ですが、私は今同じ問題を抱えており、少し複雑ですが非常にシンプルで効果的なターンアラウンドを見つけました:Nugetの標準コンテンツフォルダーに、構成ごとに1つのサブフォルダーを持つ次の構造を作成します。

/Content
 /bin
   /Debug
      native libraries
   /Release
      native libraries

Nuspecファイルをパックすると、DebugフォルダーとReleaseフォルダー内のネイティブライブラリごとに次のメッセージが表示されます。

問題:libフォルダー外のアセンブリ。説明:アセンブリー 'Content\Bin\Debug\??????。dll'は 'lib'フォルダー内にないため、パッケージをプロジェクトにインストールするときに参照として追加されません。解決策:参照する必要がある場合は、「lib」フォルダーに移動します。

これは単なる目標であるため、このような「ソリューション」は必要ありません。ネイティブライブラリはNETアセンブリ参照として追加されません。

利点は次のとおりです。

  1. パッケージのアンインストール時にリセットするのが難しい、奇妙な効果を伴う面倒なスクリプトのないシンプルなソリューション。
  2. Nugetは、インストールおよびアンインストール時に、他のコンテンツと同様にネイティブライブラリを管理します。

欠点は次のとおりです。

  1. 構成ごとにフォルダーが必要です(ただし、通常、デバッグとリリースの2つしかありません。各構成フォルダーにインストールする必要がある他のコンテンツがある場合、これはどちらかの方法です)
  2. ネイティブライブラリは、各構成フォルダーで複製する必要があります(ただし、構成ごとに異なるバージョンのネイティブライブラリがある場合、これはどちらかの方法です)
  3. 各フォルダー内の各ネイティブdllの警告(ただし、私が言ったように、VSインストール時のパッケージユーザーではなく、パッケージ作成者に警告が発行されます)
1
SERWare

正確な問題を解決することはできませんが、提案をすることができます。

重要な要件は、「参照を自動登録しないこと」です。

そのため、「ソリューションアイテム」に精通する必要があります。

こちらのリファレンスをご覧ください:

ソリューションレベルのアイテムをNuGetパッケージに追加する

ネイティブdllのコピーをホームに取得するために、Powershellブードゥーを作成する必要があります(これも、自動追加参照ブードゥーを起動したくないためです)。

これは私が書いたps1ファイルです。サードパーティの参照フォルダーにファイルを配置します。

ゼロから始めなくても、ネイティブdllを「ホーム」にコピーする方法を理解するのに十分です。

繰り返しますが、それは直接的な打撃ではありませんが、何よりも優れています。

param($installPath, $toolsPath, $package, $project)
if ($project -eq $null) {
$project = Get-Project
}

Write-Host "Start Init.ps1" 

<#
The unique identifier for the package. This is the package name that is shown when packages are listed using the Package Manager Console. These are also used when installing a package using the Install-Package command within the Package Manager Console. Package IDs may not contain any spaces or characters that are invalid in an URL.
#>
$separator = " "
$packageNameNoVersion = $package -split $separator | select -First 1

Write-Host "installPath:" "${installPath}"
Write-Host "toolsPath:" "${toolsPath}"
Write-Host "package:" "${package}"
<# Write-Host "project:" "${project}" #>
Write-Host "packageNameNoVersion:" "${packageNameNoVersion}"
Write-Host " "

<# Recursively look for a .sln file starting with the installPath #>
$parentFolder = (get-item $installPath)
do {
        $parentFolderFullName = $parentFolder.FullName

        $latest = Get-ChildItem -Path $parentFolderFullName -File -Filter *.sln | Select-Object -First 1
        if ($latest -ne $null) {
            $latestName = $latest.name
            Write-Host "${latestName}"
        }

        if ($latest -eq $null) {
            $parentFolder = $parentFolder.parent    
        }
}
while ($parentFolder -ne $null -and $latest -eq $null)
<# End recursive search for .sln file #>


if ( $parentFolder -ne $null -and $latest -ne $null )
{
    <# Create a base directory to store Solution-Level items #>
    $thirdPartyReferencesDirectory = $parentFolder.FullName + "\ThirdPartyReferences"

    if ((Test-Path -path $thirdPartyReferencesDirectory))
    {
        Write-Host "--This path already exists: $thirdPartyReferencesDirectory-------------------"
    }
    else
    {
        Write-Host "--Creating: $thirdPartyReferencesDirectory-------------------"
        New-Item -ItemType directory -Path $thirdPartyReferencesDirectory
    }

    <# Create a sub directory for only this package.  This allows a clean remove and recopy. #>
    $thirdPartyReferencesPackageDirectory = $thirdPartyReferencesDirectory + "\${packageNameNoVersion}"

    if ((Test-Path -path $thirdPartyReferencesPackageDirectory))
    {
        Write-Host "--Removing: $thirdPartyReferencesPackageDirectory-------------------"
        Remove-Item $thirdPartyReferencesPackageDirectory -Force -Recurse
    }

    if ((Test-Path -path $thirdPartyReferencesPackageDirectory))
    {
    }
    else
    {
        Write-Host "--Creating: $thirdPartyReferencesPackageDirectory-------------------"
        New-Item -ItemType directory -Path $thirdPartyReferencesPackageDirectory
    }

    Write-Host "--Copying all files for package : $packageNameNoVersion-------------------"
    Copy-Item $installPath\*.* $thirdPartyReferencesPackageDirectory -recurse
}
else
{
        Write-Host "A current or parent folder with a .sln file could not be located."
}


Write-Host "End Init.ps1" 
0
granadaCoder