为什么在解析引用(不是通过反射)时,Assembly.Load 似乎不会影响当前线程? [英] Why does Assembly.Load seem to not affect the current thread when resolving references (not through reflection)?

查看:22
本文介绍了为什么在解析引用(不是通过反射)时,Assembly.Load 似乎不会影响当前线程?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果标题没有意义,我提前道歉.我对应用程序域和程序集加载非常陌生,我真的不知道如何说明我想问什么.

I apologize in advance if the title doesn't make sense. I'm very new to appdomains and assembly loading and don't really know how to state what I'm trying to ask.

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

I have been fiddling around with loading embedded DLLs into an application during runtime and I can't seem to figure out why it works one way but not the other. It seems like if you try to load DLLs (from a byte array) into the current appdomain, any objects/threads created after that will be able to resolve references against the newly loaded library, however objects in the original context will not resolve against the newly loaded library.

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

Here is my example library that will be loaded from as an embedded resource during runtime (requires a reference to the WPF PresentationFramework.dll for MessageBox):

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

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

In my console app .csproj file I manually add the following embedded resource for that project and include a project reference to LoaderLibrary as well:

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

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

Here is the code for my console app that loads that library (requires a project reference to the LoaderLibrary csproj) ALSO: Need to set CopyLocal to false for LoaderLibrary reference:

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() 函数.

This code is able to successfully load and execute the LoaderLibrary.LoaderLibrary.Test() function.

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

My question is why does the following not work?

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 解释发生的事情.

Big thanks to Hans Passant and dthorpe for explaining what was happening.

我在这里找到了 dthorpe 关于 JIT 编译器如何工作的很好的解释:C# JIT 编译和 .NET

I found dthorpe's great explanation on how the JIT compiler works here: C# JIT compiling and .NET

在这里引用 dthorpe:

To quote dthorpe here:

是的,JIT'ing IL 代码涉及将 IL 翻译成本地机器说明.

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

是的,.NET 运行时与经过 JIT 处理的本机代码交互,从某种意义上说,运行时拥有被占用的内存块本机机器代码,运行时调用本机机器代码,等

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.

.NET 运行时不解释 IL 代码是正确的在您的程序集中.

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

当执行到达一个函数或代码块(比如,if 块的 else 子句)尚未被 JIT 编译成本地机器代码,调用 JIT'r 来编译 IL 块转换为本地机器代码.完成后,程序执行进入新发出的机器代码来执行它的程序逻辑.如果在执行本机代码执行时到达一个函数调用尚未编译为机器码的函数,调用 JIT'r 以及时"编译该函数.等等.

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.

JIT'r 不一定编译函数体的所有逻辑一下子变成机器码.如果函数有 if 语句,则if 或 else 子句的语句块可能不会被 JIT 编译直到执行实际通过该块.代码路径未执行的保留在 IL 形式,直到它们执行.

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.

编译后的本地机器码保存在内存中,以便于下次执行该部分代码时再次使用.第二当你调用一个函数时,它会比第一次运行得更快调用它是因为第二次不需要 JIT 步骤.

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.

在桌面 .NET 中,本机机器代码保存在内存中以供应用程序域的生命周期.在 .NET CF 中,本机机器代码可能是如果应用程序内存不足,则将其丢弃.这将是下次执行时从原来的 IL 代码重新编译 JIT通过该代码.

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 的信息,很清楚发生了什么:

With the information from that question, and the information from Hans Passant, it is very clear what is happening:

  1. JIT 编译器尝试转换整个入口点代码块(在本例中是我的 Main() 函数)进入本机代码.这个要求它解析所有引用.
  2. 嵌入式程序集 LoaderLibrary.dll 尚未加载到AppDomain 还没有,因为执行此操作的代码是在Main() 函数(它不能执行未编译的代码).
  3. JIT 编译器尝试解析对LoaderLibrary.dll 通过搜索 AppDomain、全局程序集缓存、App.config/Web.config 和探测(环境 PATH,当前工作目录等)更多信息可以在 MSDN 中找到这里的文章:运行时如何定位程序集
  4. JIT 编译器无法解析对LoaderLibrary.LoaderLibrary.Test(); 并导致错误无法加载文件或程序集或其依赖项之一
  1. The JIT compiler attempts to convert the entire entry point code block (in this case my Main() function) into native code. This requires that it resolve all references.
  2. The embedded assembly LoaderLibrary.dll has NOT been loaded into the AppDomain yet because the code that does this is defined in the Main() function (and it can't execute code that hasn't been compiled).
  3. The JIT compiler attempts to resolve the reference to LoaderLibrary.dll by searching the AppDomain, Global Assembly Cache, App.config/Web.config, and probing (environment PATH, current working directory, etc.) More info on this can be found in the MSDN article here: How the Runtime Locates Assemblies
  4. The JIT compiler fails to resolve the reference to LoaderLibrary.LoaderLibrary.Test(); and results in the error Could not load file or assembly or one of its dependencies

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

The way to get around this as suggested by Hans Passant is to load your assemblies in a code block that gets JIT compiled earlier than any code block that references those assemblies.

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

By adding the [MethodImpl(MethodImplOptions.NoInlining)] to methods that reference the dynamically loaded assemblies, it will prevent the optimizer from trying to inline the method code.

这篇关于为什么在解析引用(不是通过反射)时,Assembly.Load 似乎不会影响当前线程?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