首页 文章

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

提问于
浏览
94

我正在尝试为.Net程序集创建NuGet程序包,该程序集会对本机win32 dll执行pinvoke . 我需要打包程序集和本机dll,并将程序集添加到项目引用中(此部分没有问题),并且应将本机dll复制到项目输出目录或其他相关目录中 .

我的问题是:

  • 如何在没有visual studio尝试将其添加到引用列表中的情况下打包本机dll?

  • 我是否必须编写install.ps1来复制本机dll?如果是这样,我如何访问包内容进行复制?

9 回答

  • 100

    使用目标文件中的 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 版本,最终将得到类似于以下内容的目录结构:

    • build

    • x86

    • NativeLib.dll

    • NativeLibDependency.dll

    • x64

    • NativeLib.dll

    • NativeLibDependency.dll

    • MyNugetPackageID.targets

    • lib

    • net40

    • ManagedAssembly.dll

    在项目's output directory when built. If you don't需要子目录中将创建相同的 x86x64 目录,然后可以删除 **%(RecursiveDir) ,而是直接在 build 目录中包含所需的文件 . 其他所需的内容文件也可以以相同的方式添加 .

    在目标文件中添加为 None 的文件赢得't be shown in the project when open in Visual Studio. If you are wondering why I don' t使用nupkg中的 Content 文件夹's because there' s无法设置 CopyToOutputDirectory 元素without using a powershell script(它只能在Visual Studio中运行,而不是在命令提示符下,在构建服务器上运行或在其他IDE中,并且not supported in project.json / xproj DNX projects)我更喜欢使用 Link 文件,而不是在项目中有一个额外的文件副本 .

    Update: 虽然这也适用于 Content 而不是 None ,但似乎有's a bug in msbuild so files won'被复制到引用项目多个步骤被删除(例如proj1 - > proj2 - > proj3,proj3赢得't get the files from proj1'的NuGet包但proj2将会) .

  • 0

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

    这是我提出的解决方案,仅依赖于NuGet和MSBuild:

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

    • 将所有未管理的 *.dll 文件结尾重命名为不同的名称,例如 *.dl_ ,以防止NuGet抱怨所谓的程序集被放置在错误的位置("Problem: Assembly outside lib folder.") .

    • /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" /> 为项目添加了一个新项目"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包 .

  • 29

    以下是使用 .targetsinject the native DLL in the project 以及以下属性的替代方法 .

    • Build action = None

    • Copy to Output Directory = Copy if newer

    此技术的主要好处是本机DLL可传递地复制到 dependent projectsbin/ 文件夹中 .

    查看 .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 ,就像它是原始项目的一部分一样(但奇怪的是,该文件在Visual Studio中不可见) .

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

  • 21

    这有点晚了,但我已经为此创建了一个nuget包exaclty .

    我们的想法是在您的nuget包中添加一个额外的特殊文件夹 . 我相信你已经了解了Lib和Content . 我创建的nuget包查找名为Output的文件夹,并将其中的所有内容复制到项目输出文件夹中 .

    你唯一要做的就是给包_978680_添加一个nuget依赖项

    我写了一篇关于它的博文:http://www.baseclass.ch/blog/Lists/Beitraege/Post.aspx?ID=6&mobile=0

  • 12

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

    .targets filename MUST 等于NuGet包ID

    别的什么都不行 .

    积分转至:https://sushihangover.github.io/nuget-and-msbuild-targets/

    我应该仔细阅读,因为它实际上在这里注意到 . 花了我很多时间..

    添加自定义<PackageName> .targets

  • 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文件夹中创建以下结构,每个配置有一个子文件夹:

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

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

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

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

    优点是:

    • 简单的解决方案,没有繁琐的脚本,具有奇怪的效果,在卸载程序包时很难重置 .

    • Nuget在安装和卸载时将本机库作为任何其他内容进行管理 .

    缺点是:

    • 每个配置都需要一个文件夹(但通常只有两个:Debug和Release,如果你有其他内容必须安装在每个配置文件夹中,这是要走的路)

    • 必须在每个配置文件夹中复制本机库(但如果每个配置都有不同版本的本机库,则可以采用这种方式)

    • 每个文件夹中每个本机dll的警告(但正如我所说,它们会在打包时向包创建者发出警告,而不是在VS安装时发送给包用户)

  • 10

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

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

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

    见这里的参考:

    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"
    
  • 1

    把它放在内容文件夹中

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

    为我工作

相关问题