可以调用Assembly.Load(byte [])来引发AppDomain.AssemblyResolve事件吗?

时间:2014-07-13 02:00:46

标签: c# .net reflection clr .net-assembly

假设我有一个AppDomain.AssemblyResolve事件的处理程序,在处理程序中我构造一个字节数组并调用方法Assembly.Load(byte[])。此方法本身是否会导致再次引发AssemblyResolve事件,并导致我的处理程序重新进入?

我的问题不仅限于可以使用C#编译器生成的程序集,它们可以包含CLR支持的abritrary元数据和可执行代码。

我做了一些实验,发现时没有发现任何情况。我尝试加载需要额外引用的程序集,尝试将CAS属性添加到已加载的程序集,其解码需要另一个程序集,尝试使用模块初始值设定项加载程序集(全局.cctor方法)。在任何情况下,我都没有观察到AssemblyResolve方法中要引发的Assembly.Load(byte[])事件,只有在某些代码稍后尝试访问加载的程序集中的类型,方法或属性时才会发生这种情况。但我可能会在这里遗漏一些东西。

5 个答案:

答案 0 :(得分:3)

模块初始化程序是我能想到的唯一麻烦制造者。 C ++ / CLI中的一个简单示例:

#include "stdafx.h"
#include <msclr\gcroot.h>

using namespace msclr;
using namespace ClassLibrary10;

class Init {
    gcroot<ClassLibrary1::Class1^> managedObject;
public:
    Init() {
        managedObject = gcnew ClassLibrary1::Class1;
    }
} Initializer;

在初始化C运行时之后,通过模块初始化程序加载模块时,将调用Init()构造函数。尽管在特定情况下,Assembly.Load(byte [])无法加载混合模式程序集,但您对此类代码不屑一顾。

这不是由模块初始化器引起的限制。它们被添加到CLR v2.0中,其具体意图是类似这样的工作,在开始执行任何托管代码之前,让语言运行时初始化自己。你遇到这样的代码的几率应该非常非常低。当你看到它时你就会知道它:)

答案 1 :(得分:1)

据我所知Assembly.Load或通过其他方式加载程序集不会执行可由C#编译器生成的任何构造函数(包括静态构造函数)。因此,您无法在常见程序集上对AssemblyResolve进行重入。

正如您在问题中提到的那样,在Load调用期间不会执行模块初始值设定项。 CLI规范中的保证列表 - 摘录可以在张俊峰的Module Initializers中找到。

  

B中。模块的初始化方法在首次访问模块中定义的任何类型,方法或数据之前或之前执行

有相关的SO问题通常在任何类型构造函数之前讨论&#34;运行代码&#34;比如Initialize library on Assembly load。请注意,.Net: Running code when assembly is loaded得到了Marc Gravell的回答,指出由于安全限制,它可能无法实现。

答案 2 :(得分:1)

MSDN Documentation州:

  

AssemblyResolve事件的工作原理:

     

当您为AssemblyResolve事件注册处理程序时,只要运行时无法按名称绑定到程序集,就会调用该处理程序。例如,从用户代码调用以下方法可能导致引发AssemblyResolve事件:

     
      
  1. AppDomain.Load方法重载或Assembly.Load方法重载,其第一个参数是一个字符串,表示要加载的程序集的显示名称(即,由Assembly.FullName属性)。

  2.   
  3. AppDomain.Load方法重载或Assembly.Load方法重载,其第一个参数是 AssemblyName对象,用于标识要加载的程序集。

  4.   

没有提到接收byte[]的重载。我在参考源中查找,似乎接受Load重载的string在内部调用名为InternalLoad的方法,该方法在调用本机LoadImage调用之前internal static AssemblyName CreateAssemblyName( String assemblyString, bool forIntrospection, out RuntimeAssembly assemblyFromResolveEvent) { if (assemblyString == null) throw new ArgumentNullException("assemblyString"); Contract.EndContractBlock(); if ((assemblyString.Length == 0) || (assemblyString[0] == '\0')) throw new ArgumentException(Environment.GetResourceString("Format_StringZeroLength")); if (forIntrospection) AppDomain.CheckReflectionOnlyLoadSupported(); AssemblyName an = new AssemblyName(); an.Name = assemblyString; an.nInit(out assemblyFromResolveEvent, forIntrospection, true); // This method may internally invoke AssemblyResolve event. return an; 3}}及其文档说明:

  

创建AssemblyName。如果已引发AssemblyResolve事件,则填充程序集。

byte[]

nLoadImage重载没有这个,它只是调用QCall.dll内的原生ResolveEvent。这可以解释为什么不调用{{1}}。

答案 3 :(得分:1)

你提到过 -

  

在任何情况下,我都没有观察到要从中提出的AssemblyResolve事件   在Assembly.Load(byte [])方法内部,它只发生了一些代码   后来试图访问加载的类型,方法或属性   部件。但我可能会在这里遗漏一些东西。

这里要注意的要点 -

  1. 执行代码时,如果代码中引用了类型且CLR检测到未加载包含该类型的程序集,则它将加载程序集。 你的观察是正确的。

  2. AssemblyResolve是在AppDomain类型中定义的事件。因此,无法从Assembly.Load(byte [])

  3. 内部引发此事件

    因此,如果您已在正在运行的appdomain上注册了AssemblyResolve事件并调用Assembly.Load(byte []),则会在当前域中加载程序集。

    现在,当调用此加载程序集中的任何类型时,可以说正好调用其他程序集中定义的另一种类型, AppDomain将调用AssemblyResolve事件来尝试加载该程序集。 / p>

答案 4 :(得分:1)

如果执行以下代码:

            var appDomain = AppDomain.CreateDomain("some domain");
            var assembly = appDomain.Load(someAssemblyBytes);

将生成一个AssemblyResolve事件。发生这种情况是因为程序集已加载到两个域中。第一个域就是您期望的第一个域-appDomain,并且不会调用该域的程序集解析器。第二个是您当前的应用程序域,因为您尝试从那里不存在的程序集访问程序集。 因此,如果您从appDomain.Load(someAssemblyName);以外的其他域执行appDomain,则将生成两次resolve事件,每个事件针对每个域。

在这种情况下,如果要省略AssemblyResolve尝试(这将失败或将程序集也加载到主域中),则必须创建从MarshalByRefObject派生的代理类,该代理类将在两者中均可见域并使用此代理之一来包含代理类。即:

    internal class AssemblyVersionProxy : MarshalByRefObject
    {
        public Version GetVersion(byte[] assemblyBytes)
        {
            var assembly = Assembly.Load(assemblyBytes);
            var version = new AssemblyName(assembly.FullName).Version;
            return version;
        }

    }

并使用它:

        public Version GetAssemblyVersion(byte[] assemblyBytes)
        {
            var appDomain = AppDomain.CreateDomain(Guid.NewGuid().ToString());

            try
            {
                var proxyType = typeof(AssemblyVersionProxy);

                var proxy = (AssemblyVersionProxy)appDomain.CreateInstanceAndUnwrap(
                    proxyType.Assembly.FullName, proxyType.FullName,
                    false, BindingFlags.CreateInstance, null,
                    new object[0], null, new object[0]
                );

                var version = proxy.GetVersion(assemblyBytes);
                return version;
            }
            finally
            {
                AppDomain.Unload(appDomain);
            }
        }