首页 > 解决方案 > 某些类型无法从本地项目编译的 dll 加载

问题描述

设置

我正在开发一个没有外部依赖项的 C# 项目,名为“Hopper”。我将它分成几个不同的模块,每个模块都包含在相应的子文件夹中。该问题的相关问题如下:

所有这些都针对 .NET 4.8,包括Hopper.Core.

我已将AssemblyName每个项目的属性设置为相应的字符串。例如,Hopper.Utils.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Library</OutputType>
    <TargetFramework>net4.8</TargetFramework>
    <AssemblyName>Hopper.Utils</AssemblyName>
  </PropertyGroup>

</Project>

Hopper.Core.csproj通过 引用其他项目ProjectReference,如下所示:

<ItemGroup>
  <ProjectReference Include="..\Utils\Hopper.Utils.csproj" />
  <ProjectReference Include="..\Shared\Hopper.Shared.csproj" />
</ItemGroup>

Hopper.Mine.csproj,为测试目的而创建,引用核心如下:

<ItemGroup>
  <ProjectReference Include="..\Core\Hopper.Core.csproj" />
</ItemGroup>

Hopper.Core项目包含一些类型,包括类和结构。一些源文件已由工具自动生成并包含#pragma warning disable在顶部。(删除它没有帮助)。

引用的两个项目Hopper.Core仅包含一些类型和属性,由 使用Core,因此对问题没有任何意义。它们不引用任何其他项目或 dll。

问题

我可以通过在项目文件的子文件夹中Hopper.Mine运行来进行编译。dotnet build我可以bin/Debug/net4.8/Hopper.Whatever.dll在每个项目的文件夹下看到引用子项目的输出 dll。VSCode 中的 Intellisense 也可以正常工作,不会报告任何错误。

Hopper.Mine有一个带有 Main 函数的类,为测试而创建。它列出了Hopper.Core已成功加载的类型,并报告了尚未加载的类型:

using System;
using System.Reflection;
using System.Text;

namespace Hopper.Mine
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Assembly lib = typeof(Hopper.Core.Action).Assembly;
            Type[] types;
            try
            {
                types = lib.GetTypes();
            }
            catch (ReflectionTypeLoadException ex)
            {
                StringBuilder sb = new StringBuilder();
                foreach (Exception exSub in ex.LoaderExceptions)
                {
                    Console.WriteLine(exSub.Message);
                }
                types = ex.Types;
            }
            
            foreach (Type type in types)
            {
                if (type != null)
                    Console.WriteLine(type.FullName);
            }
        }
    }
}

运行这段代码,我可以看到一堆有问题的类型,其中一些重复,其余的类型已经成功加载:

Could not load type 'Hopper.Core.Stat.Attack' from assembly 'Hopper.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Could not load type 'Hopper.Core.Stat.Dig' from assembly 'Hopper.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Could not load type 'Hopper.Core.Stat.Move' from assembly 'Hopper.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Could not load type 'Hopper.Core.Stat.Push' from assembly 'Hopper.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Could not load type 'Hopper.Core.Stat.Attack' from assembly 'Hopper.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Could not load type 'Hopper.Core.Stat.Attack' from assembly 'Hopper.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.

... more errors here ...

Hopper.Core.DirectedPredict
Hopper.Core.UndirectedPredict
Hopper.Core.DirectedDo

... more types here ...

Hopper.Core.Stat.Attack+Source+Resistance
Hopper.Core.Stat.Push+Source+Resistance

需要注意4点:

  1. 加载失败的类型都是 structs 并且派生自IStat.
  2. 具有这些类型的源文件已由工具自动生成(由工具生成的其他文件加载没有错误)。
  3. 这些类型不引用来自Hopper.Shared或的任何类型Hopper.Utils
  4. 一些类型具有嵌套结构,最多 3 级深,一些更深的似乎已成功加载。

我很确定Hopper.Core的输出文件夹中的 dll 输出与Hopper.Mine. 我也很确定在我启动程序时链接了正确的 dll(我尝试使用 VS 进行调试,在其中我可以看到正在链接的 dll 以及匹配的路径)。

