将 NuGet 包中的本机文件添加到项目输出目录

我正在尝试为一个.Net 程序集创建 NuGet 包,该程序集确实对一个本机 win32dll 进行 pcall。 我需要将程序集和本机 dll 同时打包,并将程序集添加到项目引用中(这部分没有问题) ,本机 dll 应该复制到项目输出目录或其他相关目录中。

我的问题是:

  1. 如何打包本机 dll 而不用视觉工作室尝试将其添加到参考列表中?
  2. 复制本机 dll 需要编写 install.ps1吗?如果是这样,我如何访问包内容复制它?
69330 次浏览

我不能完全解决你的问题,但我可以给你一个建议。

您的关键要求是: “并有它不自动注册的参考”..。

所以你必须熟悉“解决方案项”

请参阅此处:

在 NuGet 包中添加解决方案级项

您必须编写一些 Powershell 伏都教才能将原生 dll 的副本放到它的主页中(同样,因为您不希望自动添加引用伏都教被触发)

这里是我写的 ps1文件... ... 把文件放在第三方参考文件夹。

这里有足够的内容让你知道如何将你的原生 dll 复制到某个“ home”... 而不必从头开始。

再说一次,这不是直接命中,但总比没有好。

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"

虽然有点晚了,但我已经为此创建了一个 Nuget 软件包。

这个想法是有一个额外的特殊文件夹在您的 Nuget 包。我相信你已经知道《自由与满足》了。我创建的 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

最近,当我尝试构建一个包含托管程序集和非托管共享库(也必须放在 x86子目录中)的 EmguCV NuGet 包时,我遇到了同样的问题,每次构建后,这个程序包必须自动复制到 build 输出目录。

下面是我想到的一个解决方案,它只依赖于 NuGet 和 MSBuild:

  1. 将托管程序集放在包的 /lib目录(显而易见的部分)和非托管共享库及相关文件(例如,。Pdb 包)在 /build子目录中(如 NuGet 文件中所述)。

  2. 将所有非托管的 *.dll文件结尾重命名为不同的名称,例如 *.dl_,以防止 NuGet 抱怨所谓的程序集放错了位置(“问题: lib 文件夹外的程序集。”)。

  3. /build子目录中添加一个定制的 <PackageName>.targets文件,其内容如下(参见下面的描述) :

<?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" />为该项目添加了一个新项目“ BuildAction”(在 VisualStudio 内的“ BuildAction”下拉列表中也可以看到该项目)。

  • <NativeBinary Include="...将放置在 /build/x86中的本机库添加到当前项目中,并使它们可以被自定义目标访问,该目标将这些文件复制到输出目录。

  • <TargetPath>x86</TargetPath>将自定义元数据添加到文件中,并告诉自定义目标将本机文件复制到实际输出目录的 x86子目录中。

  • <PrepareForRunDependsOn ...块将自定义目标添加到构建所依赖的目标列表中,有关详细信息,请参阅 微软,共同目标文件。

  • 自定义目标 CopyNativeBinaries包含两个复制任务。第一个负责将任何 *.dl_文件复制到输出目录,同时将其扩展名更改回原来的 *.dll。第二个只是将其余的文件(例如任何 *.pdb文件)复制到相同的位置。这可以由一个复制任务和一个 Install.ps1脚本来替代,这个脚本必须在包安装期间将所有 *.dl_文件重命名为 *.dll

但是,这个解决方案仍然不会将本机二进制文件复制到引用最初包含 NuGet 包的项目的输出目录。您仍然必须在您的“最终”项目中引用 NuGet 包。

使用目标文件中的 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目录中。目标文件将包括 build目录的所有子目录中的所有 dll文件。因此,要添加 Any CPU托管程序集使用的本机库的 x86x64版本,您最终将得到类似于下面的目录结构:

  • 建造
    • X86
      • NativeLib.dll
      • NativeLibDependency.dll
    • X64
      • NativeLib.dll
      • NativeLibDependency.dll
    • MyNugetPackageID.target
  • Lib
    • 净值40
      • ManagedAssembly.dll

生成时,将在项目的输出目录中创建相同的 x86x64目录。如果您不需要子目录,那么可以删除 **%(RecursiveDir),而直接在 build目录中包含所需的文件。还可以以同样的方式添加其他所需的内容文件。

在 VisualStudio 中打开时,目标文件中作为 None添加的文件不会显示在项目中。如果你想知道我为什么不在 nupkg 中使用 Content文件夹,那是因为没有办法设置 CopyToOutputDirectory元素 而不使用 Powershell 脚本(它只能在 Visual Studio 中运行,不能在命令提示符下运行,也不能在构建服务器或其他 IDE 中运行,而且是 在 project.json/xproj DNX 项目中不支持) ,我更喜欢对文件使用 Link,而不是在项目中使用额外的文件副本。

更新: 虽然这种方法也适用于 Content而不是 None,但是在 msbuild 中似乎存在一个 bug,因此文件不会被复制到引用项目中,而是被移除一个以上的步骤(例如 proj1-> proj2-> proj3,proj3不会从 proj1的 NuGet 包中获取文件,但是 proj2会)。

下面是使用 .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,就好像它是原始项目的一部分(但奇怪的是,该文件在 VisualStudio 中不可见)。

请注意在 bin/文件夹中设置目标文件名的 <Link>元素。

有一个纯粹的 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 标准内容文件夹中创建以下结构,每个配置一个子文件夹:

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

当您打包 nuspec 文件时,您将收到 Debug 和 Release 文件夹中每个本机库的以下消息:

问题: 程序集在 lib 文件夹之外。描述: 程序集 “ Content Bin Debug? ? ? ? ? . dll”不在“ lib”文件夹中,并且 因此在安装包时它不会被添加为引用 解决方案: 如果需要,将其移动到“ lib”文件夹中 被引用。

我们不需要这样的“解决方案”,因为这正是我们的目标: 本机库不会作为 NETAssembly 引用添加。

优点是:

  1. 简单的解决方案,没有繁琐的脚本与奇怪的效果,很难重置在软件包卸载。
  2. 在安装和卸载时,Nuget 像管理任何其他内容一样管理本机库。

缺点是:

  1. 每个配置都需要一个文件夹(但通常只有两个文件夹: Debug 和 Release,如果在每个配置文件夹中都必须安装其他内容,这两种方法都可以)
  2. 必须在每个配置文件夹中复制本机库(但是如果对于每个配置具有不同版本的本机库,则可以这样做)
  3. 每个文件夹中每个本机 dll 的警告(但是正如我所说的,它们是在打包时发给包创建者的,而不是在 VS 安装时发给包用户的)

把它放在内容文件夹里

如果您将文件标记为内容,则命令 nuget pack [projfile].csproj将自动为您执行此操作。

然后编辑这里提到的项目文件,添加 ItemGroup & NativeLibs & Nothing 元素

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

对我有用