调用Assembly.Load(byte())时,会触发AssemblyResolve事件。

5
我有一个WPF项目,它正在调用另一个项目中使用的dll。这是一个依赖关系的混乱,我一直在使用这里的技术: http://www.digitallycreated.net/Blog/61/combining-multiple-assemblies-into-a-single-exe-for-a-wpf-application将依赖项嵌入到单个可执行文件中。
现在,当我调用其中一个依赖项中的特定方法时,我遇到了AssemblyResolve事件。我的OnResolveAssembly事件运行,它将该程序集作为嵌入资源找到(很酷!),并执行“return Assembly.Load(assembyRawBytes)”。如果我此时按F11(在OnResolveAssembly开头处设置断点),我会得到另一个对同一事件的调用。它也是针对同一程序集的(args.Name相同)。
如果我让它运行,我就会遇到堆栈溢出,因为我似乎永远无法逃脱这个递归事件调用。
MSDN文档并没有真正说明Assembly.Load何时会失败,除了FileNotFoundException或BadImageFormatException。
我尝试在调用Assembly.Load之前取消挂接OnResolveAssembly,但是我的应用程序突然死亡了,即使在VS下也只是消失了。
我可能违反了几个规则,但是欢迎提供一些问题查找的起点。
我要开始搜索有问题的DLL,看看是否有关于它出现问题的线索(也许它是混合程序集?)。
这是我的OnResolveAssembly处理程序:
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];
    }
}

目前,我通过遍历所有资源并尝试使用Assembly.Load加载它们,然后将它们存储在字典中以便检索(在OnResolveAssembly事件期间)来解决这个问题:

[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;
}

现在它似乎工作正常了(“失败”的组件在我的第二个片段中可以正常加载),但我很想知道为什么它在第一个片段中不起作用。


添加了我的代码和我发现的解决方法。 - Jonathan Yee
@JonathanYeeпјҢдҪ зңҹзҡ„еҸҜд»ҘеңЁи°ғз”ЁAssembly.Load(byte[])ж—¶и§ҰеҸ‘AssemblyResolveеҗ—пјҹиҝҷжӯЈжҳҜжҲ‘еңЁиҝҷйҮҢеҜ»жүҫзҡ„жғ…еҶөпјҢдҪҶжҲ‘ж— жі•жһ„е»әиҝҷж ·зҡ„зӨәдҫӢгҖӮдҪ иғҪжҸҗдҫӣжӣҙеӨҡз»ҶиҠӮеҗ—пјҹи°ўи°ўгҖӮ - Vladimir Reshetnikov
1个回答

4
从字节流中加载程序集是容易导致依赖问题的地方,也就是所谓的“.dll地狱”(当依赖过于复杂或过多时会出现这种情况)。问题在于,虽然你将 dll 加载到了 AppDomain 中,但并没有自动解析它,当你需要它以及对应的依赖类型时会出现问题。
我在这里评论了这个问题:AssemblyResolve Does not fire 长话短说,程序集被加载到AppDomains中的不同“上下文”中。使用 Load(byte[]) 的上下文不能自动解析程序集。
解决方法是跟踪已经加载的程序集,并返回已经加载的程序集,而不是再次加载它。这种方法的起点在我的答案中:Need to hookup AssemblyResolve event when DisallowApplicationBaseProbing = true 不过,我认为你的解决方法是正确的。
顺便提一句,重复加载程序集会得到相同但不兼容的类型。曾经将来自 MyAssembly 的对象 MyType 强制转换成相同程序集中的 MyType,并得到 null 吗?
那就是热烈欢迎你来到“.dll地狱”。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接