140

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

我的问题是:

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

9 回答 9

149

使用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。因此,要添加托管程序集使用的本机库的x86和版本,您最终会得到类似于以下的目录结构:x64Any CPU

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

构建时,将在项目的输出目录中创建相同的x86和目录。x64如果您不需要子目录**,则%(RecursiveDir)可以删除 和 ,而是build直接在目录中包含所需的文件。其他需要的内容文件也可以用同样的方法添加。

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

更新: 虽然这也应该适用,Content而不是None似乎 msbuild 中存在错误,因此文件不会被复制到引用项目超过一个步骤删除(例如 proj1 -> proj2 -> proj3,proj3 不会获取文件来自 proj1 的 NuGet 包,但 proj2 会)。

于 2015-05-19T04:46:31.280 回答
33

这是一个替代方法,它使用以下属性在项目.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/

于 2015-06-05T13:54:40.757 回答
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包含两个复制任务。第一个负责将所有*.dl_文件复制到输出目录,同时将它们的扩展名更改回原来的*.dll. 第二个只是将其余部分(例如任何*.pdb文件)复制到同一位置。这可以由单个复制任务和install.ps1脚本替换,该脚本必须在包安装期间将所有*.dl_文件重命名为。*.dll

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

于 2014-03-19T15:42:42.530 回答
24

如果其他人偶然发现这一点。

.targets文件名必须等于 NuGet 包ID

别的什么都行不通。

学分转到: https ://sushihangover.github.io/nuget-and-msbuild-targets/

我应该更彻底地阅读,因为它实际上在这里指出。花了我很多年..

添加自定义<PackageName>.targets

于 2017-03-31T15:06:26.760 回答
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

于 2013-11-30T14:09:59.893 回答
3

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

于 2016-04-11T15:02:07.900 回答
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 安装时向包用户发出)
于 2016-11-08T17:14:11.367 回答
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" 
于 2013-10-21T18:54:21.417 回答
-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>

为我工作

于 2017-10-16T10:09:52.720 回答