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


126

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

私の質問は:

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

1
ランタイム/アーキテクチャ固有のライブラリのサポートはありますが、機能のドキュメントが不足しており、UWP固有のようです。docs.microsoft.com/en-us/nuget/create-packages/...
はWouter

回答:


131

Copyターゲットファイル内のターゲットを使用して必要なライブラリをコピーしても、それらのファイルはプロジェクトを参照する他のプロジェクトにコピーされず、結果としてになりDllNotFoundExceptionます。NoneMSBuildはすべての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>

build必要なネイティブライブラリと共に、ターゲットファイルをnugetパッケージのディレクトリに追加します。ターゲットファイルには、dllディレクトリのすべての子ディレクトリにあるすべてのファイルが含まれますbuild。だから、追加するx86x64で使用されるネイティブライブラリのバージョンAny CPUあなたは次のようなディレクトリ構造で終わるでしょうアセンブリの管理を:

  • 建てる
    • x86
      • NativeLib.dll
      • NativeLibDependency.dll
    • x64
      • NativeLib.dll
      • NativeLibDependency.dll
    • MyNugetPackageID.targets
  • lib
    • net40
      • ManagedAssembly.dll

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

Noneターゲットファイルとして追加されたファイルは、Visual Studioで開いたときにプロジェクトに表示されません。なぜContentnupkgでフォルダーを使用しないのか疑問に思っているのは、Powershellスクリプトを使用せずCopyToOutputDirectory要素を設定する方法がないためです(これはVisual Studio内でのみ実行され、コマンドプロンプトからではなく、ビルドサーバー上で実行されます)他のIDE、およびproject.json / xproj DNXプロジェクトではサポートされていません)。プロジェクト内にファイルの追加のコピーを作成するよりも、ファイルにa を使用することを好みます。Link

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


4
サー、あなたは天才です!魅力のように機能します。ありがとう。
MoonStom、2015年

なぜ条件が'$(MSBuildThisFileDirectory)' != '' And HasTrailingSlash('$(MSBuildThisFileDirectory)')必要なのでしょうか?MSBuildThisFileDirectoryいつも設定されていると思いました。それがそうでないときはいつですか?
kkm

@kkm正直。必要ないと思います。どこから取得したのか思い出せません。
kjbartel 2015年

@kkm私は最初にSystem.Data.SQLite nugetパッケージを変更しましたが、それらに含まれる他のすべてのがらくたをすべて削除したとき、私はそれを残したようです。元のターゲットファイル
kjbartel 2015年

2
@SuperJMNワイルドカードがあります。気づかなかったの**\*.dll?これ.dllは、すべてのディレクトリのすべてのファイルをコピーしています。**\*.*ディレクトリツリー全体を簡単にコピーできます。
kjbartel

30

管理されたアセンブリと管理されていない共有ライブラリの両方を含むEmguCV NuGetパッケージをビルドしようとしたときに、最近同じ問題が発生しました(これらもまた、 x86サブディレクトリ)の。

NuGetとMSBuildのみに依存する私が思いついたソリューションは次のとおりです。

  1. 管理されたアセンブリを/libパッケージのディレクトリ(明白な部分)に配置し、管理されていない共有ライブラリと関連ファイル(たとえば.pdbパッケージ)を/buildサブディレクトリに配置します(NuGetのドキュメントで説明されています)。

  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ファイル(ファイルなど)を同じ場所にコピーするだけです。これは、単一のコピータスクと、パッケージのインストール中にすべてのファイルの名前を変更する必要があるinstall.ps1スクリプトで置き換えることができます。*.dl_*.dll

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


4
" ただし、このソリューションでは、最初にNuGetパッケージが含まれているプロジェクトを参照する別のプロジェクトの出力ディレクトリにネイティブバイナリをコピーしません。"最終 "プロジェクトでもNuGetパッケージを参照する必要があります。 "これは、ストッパーを見せてください。これは通常、nugetパッケージを複数のプロジェクト(ユニットテストなど)に追加する必要があることを意味しますDllNotFoundException。そうしないと、スローされます。
kjbartel 2015年

