Can a call to Assembly.Load(byte[]) raise the AppDomain.AssemblyResolve event?

前端 未结 5 1969
迷失自我
迷失自我 2020-12-25 14:05

Suppose I have a handler for AppDomain.AssemblyResolve event, and in the handler I construct a byte array and invoke the method Assembly.Load(byte[]). Can this method itself

相关标签:
5条回答
  • 2020-12-25 14:10

    To my knowledge Assembly.Load or loading assembly by other means does not execute any constructors that can be generated by the C# compiler (including static constructors). As result you're not going to get reentrancy to AssemblyResolve on commonly found assemblies.

    As you've mentioned in the question, module initializers are not executed during the Load call. Covered in list of guarantees in CLI spec - excerpt can be found in Module Initializers by Junfeng Zhang.

    B. The module’s initializer method is executed at, or sometime before, first access to any types, methods, or data defined in the module

    There are related SO questions usually discussing "run code before any type constructors" like Initialize library on Assembly load. Note that .Net: Running code when assembly is loaded has an answer by Marc Gravell that states it may not be possible due to security constraints.

    0 讨论(0)
  • 2020-12-25 14:15

    A module initializer is the only trouble-maker I can think of. A simple example of one in 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;
    

    The Init() constructor is invoked when the module is loaded through the module initializer, right after it initializes the C runtime. You are off the hook on this kind of code though in your specific case, Assembly.Load(byte[]) is not capable of loading mixed-mode assemblies.

    That is not otherwise a restriction induced by module initializers. They were added in CLR v2.0 with the specific intention to a similar jobs like this, getting a language runtime to initialize itself before it starts executing any managed code. The odds that you run into such code should be very, very low. You'll know it when you see it :)

    0 讨论(0)
  • 2020-12-25 14:23

    The MSDN Documentation states:

    How the AssemblyResolve Event Works:

    When you register a handler for the AssemblyResolve event, the handler is invoked whenever the runtime fails to bind to an assembly by name. For example, calling the following methods from user code can cause the AssemblyResolve event to be raised:

    1. An AppDomain.Load method overload or Assembly.Load method overload whose first argument is a string that represents the display name of the assembly to load (that is, the string returned by the Assembly.FullName property).

    2. An AppDomain.Load method overload or Assembly.Load method overload whose first argument is an AssemblyName object that identifies the assembly to load.

    It doesn't mention the overload receiving a byte[]. I looked up in the reference source and it seems that the Load which accepts a string overload internally calls a method named InternalLoad, which before invoking the native LoadImage calls CreateAssemblyName and its documentation states:

    Creates AssemblyName. Fills assembly if AssemblyResolve event has been raised.

    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;
    

    The byte[] overload doesn't have this, it simply calls the native nLoadImage inside QCall.dll. This may explain why ResolveEvent isn't invoked.

    0 讨论(0)
  • 2020-12-25 14:25

    If you execute following code:

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

    an AssemblyResolve event will be generated. This happens because the assembly is loaded into BOTH domains. First domain is the one you expect it to be - appDomain and the assembly resolver for that domain is not called. Second is your current app domain, because you try to access the assembly from it while it's not present there. Therefore if you execute appDomain.Load(someAssemblyName); from domain other than appDomain the resolve event will be generated twice, each for each domain.

    In such case, if you want to omit the AssemblyResolve attempt (which will fail or will load assembly also into main domain) you have to create proxy class that derives from MarshalByRefObject in assembly that will be visible at both domains and use this proxy one to contain proxied class. I.e.:

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

    and use it:

            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);
                }
            }
    
    0 讨论(0)
  • 2020-12-25 14:26

    You mentioned -

    In no case I observed the AssemblyResolve event to be raised from inside the Assembly.Load(byte[]) method, it only happened if some code later tried to access types, methods or attributes in the loaded assembly. But I can be missing something here.

    The points to note here -

    1. While executing code, if a type is referenced in code and the CLR detects that the assembly containing the type is not loaded, then it will load the assembly. Your observation is correct here.

    2. AssemblyResolve is an event defined in AppDomain type. So this event cannot be raised from inside the Assembly.Load(byte[])

    Hence if you have already registered with AssemblyResolve event on the running appdomain and call Assembly.Load(byte[]) it loads the assembly in the current domain.

    Now when any type from this loaded assembly is invoked which lets say happens to be calling another type defined in some other assembly, the AppDomain will call the AssemblyResolve event to try and load that assembly.

    0 讨论(0)
提交回复
热议问题