ChatGPT解决这个技术问题 Extra ChatGPT

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

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

我的问题是:

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

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

D
Drew Noakes

使用目标文件中的 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.targets

x86 NativeLib.dll NativeLibDependency.dll

NativeLib.dll

NativeLibDependency.dll

x64 NativeLib.dll NativeLibDependency.dll

NativeLib.dll

NativeLibDependency.dll

MyNugetPackageID.targets

lib net40 ManagedAssembly.dll

net40 ManagedAssembly.dll

ManagedAssembly.dll

构建时,将在项目的输出目录中创建相同的 x86x64 目录。如果您不需要子目录,则可以删除 **%(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 - > proj2 -> proj3,proj3 不会从 proj1 的 NuGet 包中获取文件,但 proj2 会)。


先生,您真是个天才!奇迹般有效。谢谢。
@SuperJMN 那里有通配符。您没有注意到 **\*.dll 吗?这是复制所有目录中的所有 .dll 文件。您可以轻松地执行 **\*.* 来复制整个目录树。
真的吗?我不敢相信这是 NuGet 社区 5 年来最好的解决方案
@kjbartel 我实际上发现了问题 - 我在 .targets 文件的这一行中为资源提供了相同的名称:<NativeLibs Include="$(MSBuildThisFileDirectory)**\*.dll" /> 这导致了冲突。相反,您必须在不同的 nuget 包中提供不同的名称。例如:'<LibraryANativeLibs Include=...>, '<LibraryBNativeLibs Include=...>
我已经找到了解决方案:默认情况下,传递依赖项 .targets 似乎被禁用。您需要通过调整 PrivateAssets 的值来选择加入,请参阅 herems docs
D
Drew Noakes

这是一个替代方法,它使用 .targets在项目中注入本机 DLL,并具有以下属性。

构建操作 = 无

复制到输出目录 = 如果更新则复制

这种技术的主要好处是本机 DLL 可传递地复制到 依赖项目bin/ 文件夹中。

查看 .nuspec 文件的布局:

https://i.stack.imgur.com/nGB5G.png

这是 .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 中不可见)。

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


处理一些我需要包含在 Azure 服务中的 .bat 和 .ps1 文件 - 谢谢 :)
“(但奇怪的是,该文件在 Visual Studio 中不可见)。” — 项目文件由 VS 本身 AFAIK 解析,因此不显示在外部 .target 文件中添加的项目(或在目标执行中动态创建的项目)。
除了从 Content 更改为 None 之外,这与 other earlier answer 有何不同?
哇,你真快。无论如何,如果您选择这样做,您至少可以问“这与我的答案有何不同”。这比编辑原始问题、自己回答然后在其他人的评论中宣传你的答案更公平。更不用说我个人比你更喜欢这个特定的答案——它简洁、中肯、更容易阅读
@MaksimSatsikau 您可能想看看历史。我编辑了问题以使其更清楚,然后回答了问题。这个答案是几周后出现的,实际上是一个副本。对不起,如果我觉得这很粗鲁。
D
Drew Noakes

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

这是我想出的一个解决方案,它仅依赖于 NuGet 和 MSBuild:

将托管程序集放在包的 /lib 目录中(显而易见的部分),将非托管共享库和相关文件(例如 .pdb 包)放在 /build 子目录中(如 NuGet 文档中所述)。将所有非托管 *.dll 文件结尾重命名为不同的名称,例如 *.dl_ 以防止 NuGet 抱怨所谓的程序集被放置在错误的位置(“问题:lib 文件夹外的程序集。”)。在 /build 子目录中添加一个自定义 .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 包时注入,并负责将本机库复制到输出目录。

为项目添加了一个新项目“构建操作”(在 Visual Studio 的“构建操作”下拉列表中也可用)。

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

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

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


"但是,此解决方案仍然不会将本机二进制文件复制到另一个项目的输出目录,该目录引用最初包含 NuGet 包的项目。您仍然必须在“最终”项目中引用 NuGet 包。< /i>" 这对我来说是一个表演终结者。这通常意味着您需要将 nuget 包添加到多个项目(例如单元测试)中,否则会抛出 DllNotFoundException
仅仅因为警告而重命名文件等有点过激。
您可以通过将 <NoWarn>NU5100</NoWarn> 添加到项目文件中来删除警告
D
DirtyLittleHelper

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

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

别的什么都行不通。

致谢:https://sushihangover.github.io/nuget-and-msbuild-targets/

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

添加自定义 .targets


你救了我一整天!
你用其他东西解决了一个为期一周的问题。感谢你和那个 github 页面。
D
Daniel Romero

这有点晚了,但我已经为此创建了一个 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 应用程序”,则 DLL 不会在 Web 应用程序中构建!我的“快速修复”是:为您的库创建一个 NuGet,并应用于类库,并为依赖项(在本例中为 dll)创建另一个 Nuget 并应用于 WebApplication。有什么最好的解决方案吗?
您似乎仅为 .NET 4.0 (Windows) 创建了这个项目。您是否打算更新它以支持可移植类库?
O
Ondrej Janacek

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


这种方法更简单,并且不会强迫您的 lib 用户选择 x86 或 x64,因为它可以正常工作。我嵌入了 32 位和 64 位本机 dll,解包例程将根据 ptr 的运行时大小创建必要的文件。我首先测试是否存在,然后测试本机调用并捕获 (BadImageFormatException e) 并在格式错误时删除文件。所有这一切都在大约 12 行代码中完成,没有复杂的 nuget 内容和对 AnyCpu 的继承支持。
@罗伯特感谢您的评论。到目前为止,在玩过 .NET SDK 之后,我会使用 nuget 解决方案,但这仍然是一个不错的后备解决方案。
好奇的?在用户不再需要显式设置和构建到 x86 或 x64 以使 nuget 处理这两种情况的情况下,事情是否发生了变化?
是的。检查来自 kjbartel 的答案。这就是我这些天倾向于使用的解决方案。
S
SERWare

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

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

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

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

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

优点是:

简单的解决方案,没有繁琐的脚本,具有在软件包卸载时难以重置的奇怪效果。 Nuget 在安装和卸载时将本机库作为任何其他内容进行管理。

缺点是:

每个配置都需要一个文件夹(但通常只有两个:Debug 和 Release,如果每个配置文件夹中还有其他必须安装的内容,这也是可行的方法)每个配置中都必须复制原生库文件夹(但如果每个配置都有不同版本的本机库,这就是要走的路)每个文件夹中每个本机 dll 的警告(但正如我所说,它们在打包时向包创建者发出警告,而不是 VS 安装时的包用户)


C
Community

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

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

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

请参阅此处的参考:

Adding solution-level items in a NuGet package

您必须编写一些 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" 

S
Sharon Salmon

放它是内容文件夹

如果您将文件标记为内容,命令 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>

为我工作