我正在尝试为.Net程序集创建NuGet程序包,该程序集会对本机win32 dll执行pinvoke。 我需要打包程序集和本机dll,并将程序集添加到项目引用中(此部分没有问题),本机dll应该复制到项目输出目录或其他相关目录中。
我的问题是:
答案 0 :(得分:112)
使用目标文件中的Copy
目标复制所需的库,不会将这些文件复制到引用项目的其他项目,从而产生DllNotFoundException
。这可以使用更简单的目标文件来完成,但使用None
元素,因为MSBuild会将所有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
目录中包含所需的文件。其他所需的内容文件也可以以相同的方式添加。
在Visual Studio中打开时,在目标文件中添加为None
的文件不会显示在项目中。如果您想知道我为什么不在nupkg中使用Content
文件夹,因为无法设置CopyToOutputDirectory
元素without using a powershell script(它只能在Visual Studio中运行,而不是从命令提示符,构建服务器或其他IDE中运行,并且是not supported in project.json / xproj DNX projects)而我更喜欢对文件使用Link
而不是额外的项目中文件的副本。
<强>更新强>
虽然这也应该与Content
而不是None
一起使用,但似乎msbuild中存在一个错误,因此文件不会被复制到引用项目的多个步骤被删除(例如proj1 - &gt; proj2 - &gt; proj3,proj3无法从proj1的NuGet包中获取文件,但proj2将会这样做。
答案 1 :(得分:30)
当我尝试构建EmguCV NuGet包时,我最近遇到了同样的问题,包括托管程序集和非托管共享库(也必须放在 x86
子目录中)每次构建后都必须自动复制到构建输出目录。
这是我提出的解决方案,仅依赖于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" />
为项目添加了一个新项目“Build Action”(也可以在Visual Studio的“Build Action”下拉列表中找到)。
<NativeBinary Include="...
将放置在 /build/x86
中的本机库添加到当前项目中,并使其可供自定义目标访问,该目标将这些文件复制到输出目录。 / p>
<TargetPath>x86</TargetPath>
向文件添加自定义元数据,并告知自定义目标将本机文件复制到实际输出目录的 x86
子目录。
<PrepareForRunDependsOn ...
块将自定义目标添加到构建所依赖的目标列表中,有关详细信息,请参阅Microsoft.Common.targets文件。
自定义目标CopyNativeBinaries
包含两个复制任务。第一个负责将任何 *.dl_
文件复制到输出目录,同时将其扩展名更改回原来的 *.dll
。第二个只是将其余的(例如任何 *.pdb
文件)复制到同一位置。这可以由单个副本任务和install.ps1脚本替换,该脚本在程序包期间必须将所有 *.dl_
文件重命名为 *.dll
安装。
但是,此解决方案仍然不会将本机二进制文件复制到引用最初包含NuGet包的项目的另一个项目的输出目录中。您仍然需要在“最终”项目中引用NuGet包。
答案 2 :(得分:26)
以下是使用.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/
文件夹中设置目标文件名。
答案 3 :(得分:12)
有点晚了,但我为此创建了一个nuget包exaclty。
我们的想法是在您的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=0
答案 4 :(得分:12)
如果其他人偶然发现了这一点。
.targets
文件名必须等于NuGet包ID
其他任何事情都无法奏效。
积分转到: https://sushihangover.github.io/nuget-and-msbuild-targets/
我应该仔细阅读,因为这里实际上已经注意到了。花了我很多时间..
添加自定义
<PackageName>.targets
答案 5 :(得分:1)
有一个纯粹的C#解决方案,我觉得它很容易使用,我不必为NuGet的限制而烦恼。请按照以下步骤操作:
在项目中包含本机库,并将其Build Action属性设置为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");
,它将在您需要之前将库解压缩到磁盘。当然,您需要确保您对磁盘的该部分具有写入权限。
答案 6 :(得分:1)
这是一个老问题,但我现在遇到了同样的问题,我找到了一个有点棘手但又非常简单有效的转变:在Nuget标准Content文件夹中创建以下结构,每个配置都有一个子文件夹:
/Content
/bin
/Debug
native libraries
/Release
native libraries
打包nuspec文件时,您将在Debug和Release文件夹中收到每个本机库的以下消息:
问题:lib文件夹外的程序集。描述:装配 &#39;内容\ BIN \调试\ ?????? DLL&#39。不在&#39; lib&#39;文件夹和 因此,在安装软件包时,它不会被添加为参考 进入一个项目。解决方案:将其移入&#39; lib&#39;文件夹,如果它应该 被引用。
我们不需要这样的&#34;解决方案&#34;因为这只是我们的目标:本地库不作为.NET程序集引用添加。
优点是:
缺点是:
答案 7 :(得分:0)
我无法解决您的确切问题,但我可以给您一个建议。
您的关键要求是:“并且不要自动注册参考”.....
所以你必须熟悉“解决方案项目”
参见此处的参考资料:
Adding solution-level items in a NuGet package
你必须写一些powershell voodoo才能将你的原生dll的副本送到它的家里(再次,因为你不希望自动添加引用伏都教开火)
这是我写的一个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"
答案 8 :(得分:-2)
把它放在内容文件夹
如果您将文件标记为内容,命令nuget pack [projfile].csproj
将自动为您执行此操作。
然后编辑项目文件,如此处所述添加ItemGroup&amp; NativeLibs&amp;无元素
<ItemGroup>
<NativeLibs Include="$(MSBuildThisFileDirectory)**\*.dll" />
<None Include="@(NativeLibs)">
<Link>%(RecursiveDir)%(FileName)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
为我工作