【问题标题】:MSBuild 16.0 not finding its own dependent assemblies when loaded through MSBuildLocator通过 MSBuildLocator 加载时,MSBuild 16.0 找不到自己的依赖程序集
【发布时间】:2021-01-15 23:11:26
【问题描述】:

我正在尝试从 C# DLL(最终从 PowerShell 加载)以编程方式运行 MSBuild,并作为命令行应用程序的第一步。我已按照推荐(或者我认为)使用 Microsoft.Build.Locator,将其 NuGet 包安装到我的项目中,并将以下引用添加到我的测试项目中:

    <Reference Include="Microsoft.Build, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
      <Private>False</Private>
    </Reference>
    <Reference Include="Microsoft.Build.Framework, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
      <Private>False</Private>
    </Reference>
    <Reference Include="Microsoft.Build.Locator, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9dff12846e04bfbd, processorArchitecture=MSIL">
      <HintPath>..\packages\Microsoft.Build.Locator.1.2.6\lib\net46\Microsoft.Build.Locator.dll</HintPath>
    </Reference>

项目面向.NET Framework 4.8,源码如下:

using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Locator;
using System.Collections.Generic;

namespace nrm_testing
{
    class Program
    {
        static void Main(string[] args)
        {
            MSBuildLocator.RegisterDefaults();
            DoStuff();
        }
        
        static void DoStuff()
        {
            using (var projectCollection = new ProjectCollection())
            {
                var buildParameters = new BuildParameters
                {
                    MaxNodeCount = 1 // https://stackoverflow.com/q/62658963/3233393
                };
                
                var buildRequestData = new BuildRequestData(
                    @"path\to\a\project.vcxproj",
                    new Dictionary<string, string>(),
                    null,
                    new string[0],
                    null
                );

                var result = BuildManager.DefaultBuildManager.Build(buildParameters, buildRequestData);
            }
        }
    }
}

进入using 块后,我收到以下异常:

System.IO.FileNotFoundException: 'Could not load file or assembly 'System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.'

Modules 窗口显示 MSBL 确实成功找到了我的 VS2019 安装:

Microsoft.Build.dll           16.07.0.37604 C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\Microsoft.Build.dll
Microsoft.Build.Framework.dll 16.07.0.37604 C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\Microsoft.Build.Framework.dll
Microsoft.Build.Locator.dll   1.02.6.49918  C:\dev\nrm3-tests\nrm\nrm-testing\.out\AnyCPU-Debug\Microsoft.Build.Locator.dll

System.Runtime.CompilerServices.Unsafe.dll 在版本 4.0.6.0(根据 DotPeek)中确实存在于定位的 MSBuild 程序集之外。

什么可能导致此错误,我该如何解决?


到目前为止我的尝试:

  • 我找到了this question,但linked GitHub issue 仍然打开,我不确定是否是同一个问题。

  • 我已经设法让绑定重定向工作,但我认为我不能在 DLL 中使用它们,所以这是一条死胡同。

  • System.Runtime.CompilerServices.Unsafe NuGet 包添加到项目(并验证它确实与项目的可执行文件一起复制)没有任何作用(感谢 magicandre1981 的建议)。

  • 从 packages.config 切换到 PackageReference(由 Perry Qian 建议),行为没有改变。

【问题讨论】:

  • 将 System.Runtime.CompilerServices.Unsafe 包直接添加到项目中
  • @magicandre1981 我刚试过——唉,没有任何改变。
  • app.config add assemblyBinding System.Runtime.CompilerServices.Unsafe 的条目中
  • @magicandre1981 在我的测试应用程序中工作,但是手动绑定动态定位程序集的依赖项感觉就像一个巨大的黑客攻击。然而,真正的问题是库不能声​​明绑定重定向......我将研究我在其他地方找到的一种解决方法,它基于覆盖一个程序集加载器的回调。
  • 这不是hack,这是正常的,VS在添加包时会自己这样做

标签: c# .net visual-studio msbuild visual-studio-2019


【解决方案1】:

经过大量摆弄不同的想法后,我最终基于manual assembly resolution编写了这个解决方法。

RegisterMSBuildAssemblyPath 检测Microsoft.Build.dll 何时加载,并记住其目录。在随后的程序集加载失败时,RedirectMSBuildAssemblies 检查该路径中是否存在丢失的程序集,如果存在则加载它。

class Program
{
    private static string MSBuildAssemblyDir;

    static void Main(string[] args)
    {
        MSBuildLocator.RegisterDefaults();

        Thread.GetDomain().AssemblyLoad += RegisterMSBuildAssemblyPath;
        Thread.GetDomain().AssemblyResolve += RedirectMSBuildAssemblies;

        DoStuff();
    }

    private static void RegisterMSBuildAssemblyPath(object sender, AssemblyLoadEventArgs args)
    {
        var assemblyPath = args.LoadedAssembly.Location;

        if (Path.GetFileName(assemblyPath) == "Microsoft.Build.dll")
            MSBuildAssemblyDir = Path.GetDirectoryName(assemblyPath);
    }

    private static Assembly RedirectMSBuildAssemblies(object sender, ResolveEventArgs args)
    {
        if (MSBuildAssemblyDir == null)
            return null;

        try
        {
            var assemblyFilename = $"{args.Name.Split(',')[0]}.dll";
            var potentialAssemblyPath = Path.Combine(MSBuildAssemblyDir, assemblyFilename);

            return Assembly.LoadFrom(potentialAssemblyPath);
        }
        catch (Exception)
        {
            return null;
        }
    }

    static void DoStuff()
    {
        // Same as before
    }
}

我很确定有(很多)极端情况会导致此失败,但现在可以。

【讨论】:

  • 既然你有解决方法,我建议你可以标记你自己的答案:)
【解决方案2】:

其实,这对于packages.config nuget management format 来说是一个长期存在的问题。而 Microsoft 的 推荐的解决此问题的方法是添加 bindingRedirect。

通常,您可以在xxx.csproj文件中使用该节点自动生成绑定重定向。

但是,对于某些特定的 dll,由于某些原因,此节点可能无法正常工作。正如您所说,这仍然是当前packages.config nuget management format 的问题。

建议

作为建议,您可以改用 new PackageReference nuget manage format,因为 VS2017。这种格式简单、方便、高效。

另外,当您使用这种格式时,首先应该对您的项目进行备份。

只需右键单击packages.config 文件-->单击Migrate packages.config to PackageReference

另外,我也在DC论坛上举报this issue,希望团队提供更好的建议。

【讨论】:

  • PackageReference 也不走运...我已经设法使绑定重定向在我的测试应用程序上工作,但这对我必须成为库的最终目标没有帮助。从那以后,我发现了一个基于从 MSBuild 目录手动加载未解析的程序集的有点 hacky 的解决方法,我会在有时间后立即在答案中记录。
猜你喜欢
  • 2019-01-30
  • 2015-02-02
  • 1970-01-01
  • 2011-02-23
  • 1970-01-01
  • 1970-01-01
  • 2010-09-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多