Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AssemblyResolve event fires when calling Assembly.Load(byte())

Tags:

c#

.net

So I have a WPF project that is pulling in dlls that are used by another project here at my job. It's a mess of dependencies, I've been using the technique here: http://www.digitallycreated.net/Blog/61/combining-multiple-assemblies-into-a-single-exe-for-a-wpf-application to embed the dependencies into a single executable.

Now, when I'm calling a specific method inside one of the dependencies, I hit the AssemblyResolve event. My OnResolveAssembly event runs, it finds the assembly as an embedded resource (cool!), and does "return Assembly.Load(assembyRawBytes)". If I hit F11 at this point (with a breakpoint at the beginning of OnResolveAssembly), I get another call into the same event. It's for the same assembly too (args.Name is the same).

If I let this run I hit a stack overflow, since I can never seem to escape this recursive event calling.

The MSDN docs don't really say when Assembly.Load can fail, except with a FileNotFoundException or BadImageFormatException.

I've tried unhooking the OnResolveAssembly at the moment before I call Assembly.Load, but then my application dies a mysterious death, even under VS it just goes poof.

I'm probably breaking several rules here, but some ideas of where to start looking for problems would be welcome.

I'm going to start poking around in the problematic DLL to see if there are hints about what is wrong with it (maybe it's a mixed assembly?).

Here's my OnResolveAssembly handler:

private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
{
    Assembly executingAssembly = Assembly.GetExecutingAssembly();
    AssemblyName assemblyName = new AssemblyName(args.Name);

    string path = assemblyName.Name + ".dll";

    if (assemblyName.CultureInfo.Equals(System.Globalization.CultureInfo.InvariantCulture) == false)
    {
        path = String.Format(@"{0}\{1}", assemblyName.CultureInfo, path);
    }
    using (Stream stream = executingAssembly.GetManifestResourceStream(path))
    {
        if (stream == null)
            return null;

        byte[] assemblyRawBytes = new byte[stream.Length];
        stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
        assemblyDictionary.Add(assemblyName.Name, Assembly.Load(assemblyRawBytes));
        return assemblyDictionary[assemblyName.Name];
    }
}

For the time being, I've resolved it by iterating through all of my resources and attempting Assembly.Load on them, and storing them in a dictionary for retrieval (during the OnResolveAssembly event):

[STAThread]
public static void Main()
{
    AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
    Assembly executingAssembly = Assembly.GetExecutingAssembly();
    string[] resources = executingAssembly.GetManifestResourceNames();
    foreach (string resource in resources)
    {
        if (resource.EndsWith(".dll"))
        {
            using (Stream stream = executingAssembly.GetManifestResourceStream(resource))
            {
                if (stream == null)
                    continue;

                byte[] assemblyRawBytes = new byte[stream.Length];
                stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
                try
                {
                    assemblyDictionary.Add(resource, Assembly.Load(assemblyRawBytes));
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.Print("Failed to load: " + resource + " Exception: " + ex.Message);
                }
            }
        }
    }
    App.Main();
}

private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
{
    Assembly executingAssembly = Assembly.GetExecutingAssembly();
    AssemblyName assemblyName = new AssemblyName(args.Name);

    string path = assemblyName.Name + ".dll";

    if (assemblyDictionary.ContainsKey(path))
    {
        return assemblyDictionary[path];
    }
    return null;
}

It seems be working fine now (the "failing" assembly will load fine in my second snippet), but I'd be interested to learn why it doesn't work in the first.

like image 543
Jonathan Yee Avatar asked Oct 08 '22 11:10

Jonathan Yee


1 Answers

Loading an assembly from byte[] is a good way to end up in .dll hell (the place you go for too many/complex dependencies). Problem here is that although you loaded the dll to an AppDomain it is not automatically resolved, when you need it again for dependent types. I commented on this problem here: AssemblyResolve Does not fire

Long story short, Assemblies are loaded into different "contexts" inside of AppDomains. The context used by Load(byte[]) does not resolve Assemblies automatically.

The solution is keeping track of the loaded assemblies and returning the already loaded assembly instead of loading it a second time. There is a starting point to this approach in my answer to: Need to hookup AssemblyResolve event when DisallowApplicationBaseProbing = true

But I think you got it right with your workaround.

BTW. Loading an assembly twice is a way to get identical but incompatible types. Ever cast an object of MyType from MyAssembly into MyType from the very same assembly and got null?

That's a warm "Welcome to .dll hell".

like image 179
No answer Avatar answered Oct 12 '22 02:10

No answer