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

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

问题描述

如果标题没有意义,我事先表示歉意。我对appdomains和程序集加载非常陌生,我真的不知道该如何陈述我要问的问题。



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



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

 命名空间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 ALSO :需要将 CopyLocal 设置为 false 以获取LoaderLibrary参考:

 命名空间AssemblyLoaderTest 
{
class Program
{
static void Main(string [] args)
{
EmbeddedAssembly.Load( EmbeddedResource.LoaderLibrary.dll);
System.AppDomain.CurrentDomain.AssemblyResolve + =(s,a)=> {返回EmbeddedAssembly.Get(a.Name); };

var app = new TestApp();
}
}

公共类TestApp
{
public TestApp()
{
LoaderLibrary.LoaderLibrary.Test() ;
}
}

公共类EmbeddedAssembly
{
静态System.Collections.Generic.Dictionary< string,System.Reflection.Assembly>程序集=新的System.Collections.Generic.Dictionary< string,System.Reflection.Assembly>();
公共静态无效载荷(字符串EmbeddedResource)
{
使用(System.IO.Stream stm = System.Reflection.Assembly.GetExecutingAssembly()。GetManifestResourceStream(embeddedResource))
使用(var mstream = new System.IO.MemoryStream())
{
stm.CopyTo(mstream);
var assembly = System.Reflection.Assembly.Load(mstream.ToArray());
assembly.Add(assembly.FullName,程序集);
的回报;
}
}

公共静态System.Reflection.Assembly Get(字符串assemblyFullName)
{
return(assemblies.Count == 0 ||! assembly.ContainsKey(assemblyFullName))? null:assembly [assemblyFullName];
}
}
}

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



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

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

LoaderLibrary.LoaderLibrary.Test(); //非常不满意的代码行
}

这也不起作用:

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

var app = new TestApp();
LoaderLibrary.LoaderLibrary.Test(); //非常不满意的代码行
}


解决方案

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



我发现dthorpe对JIT编译器的工作原理做了很好的解释:C# JIT编译和.NET



在此处引用dthorpe:


是的,准时化IL代码涉及将IL转换为本地机器
指令。



是,.NET运行时与JIT的本机代码
进行交互,这意味着运行时拥有
本机代码占用的内存块,运行时将调用本机代码
等。



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



执行是在执行到达功能或代码块时发生的(例如,
else子句)如果尚未将JIT编译为
本机代码,则调用JIT’r将IL
的该块编译为本机代码。完成后,程序执行将在新发出的机器代码中输入
来执行程序逻辑。如果在执行本机代码时
调用了尚未编译为机器代码的函数
,则调用
JIT'r来编译该函数时间。等等。



JIT’r不一定会立即将功能主体
的所有逻辑编译为机器代码。如果函数具有if语句,则if或else子句的
语句块可能无法JIT编译
,直到执行实际通过该块为止。
尚未执行的代码路径在执行之前一直保持IL形式。



已编译的本机代码保存在内存中,因此可以是
在下一段代码执行时再次使用。
第二次调用该函数将比第一次调用该函数运行得快,因为第二次不需要JIT步骤。



<在桌面.NET中,本地机器代码在appdomain的
生命周期中保留在内存中。在.NET CF中,如果应用程序内存不足,则可能会丢弃本机代码
。下次执行
通过该代码时,将从原始IL代码再次编译
JIT。


利用该问题的信息以及Hans Passant的信息,很清楚发生了什么事情:


  1. JIT编译器试图将整个入口点代码
    块(在本例中为 Main()函数)转换为本机代码。此
    要求它解析所有引用。

  2. 尚未将嵌入式程序集LoaderLibrary.dll加载到
    AppDomain中,因为执行此操作的代码已在
    Main()函数(它无法执行尚未编译的代码)。

  3. JIT编译器尝试通过以下方法来解析对
    LoaderLibrary.dll的引用:搜索AppDomain,全局程序集缓存,
    App.config / Web.config并进行探测(环境PATH,当前
    工作目录,有关更多信息,请参见此处的MSDN
    文章:运行时如何定位程序集

  4. JIT编译器无法解析对
    LoaderLibrary的引用。 LoaderLibrary.Test(); 并导致错误
    无法加载文件或程序集或其依赖项之一

获取如Hans Passant所建议的那样,这是为了将程序集加载到比任何引用这些程序集的代码块更早编译JIT的代码块中。



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


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.

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.

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");
        }
    }
}

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>

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];
        }
    }
}

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
}

This also doesn't work:

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
}

解决方案

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

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

To quote dthorpe here:

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.

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

  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

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.

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天全站免登陆