2
警告のためにファイルなどの名前を変更するのは少し抜本的です。

<NoWarn>NU5100</NoWarn>プロジェクトファイルに追加することで警告を削除できます
Florian Koch

28

以下は、を使用して、次のプロパティを持つネイティブDLLをプロジェクト.targets挿入する代替方法です。

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

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

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

NuGetパッケージエクスプローラーのスクリーンキャプチャ

これが.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/ください。


Azureサービスの一部として含める必要があるいくつかの.batファイルと.ps1ファイルでご馳走を動かします-ありがとう:)
Zhaph-Ben Duguid

「(しかし不思議なことに、ファイルはVisual Studioに表示されません)。」—プロジェクトファイルはVS自体によって解析されるため、外部の.targetファイルに追加されたアイテム(またはターゲットの実行で動的に作成されたアイテム)は表示されません。
kkm

これは、からに変更する以外に、他の以前の回答とどのように異なりますか?ContentNone
kjbartel 2016

3
うわー、あなたは速いです。とにかく、そうすることを選択した場合、少なくとも「これが私の答えとどのように違うのか」と尋ねることができます。そのimoは、元の質問を編集して自分で答え、他の人のコメントであなたの答えを宣伝するよりも公平です。言うまでもなく、私はこの特定の回答があなたの回答よりも個人的に気に入っていることは言うまでもありません。簡潔で、要点があり、読みやすいです
Maksim Satsikau

3
@MaksimSatsikau歴史を見たいかもしれません。質問をより明確にするために編集し、質問に回答しました。この回答は数週間後に届き、事実上のコピーでした。失礼なことを見つけたら申し訳ありません。
kjbartel 2016年

19

他の誰かがこれに遭遇した場合。

.targetsファイル名のMUST NuGetパッケージ同上等しく

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

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

ここで実際に言及されているように、もっと徹底的に読むべきでした。私の年齢を取りました。

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


3
あなたは私の一日を救います!
zheng yu 2017

1
あなたは何か他のもので一週間の問題を修正しました。あなたとそのgithubページに感謝します。
グレンワトソン

13

少し遅いですが、そのためのnugetパッケージを作成しました。

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

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

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


すごい!ただし、これは現在のプロジェクトでのみ機能します。プロジェクトが「クラスライブラリ」であり、依存関係として「Webアプリケーション」などに追加したい場合、DLLはWebアプリケーションでビルドされません。私の「迅速な修正」は、ライブラリのNuGetを作成してクラスライブラリに適用し、依存関係(この場合はDLL)の別のNugetを作成してWebApplicationに適用します。これに対する最善の解決策はありますか?
ワーグナーレオナルディ2014年

このプロジェクトは.NET 4.0(Windows)専用に作成したようです。ポータブルクラスライブラリもサポートするように更新する予定はありますか?
Ani

1

かなり使いやすい純粋な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

これは古い質問ですが、今は同じ問題があり、少しトリッキーですが非常にシンプルで効果的なターンアラウンドが見つかりました。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インストール時のパッケージユーザーではなく、パック時にパッケージ作成者に発行されます)

0

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

あなたの重要な要件は次のとおりです:「参照を自動登録しない」.....

だから「ソリューションアイテム」に慣れる必要があります

ここの参照を参照してください:

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" 

-2

コンテンツフォルダです

nuget pack [projfile].csprojファイルをコンテンツとしてマークする場合、コマンドは自動的にそれを行います。

次に、プロジェクトグループファイルを編集して、ItemGroup&NativeLibs&None要素を追加します。

<ItemGroup>
    <NativeLibs Include="$(MSBuildThisFileDirectory)**\*.dll" />
    <None Include="@(NativeLibs)">
      <Link>%(RecursiveDir)%(FileName)%(Extension)</Link>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
</ItemGroup>

私のために働いた

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.