我已经尝试删除AssemblyName属性,将 csproj 文件从 重命名Hopper.Whatever.csprojHopper_Whatever.csproj,我显然尝试清理任何以前的输出 dll 并从头开始构建,但这些都没有帮助。我还能够在另一台机器上以完全相同的结果重现这一点。

我尝试Hopper.Core使用反编译器 ILSpy 查看 dll。我能够看到代码中的“缺失”类型。ILSpy反编译结果

我已经在谷歌上搜索了几个小时来寻找像我这样的问题,但无济于事。我发现很多人错误地设置了他们的项目文件,因此他们意外地链接到了与他们预期不同的 dll 版本。对于某些人来说,这种依赖关系出现在引用的第三方项目之一的需求中。有些人指责 GAC 提供了不正确的 dll 版本。但是,我很确定我的项目设置正确(+ 设置非常简单,并且没有引用第三方项目)并且我引用了正确的 dll(因为我可以在其中看到我的类型)。看起来类型是在 dll 中“声明”的,但不是“定义”(有点),所以 dll 被破坏了。但是为什么反编译不会失败呢?所以我目前完全坚持这一点。

手动加载程序集

我已经尝试通过它们的名称动态链接 dll。列出从导入的类型Hopper.SharedHopper.Utils工作,但是,当我尝试使用 时Hopper.Core,我得到与上面示例相同的输出。

为了实现这一点,我将以前的 dll 从输出复制到一个新文件夹,从 中删除引用Hopper.Mine,用 编译新代码dotnet build,然后将可执行文件移动到新文件夹,最后在控制台中运行它。编码:

using System;
using System.Reflection;
using System.Text;

namespace Hopper.Mine
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Assembly shared = Assembly.LoadFrom("Hopper.Shared.dll");
            Assembly utils = Assembly.LoadFrom("Hopper.Utils.dll");
            Assembly core = Assembly.LoadFrom("Hopper.Core.dll");
            Type[] types;
            try
            {
                types = core.GetTypes();
            }
            catch (ReflectionTypeLoadException ex)
            {
                StringBuilder sb = new StringBuilder();
                foreach (Exception exSub in ex.LoaderExceptions)
                {
                    Console.WriteLine(exSub.Message);
                }
                types = ex.Types;
            }
            
            foreach (Type type in types)
            {
                if (type != null)
                    Console.WriteLine(type.FullName);
            }
        }
    }
}

重现

如果有帮助,您可以尝试运行我的代码。这是项目当前状态的 github 链接。为了运行它,您需要:

  1. .NET 4.8 已安装。
  2. 从上面的链接克隆存储库。
  3. 仍然缺少一些代码。您需要运行文件夹中的项目Meta来生成它。只需dotnet runMeta文件夹中运行。
  4. 现在您可以运行Mine子项目。

标签: c#.netdllmsbuild

解决方案


找出了原因。CLR 类型系统中存在一个已经存在 6 年的错误。基本上,在其他结构中声明的通用结构类型的字段(静态或非静态)会导致此行为。有关更多详细信息,请参阅此 github 线程

就我而言,我已经能够制作一个更短的版本来说明这个错误。

public struct Index<T>
{
}

public struct Test
{
    public static Index<Test> Index;
}

使用以下代码测试程序:

public class Program
{
    public static void Main(string[] args)
    {
        Console.WriteLine(typeof(Test).Assembly.GetTypes());
    }
}

运行代码时,会抛出以下错误:

Unhandled Exception: System.TypeLoadException: Could not load type 'Hopper.Mine.Test' from assembly 'Hopper.Mine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
   at Hopper.Mine.Program.Main(String[] args)

但是,有一种简单的解决方法。从结构更改Test为类工作:

// This works!
public struct Index<T>
{
}

public class Test
{
    public static Index<Test> Index;
}

在某些情况下,这可能是不可接受的。所以这是另一种解决方法——将静态字段存储在数组中并使用属性访问它也可以:

public struct Index<T>
{
}

public struct Test
{
    public static Index<Test> Index { get => index[0]; set => index[0] = value; }
    public static Index<Test>[] index = new Index<Test>[1];
}

推荐阅读