Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

Reflection on Problem Assemblies


:P
On this page:

Markus bugged me this morning about a problem with an import of a couple of assemblies using Html Help Builder. Html Help Builder can import assemblies and quickly create full documentation from the assemblies to HTML, CHM and Word. It supports .NET 2.0 and can deal with generic types although there was a small bug with one of the template sets that caused the generic parameters not to render properly.

 

While working on this Markus sent me his files to check out the problem and it turns out that I couldn’t even get the assemblies to load locally. Markus (or rather one of his employees Mike) was complaining about the import failing and sure enough once I had the files here locally I saw the same failures.

 

It turns out the failure is due to some sort of referencing problem in the assembly for one of the dependent assemblies and Html Help Builder is choking while trying to get the types loaded. I use code like this to get the initial list of types in the specified assembly:

 

public int GetAllObjects(bool DontParseMethods)

{

    this.lError = false;

 

    Assembly assembly;

    try

    {

        // assembly = Assembly.LoadFrom(this.cFilename);

        AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve +=

                new ResolveEventHandler(CurrentDomain_ReflectionOnlyAssemblyResolve);

        assembly = System.Reflection.Assembly.ReflectionOnlyLoadFrom(this.cFilename);

    }

    catch (Exception ex)

    {

        this.lError = true;

        this.cErrorMsg = "Unable to load assembly.\r\n\r\nRaw Error:\r\n" + ex.Message;

        return 0;

    }

 

    try

    {

        this.aXObjects = assembly.GetTypes();               

    }

    catch(Exception ex)

    {

        this.lError = true;

        this.cErrorMsg = "Unable to load types from assembly.Typically this is caused by missing dependent assemblies or assembly version mismatches.\r\n\r\nRaw Error:\r\n" + ex.Message;

        return 0;

    }

 

    … go on parsing

}

 

(excuse the bad naming conventions – this code is ancient and was one of my first .NET endeavours <s>)

 

The code is failing on the call to GetTypes() which returns an collection of types that are contained in the assembly. And the aXObjects collection ends up null with an exception thrown into the exception block.

Both Markus and Mike insisted that the assemblies are fine, but I kept getting loader exceptions on one of the related assemblies. Finally I created a WinForms project and loaded the assemblies in a page to see if that work and sure enough that code bombed *at runtime* trying to load the same assembly with the exact same error message. Ok, so the failure makes sense. Garbage in, garbage out...

 

Reflector and the VS.NET Assembly browser can though

However I noticed that if I’m in VS.NET and click on the assembly I can get the assembly browser to show me the assembly and all of its types and details. Same with Reflector which can also browse and display the assembly types, and member signatures and even the decompiled code.

 

So what are they doing that I’m not? <g> How can you get a list of types in the assemblies without using GetTypes()? Are these tools going straight to the IL or the manifest to get the data out bypassing Reflection altogether?

 

ReflectionOnly Assembly parsing – hopeful but No Cigar

In the meantime, while I was looking this over, I noticed that .NET 2.0 has a new feature IsReflectionOnly (here’s some more detailed info on this), which is supposed to be more lightweight and doesn’t actually require loading the types.

 

Originally I simply did:

 

assembly = Assembly.LoadFrom(this.cFilename);

 

but ended up changing this to ReflectionOnly loading in hopes that this might resolve the issue with the type loading:

 

AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve +=

new ResolveEventHandler(CurrentDomain_ReflectionOnlyAssemblyResolve);

 

assembly = System.Reflection.Assembly.ReflectionOnlyLoadFrom(this.cFilename);

 

You then need to implement a fairly generic resolver method like so:

 

Assembly CurrentDomain_ReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args)

{

    return System.Reflection.Assembly.ReflectionOnlyLoad(args.Name);   

}

 

This delegate is fired whenever a dependent type is loaded and it's meant to give your chance to do any fix up to ensure that the assembly can be loaded properly. The code above is pretty generic and it works fine, but it looks like implementing this handler is required - without it any loading of dependent assemblies will fail. I suspect the .NET team wanted to make it explicit that external types need to be loaded since ReflectionOnly gives the impression of being a low impact operation.

 

Testing with a number of my fairly complex framework assemblies I didn’t see any functionality problems but I’ll have to do some more testing. I suspect performance should be improved but it’s hard to tell – I don’t have assemblies that are large enough to make enough of a difference.

 

So anybody know if there’s a different way to parse types through the framework that I might be missing?

Posted in .NET  

The Voices of Reason


 

Rick Strahl
December 22, 2006

# re: Reflection on Problem Assemblies

I guess I was a little overeager here with the ReflectionOnly code. For some reason the code above using only the ReflectionOnlyLoad() code works perfect in Debug mode, but not at runtime.

Actually I don't understand why it works in the debugger either because ReflectionOnlyLoad will load an assembly by its full name - if the assembly is not registered in the GAC that name is not going to resolve and fail.

This reasonably easy to fix by trapping the load failure by name and then loading from file:

Assembly CurrentDomain_ReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args)
{
    try
    {
        Assembly assembly = System.Reflection.Assembly.ReflectionOnlyLoad(args.Name);
        if (assembly != null)
            return assembly;
    }
    catch { ;}
    
    // *** Try to load by filename - split out the filename of the full assembly name
    // *** and append the base path of the original assembly (ie. look in the same dir)
    // *** NOTE: this doesn't account for special search paths but then that never
    //           worked before either.
    string[] Parts = args.Name.Split(',');
    string File = Path.GetDirectoryName(this.cFilename) + "\\" + Parts[0].Trim() + ".dll";

    return System.Reflection.Assembly.ReflectionOnlyLoadFrom(File);
}


This seems to work now with both, but I'm not clear what the difference is between the two environments. I don't even see the assembly loading in the debugger either - it's being loaded in a separate AppDomain, but still it should be showing in the output window in the debugger and it's not. The parsing works on it though...

Bizarre.

Rick Strahl
December 23, 2006

# re: Reflection on Problem Assemblies

It also looks like the above updated related assembly loading code fixes my load problem with the problem assemblies. What apparently happens is that .NET'S default assembly loading try to load with the strong name and if the versioning is off that will fail. With the code above the strong name load still fails, but it falls through and then can try to load from file which then in turn works.

Kevin Dente
December 23, 2006

# re: Reflection on Problem Assemblies

>Are these tools going straight to the IL

In Reflector's case, yes, it's going straight to the IL.

# DotNetSlackers: Reflection on Problem Assemblies


Rick Strahl's Web Log
June 23, 2007

# Rick Strahl's Web Log


Eric Schneider
August 11, 2008

# re: Reflection on Problem Assemblies

Rick is there anything new on this? I seem to have a case where its still does not load dependencies.

In my case I'm trying to use reflection to perform an API diff between two version of the same DLL. This is a free developer tool I maintain.

Thanks,
Schneider

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024