Reflection Only Assembly Loading

.Net Framework 2.0 introduces several new assembly loading APIs.

 

Assembly.ReflectionOnlyLoadFrom(String assemblyFile)

Assembly.ReflectionOnlyLoad(byte[] rawAssembly)

Assembly.ReflectionOnlyLoad(String assemblyName)

 

And Assembly class now has ReflectionOnly property to tell you if an assembly is loaded as reflection only or not.

 

As you can infer from the name of those APIs, Assembly returned from those APIs can only be used for reflection. Any intention to create an instance of types in those assemblies will result in InvalidOperationException.

 

There are a few scenarios those APIs address:

  1. Being able to confidently inspection any given assembly.
  2. Being able to inspect exactly the given bits.

 

The normal assembly loading APIs can’t satisfy those scenarios, for the following reasons:

  1. Assembly may be delay-signed.
  2. Assembly may be prohibited by CAS policy.
  3. Assembly may have a different processor architecture that can not be loaded in the running process.
  4. Code in the target assembly may be executed during assembly loading. (For example, module constructor)
  5. Binding Policy may be applied, and you may not get the exact same assembly as you specified.

 

For those reasons, Reflection Only Assembly Load APIs will:

  1. Skip assembly strong name verifications
  2. Skip CAS policy check
  3. Skip processor architecture loading rule
  4. Not execute any code in the target assembly, including module constructor
  5. Not apply any binding policy.

 

Reflection Only Assembly Load APIs behave quite differently from the execution assembly load APIs.

 

  1. There is one inspection context per AppDomain. All the reflection only assemblies live in that context.
  2. Reflection only assemblies will be unloaded only when the AppDomain is unloaded, same as execution assemblies.
  3. CLR will not probe for dependencies. The user of those APIs is responsible to provide all the necessary assemblies using Reflection Only Assembly Load APIs. The reason of this decision is that by probing dependencies, CLR may return a different assembly than the one you want. And it will be very difficult to overwrite CLR’s decision. However, if an assembly with the same idemtity is already loaded in the inspection context, CLR will use it to satisfy the dependency.
  4. All reflection only assemblies will be cached. Only one assembly per identity is allowed in the inspection context.  It does not matter how that assembly was loaded – the first one loaded always wins.  This means:
    1. If someone attempts to load a second one using ReflectionOnlyLoadFrom(), it will fail with a FileLoadException. The decision is based on 3). If multiple assemblies are allowed to be loaded in inspection context, CLR will not know which assembly to be used when looking for dependencies.
    2. If ReflectionOnlyLoad() is called on an assembly when another assembly with that identity was already loaded, the already-loaded assembly will be returned.
  5. ReflectionOnlyAssemblyResolve event will be fired, instead of AssemblyResolve event. 
  6. You have to return a reflection only assembly in ReflectionOnlyAssemblyResolve event handler.
  7. CLR will not return instances of custom attributes, since that means executing code of the target assembly. Instead, a new class CustomAttributeData will be used to return information about the custom attributes.

<Update  date="2005/09/18">

In .Net framework 2.0 ReflectionOnly APIs cannot be used to reflect other mscorlib.dll (for example, v1.0/v1.1/NetCF). The reason is that mscorlib.dll is being special cased by CLR Loader all over the code base. It will be a major work to make it work. This may be possible in future version of .Net framework.

</Update>

 

So how will you use the Reflection Only Assembly Load APIs?

  1. You will likely start with Assembly.ReflectionOnlyLoadFrom
  2. You will need to subscribe to ReflectionOnlyAssemblyResolve
  3. You probably want to check if the dependency exists in the same directory as the target assembly (or any other directories defined by you). If no assembly matches there, you will resort to Assembly.ReflectionOnlyLoad.

 

I recommend using Assembly.ReflectionOnlyLoadFrom whenever possible, and only use Assembly.ReflectionOnlyLoad for assemblies in GAC. The reason is the same as CLR not probing dependencies --- when CLR starts probing application base, it may return something you don’t want.

 

Here is a very simple sample application showing using Reflection Only Assembly Load APIs.

 

C:\ >more test.cs

using System;

using System.IO;

using System.Reflection;

public class ReflectionOnlyLoadTest

{

    public ReflectionOnlyLoadTest(String rootAssembly)

    {

        m_rootAssembly = rootAssembly;

    }

    public static void Main(String[] args)

    {

       if (args.Length != 1) {

            Console.WriteLine("Usage: Test assemblyPath");

            return;

        }

        try {

            ReflectionOnlyLoadTest rolt = new ReflectionOnlyLoadTest(args[0]);

            rolt.Run();

        }

        catch (Exception e) {

Console.WriteLine("Exception: {0}!!!", e.Message);

        }

    }

    internal void Run()

    {

        AppDomain curDomain = AppDomain.CurrentDomain;

        curDomain.ReflectionOnlyPreBindAssemblyResolve += new ResolveEventHandler(MyReflectionOnlyResolveEventHandler);

        Assembly asm = Assembly.ReflectionOnlyLoadFrom(m_rootAssembly);

        // force loading all the dependencies

        Type[] types = asm.GetTypes();

        // show reflection only assemblies in current appdomain

        Console.WriteLine("------------- Inspection Context --------------");

        foreach (Assembly a in curDomain.ReflectionOnlyGetAssemblies())

        {

            Console.WriteLine("Assembly Location: {0}", a.Location);

            Console.WriteLine("Assembly Name: {0}", a.FullName);

            Console.WriteLine();

        }

    }

    private Assembly MyReflectionOnlyResolveEventHandler(object sender, ResolveEventArgs args)

    {

        AssemblyName name = new AssemblyName(args.Name);

        String asmToCheck = Path.GetDirectoryName(m_rootAssembly) + "\\" + name.Name + ".dll";

        if (File.Exists(asmToCheck)) {

            return Assembly.ReflectionOnlyLoadFrom(asmToCheck);

        }

        return Assembly.ReflectionOnlyLoad(args.Name);

    }

    private String m_rootAssembly;

}

 

C:\ >test c:\windows\Microsoft.NET\Framework\v2.0.40607\system.dll

------------- Inspection Context --------------

Assembly Location: c:\windows\Microsoft.NET\Framework\v2.0.40607\system.dll

Assembly Name: System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5

c561934e089

Assembly Location: c:\windows\Microsoft.NET\Framework\v2.0.40607\Microsoft.Vsa.d

ll

Assembly Name: Microsoft.Vsa, Version=8.0.1200.0, Culture=neutral, PublicKeyToke

n=b03f5f7f11d50a3a

Assembly Location: c:\windows\Microsoft.NET\Framework\v2.0.40607\System.Xml.dll

Assembly Name: System.Xml, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b

77a5c561934e089