ネイティブwin32 dllにピンボークする.Netアセンブリ用のNuGetパッケージを作成しようとしています。アセンブリとネイティブDLLの両方を、プロジェクト参照に追加されたアセンブリと共にパックする必要があり(この部分では問題ありません)、ネイティブdllをプロジェクト出力ディレクトリまたは他の相対ディレクトリにコピーする必要があります。
私の質問は:
ターゲットファイルで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
および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は取得します)。
自動的にコピーする必要があるマネージアセンブリと非マネージ共有ライブラリ(x86
サブディレクトリに配置する必要があります)の両方を含むEmguCV NuGetパッケージをビルドしようとしたときに、最近同じ問題が発生しました各ビルド後にビルド出力ディレクトリに。
NuGetとMSBuildのみに依存する、私が思いついたソリューションを次に示します。
マネージアセンブリをパッケージの/lib
ディレクトリ(明らかな部分)に配置し、非マネージド共有ライブラリと関連ファイル(例:.pdbパッケージ)を/build
サブディレクトリ( NuGet docs )で説明されています。
すべての非管理対象の名前を変更します*.dll
ファイルの末尾を別の名前に変更します。たとえば、*.dl_
は、NuGetが間違った場所に配置された疑いのあるアセンブリについてうめき声を発しないようにします(「問題:libフォルダー外のアセンブリ。」)。
カスタムの<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パッケージを参照する必要があります。
.targets
をプロジェクトにネイティブDLLを挿入に使用する代替手段を以下に示します。
Build action
= None
Copy to Output Directory
= Copy if newer
この手法の主な利点は、ネイティブDLLが依存プロジェクトのbin/
フォルダーに一時的にコピーされることです。
.nuspec
ファイルのレイアウトを参照してください。
.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/
エレメントに注目してください。
他の誰かがこれに出くわした場合。
.targets
ファイル名 しなければならない NuGetパッケージIDと等しい
それ以外は機能しません。
クレジットは以下に移動します: https://sushihangover.github.io/nuget-and-msbuild-targets/
ここに実際に記載されているように、もっと徹底的に読むべきでした。年齢を取って..
カスタム
<PackageName>.targets
を追加します
それは少し遅れていますが、私はそのための核となるパッケージを作成しました。
アイデアは、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=
純粋に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");
。必要な直前にライブラリーをディスクにアンパックします。もちろん、ディスクのその部分に対する書き込み権限があることを確認する必要があります。
これは古い質問ですが、私は今同じ問題を抱えており、少し複雑ですが非常にシンプルで効果的なターンアラウンドを見つけました:Nugetの標準コンテンツフォルダーに、構成ごとに1つのサブフォルダーを持つ次の構造を作成します。
/Content
/bin
/Debug
native libraries
/Release
native libraries
Nuspecファイルをパックすると、DebugフォルダーとReleaseフォルダー内のネイティブライブラリごとに次のメッセージが表示されます。
問題:libフォルダー外のアセンブリ。説明:アセンブリー 'Content\Bin\Debug\??????。dll'は 'lib'フォルダー内にないため、パッケージをプロジェクトにインストールするときに参照として追加されません。解決策:参照する必要がある場合は、「lib」フォルダーに移動します。
これは単なる目標であるため、このような「ソリューション」は必要ありません。ネイティブライブラリはNETアセンブリ参照として追加されません。
利点は次のとおりです。
欠点は次のとおりです。
正確な問題を解決することはできませんが、提案をすることができます。
重要な要件は、「参照を自動登録しないこと」です。
そのため、「ソリューションアイテム」に精通する必要があります。
こちらのリファレンスをご覧ください:
ソリューションレベルのアイテムを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"