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


126

我正在尝试为确实拼凑到本地win32 dll的.Net程序集创建NuGet程序包。我需要将程序集和本机dll打包在一起,并将程序集添加到项目引用中(此部分没有问题),本机dll应复制到项目输出目录或其他相对目录中。

我的问题是:

  1. 如何在不使用Visual Studio尝试将其添加到引用列表的情况下打包本机dll?
  2. 我必须编写一个install.ps1来复制本机dll吗?如果是这样,我如何访问包装内容以进行复制?

1
它支持运行时/体系结构特定的库,但是缺少功能文档,而且似乎是UWP特定的。docs.microsoft.com/en-us/nuget/create-packages/…–
Wouter

Answers:


131

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>

将目标文件build以及所需的本机库添加到nuget包的目录中。目标文件将包括dll目录的所有子目录中的所有文件build。因此,要添加托管程序集使用的本机库的x86x64版本,Any CPU您将得到一个类似于以下内容的目录结构:

  • 建立
    • x86
      • NativeLib.dll
      • NativeLibDependency.dll
    • x64
      • NativeLib.dll
      • NativeLibDependency.dll
    • MyNugetPackageID.targets
  • LIB
    • 净40
      • ManagedAssembly.dll

同样x86x64目录将在项目的输出目录生成时创建。如果你不需要子目录,则**%(RecursiveDir)可以被删除,而不是包括在所要求的文件build直接目录。其他所需的内容文件也可以以相同的方式添加。

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

更新: 尽管这应该也可以解决,Content而不是None看起来msbuild中存在错误,所以文件不会被复制到引用项目中,而已删除一个步骤(例如proj1-> proj2-> proj3,proj3不会获取文件)从proj1的NuGet包中获取,但是proj2可以)。


4
先生,你真是个天才!奇迹般有效。谢谢。
MoonStom 2015年

想知道为什么'$(MSBuildThisFileDirectory)' != '' And HasTrailingSlash('$(MSBuildThisFileDirectory)')需要这种条件?我认为MSBuildThisFileDirectory总是这样。什么时候不是这样?
kkm

@kkm老实说。我认为这不是必需的。我什至不记得我最初从哪里得到的。
kjbartel

@kkm我最初修改了System.Data.SQLite nuget程序包,当我删除它们包括的所有其他废话时,我似乎把它抛在了后面。原始目标文件
kjbartel

2
@SuperJMN那里有通配符。你没注意到**\*.dll吗?那就是复制所有.dll目录中的所有文件。您可以轻松**\*.*地复制整个目录树。
kjbartel 2015年

30

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

这是我想出的一种解决方案,仅依赖于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" /> 为项目添加一个新项目“ Build Action”(也可以在Visual Studio的“ Build Action”下拉列表中找到)。

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

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

  • <PrepareForRunDependsOn ...块将自定义目标添加到构建所依赖的目标列表中,有关详细信息,请参见Microsoft.Common.targets文件。

  • 定制目标CopyNativeBinaries包含两个复制任务。第一个负责将任何*.dl_文件复制到输出目录,同时将其扩展名更改回原始文件*.dll。第二个简单地将其余*.pdb文件(例如任何文件)复制到同一位置。可以用一个复制任务和一个install.ps1脚本代替该脚本,该脚本必须在软件包安装期间将所有*.dl_文件重命名为*.dll

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


4
但是,该解决方案仍然不会将本机二进制文件复制到另一个项目的输出目录中,该项目引用最初包含NuGet软件包的项目。您仍然还必须在“最终”项目中引用NuGet软件包。 “这是一个给我看一下塞子。这通常意味着您需要将nuget包添加到多个项目(例如单元测试)中,否则会被DllNotFoundException抛出。
kjbartel

2
仅仅由于警告,重命名文件等就有点麻烦了。

您可以通过添加删除警告<NoWarn>NU5100</NoWarn>到项目文件
弗洛里安·科赫

28

下面是使用替代.targets项目注入机DLL具有以下属性。

  • 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/夹中设置目标文件名的元素。


我需要将一些.bat和.ps1文件作为我的Azure服务的一部分进行处理-谢谢:)
Zhaph-Ben Duguid 2015年

“(但奇怪的是,该文件在Visual Studio中不可见)。” —项目文件由VS本身的AFAIK解析,因此不会显示添加到外部.target文件中的项目(或在目标执行中动态创建的项目)。
公里

这是怎样的任何不同其他更早的答案比其他变化ContentNone
kjbartel '16

3
哇,你很快。无论如何,如果您选择这样做,您至少可以问“这与我的答案有何不同”。该imo比编辑原始问题,自己回答然后在其他人的评论中推广您的答案更加公平。更何况,我个人很喜欢这个特别的答案比你的好-它的简洁,直截了当,更易于阅读
马克西姆Satsikau

3
@MaksimSatsikau您可能想看看历史。我编辑了问题以使其更清楚,然后回答了问题。几个星期后才得到这个答案,实际上是一个副本。对不起,如果我发现那样的话。
kjbartel


13

有点晚了,但是我为此创建了一个nuget软件包。

这个想法是在您的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


棒极了!但是,这仅在当前项目中有效。如果项目是“类库”,而您想将其作为依赖项添加到“ Web应用程序”中,例如,则不会在Web应用程序中构建DLL!我的“快速修复”是:为您的库创建一个NuGet,并将其应用于类库,并为依赖项(在这种情况下为dll)创建另一个Nuget,并将其应用于WebApplication。有什么最佳解决方案吗?
Wagner Leonardi 2014年

您似乎只为.NET 4.0(Windows)创建了此项目。您是否打算对其进行更新以支持可移植类库?
阿妮(2013年

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");,它将在需要之前将库解压缩到磁盘。当然,您需要确保对磁盘的该部分具有写权限。


1

这是一个老问题,但是我现在遇到了同样的问题,并且发现了一个棘手的问题,但是非常简单有效:在Nuget标准内容文件夹中,为每个配置创建以下结构和一个子文件夹:

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

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

问题:在lib文件夹之外的程序集。说明:程序集“ Content \ Bin \ Debug \ ??????。dll”不在“ lib”文件夹中,因此,将程序包安装到项目中时,不会将其添加为引用。解决方案:如果需要引用,请将其移至“ lib”文件夹。

我们不需要这种“解决方案”,因为这只是我们的目标:不将本机库添加为NET程序集引用。

优点是:

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

缺点是:

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

0

我无法解决您的确切问题,但是我可以给您一个建议。

您的主要要求是:“并且不要让它自动注册参考”。

因此,您必须熟悉“解决方案项”

请参阅此处的参考:

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

您必须编写一些powershell voodoo才能将本机dll的副本放到主目录中(再次,因为您不希望触发自动添加引用的voodoo)

这是我写的一个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>

为我工作

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.