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

时间:2016-02-18 11:56:54

标签: c# .net reflection appdomain

如果标题没有意义,我会提前道歉。我对appdomains和程序集加载很新,并且不知道如何陈述我想要问的内容。

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

这是我的示例库,它将在运行时作为嵌入式资源加载(需要引用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的项目引用 :需要设置 CopyLocal to false

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
}

1 个答案:

答案 0 :(得分:1)

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

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

在这里引用dthorpe:

  

是的,JIT的IL代码涉及将IL翻译成本机   指令。

     

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

     

您认为.NET运行时不解释IL代码是正确的   在你的集会中。

     

当执行到达函数或代码块时(例如,   尚未被JIT编译的if块的else子句   本机机器代码,调用JIT'r来编译IL块   到本机机器代码。完成后,程序执行进入   刚刚发出的机器代码,用于执行程序逻辑。如果   执行本机机器代码执行时达到一个功能   调用尚未编译为机器代码的函数   调用JIT'r来“及时”编译该函数。等等。

     

JIT'r不一定编译函数体的所有逻辑   立即进入机器代码。如果函数有if语句,那么   if或else子句的语句块可能不是JIT编译的   直到执行实际通过该块。代码路径   没有执行仍然以IL形式保留,直到它们执行。

     

编译后的本机代码保存在内存中,以便它可以   下次代码段执行时再次使用。第二   你调用一个函数的时间会比第一次运行得快   之所以称之为,是因为第二次不需要JIT步骤。

     

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

根据该问题的信息以及Hans Passant的信息,很清楚发生了什么:

  1. JIT编译器尝试转换整个入口点代码 阻止(在本例中为我的Main()函数)到本机代码中。这个 要求它解决所有引用。
  2. 嵌入式程序集LoaderLibrary.dll尚未加载到 AppDomain还是因为执行此操作的代码是在 Main()函数(并且它不能执行尚未编译的代码)。
  3. JIT编译器尝试解析对它的引用 LoaderLibrary.dll通过搜索AppDomain,全局程序集缓存, App.config / Web.config和探测(环境PATH,当前 工作目录等。有关这方面的更多信息可以在MSDN中找到 文章:How the Runtime Locates Assemblies
  4. JIT编译器无法解析对它的引用 LoaderLibrary.LoaderLibrary.Test();并导致错误 Could not load file or assembly or one of its dependencies
  5. 按照Hans Passant的建议解决这个问题的方法是将程序集加载到比引用这些程序集的任何代码块更早编译JIT的代码块中。

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