c# - 为什么 Assembly.Load 在解析引用时似乎不影响当前线程(不是通过反射)?

标签 c# .net reflection appdomain

如果标题没有意义,我提前道歉。我对 appdomains 和程序集加载还很陌生,真的不知道如何表达我想问的问题。

我一直在摆弄在运行时将嵌入式 DLL 加载到应用程序中的问题,我似乎无法弄清楚为什么它以一种方式工作而不是另一种方式。似乎如果您尝试将 DLL(从字节数组)加载到当前应用程序域中,之后创建的任何对象/线程都将能够解析对新加载库的引用,但是原始上下文中的对象将不会解析新加载的库。

这是我的示例库,它将在运行时作为嵌入式资源从中加载(需要引用 MessageBox 的 WPF PresentationFramework.dll):

namespace LoaderLibrary
{
    public class LoaderLibrary
    {
        public static void Test()
        {
            System.Windows.MessageBox.Show("success");
        }
    }
}

在我的控制台应用程序 .csproj 文件中,我为该项目手动添加了以下嵌入式资源,并且还包含了对 LoaderLibrary 的项目引用:

  <ItemGroup>
    <EmbeddedResource Include="..\LoaderLibrary\bin\$(Configuration)\LoaderLibrary.dll">
      <LogicalName>EmbeddedResource.LoaderLibrary.dll</LogicalName>
    </EmbeddedResource>
  </ItemGroup>

这是加载该库的控制台应用程序的代码(需要对 LoaderLibrary csproj 的项目引用)另外:需要设置CopyLocalfalse 用于 LoaderLibrary 引用:

namespace AssemblyLoaderTest
{
    class Program
    {
        static void Main(string[] args)
        {
            EmbeddedAssembly.Load("EmbeddedResource.LoaderLibrary.dll");
            System.AppDomain.CurrentDomain.AssemblyResolve += (s, a) => { return EmbeddedAssembly.Get(a.Name); };

            var app = new TestApp();
        }
    }

    public class TestApp
    {
        public TestApp()
        {
            LoaderLibrary.LoaderLibrary.Test();            
        }
    }

    public class EmbeddedAssembly
    {
        static System.Collections.Generic.Dictionary<string, System.Reflection.Assembly> assemblies = new System.Collections.Generic.Dictionary<string, System.Reflection.Assembly>();
        public static void Load(string embeddedResource)
        {
            using (System.IO.Stream stm = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(embeddedResource))
            using (var mstream = new System.IO.MemoryStream())
            {
                stm.CopyTo(mstream);
                var assembly = System.Reflection.Assembly.Load(mstream.ToArray());
                assemblies.Add(assembly.FullName, assembly);
                return;
            }
        }

        public static System.Reflection.Assembly Get(string assemblyFullName)
        {
            return (assemblies.Count == 0 || !assemblies.ContainsKey(assemblyFullName)) ? null : assemblies[assemblyFullName];
        }
    }
}

此代码能够成功加载并执行 LoaderLibrary.LoaderLibrary.Test() 函数。

我的问题是为什么以下内容不起作用?

static void Main(string[] args)
{
    EmbeddedAssembly.Load("EmbeddedResource.LoaderLibrary.dll");
    System.AppDomain.CurrentDomain.AssemblyResolve += (s, a) => { return EmbeddedAssembly.Get(a.Name); };

    LoaderLibrary.LoaderLibrary.Test(); // very unhappy line of code
}

这也行不通:

static void Main(string[] args)
{
    EmbeddedAssembly.Load("EmbeddedResource.LoaderLibrary.dll");
    System.AppDomain.CurrentDomain.AssemblyResolve += (s, a) => { return EmbeddedAssembly.Get(a.Name); };

    var app = new TestApp();
    LoaderLibrary.LoaderLibrary.Test(); // very unhappy line of code
}

最佳答案

非常感谢 Hans Passant 和 dthorpe 解释了发生的事情。

我在这里找到了 dthorpe 关于 JIT 编译器如何工作的精彩解释:C# JIT compiling and .NET

在这里引用 dthorpe:

Yes, JIT'ing IL code involves translating the IL into native machine instructions.

Yes, the .NET runtime interacts with the JIT'ed native machine code, in the sense that the runtime owns the memory blocks occupied by the native machine code, the runtime calls into the native machine code, etc.

You are correct that the .NET runtime does not interpret the IL code in your assemblies.

What happens is when execution reaches a function or code block (like, an else clause of an if block) that has not yet been JIT compiled into native machine code, the JIT'r is invoked to compile that block of IL into native machine code. When that's done, program execution enters the freshly emitted machine code to execute it's program logic. If while executing that native machine code execution reaches a function call to a function that has not yet been compiled to machine code, the JIT'r is invoked to compile that function "just in time". And so on.

The JIT'r doesn't necessarily compile all the logic of a function body into machine code at once. If the function has if statements, the statement blocks of the if or else clauses may not be JIT compiled until execution actually passes through that block. Code paths that have not executed remain in IL form until they do execute.

The compiled native machine code is kept in memory so that it can be used again the next time that section of code executes. The second time you call a function it will run faster than the first time you call it because no JIT step is necessary the second time around.

In desktop .NET, the native machine code is kept in memory for the lifetime of the appdomain. In .NET CF, the native machine code may be thrown away if the application is running low on memory. It will be JIT compiled again from the original IL code the next time execution passes through that code.

有了那个问题的信息,以及来自 Hans Passant 的信息,很清楚发生了什么:

  1. JIT 编译器尝试转换整个入口点代码 block (在本例中是我的 Main() 函数)到 native 代码中。这 要求它解析所有引用。
  2. 嵌入式程序集 LoaderLibrary.dll 尚未加载到 AppDomain,因为执行此操作的代码定义在 Main() 函数(不能执行未编译的代码)。
  3. JIT 编译器尝试解析对 LoaderLibrary.dll通过搜索AppDomain,Global Assembly Cache, App.config/Web.config,并探测(环境路径,当前 工作目录等)更多信息可以在 MSDN 中找到 此处文章:How the Runtime Locates Assemblies
  4. JIT 编译器无法解析对 LoaderLibrary.LoaderLibrary.Test(); 并导致错误 无法加载文件或程序集或其依赖项之一

按照 Hans Passant 的建议,解决此问题的方法是将您的程序集加载到一个代码块中,该代码块比引用这些程序集的任何代码块更早地进行 JIT 编译。

通过将 [MethodImpl(MethodImplOptions.NoInlining)] 添加到引用动态加载程序集的方法中,它将阻止优化器尝试内联方法代码。

关于c# - 为什么 Assembly.Load 在解析引用时似乎不影响当前线程(不是通过反射)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35480817/

相关文章:

c# - 引用外部 dll 时由于某些元组错误而无法编译 c# 项目

c# - 是否有令人信服的理由在声明事件时使用 EventHandler<T> 委托(delegate)而不只是 Action<T>?

.net - Reflector 的开源替代品?

c# - 如何获取接口(interface)方法的MethodInfo,实现了类方法的MethodInfo?

c# - 跨越不同性能计数器的性能计数器实例

c# - 为 System.Windows.Shapes.Path 动态设置 Path.Data

c# - 如何在调试时轻松查看事件订阅数?

c# - C# 中的 2 个参数未编译

.net - 集合内存不足

java - ParameterizedType.getRawType() 返回 j.l.r.Type,而不是 Class<?>?