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

Jon*_*Yee 5 .net c#

所以我有一个WPF项目正在拉动我的工作中另一个项目使用的dll.这是依赖乱七八糟的,我一直在使用的技术在这里:http://www.digitallycreated.net/Blog/61/combining-multiple-assemblies-into-a-single-exe-for-a-wpf-application将依赖项嵌入到单个可执行文件中.

现在,当我在其中一个依赖项中调用特定方法时,我点击了AssemblyResolve事件.我的OnResolveAssembly事件运行,它将程序集作为嵌入式资源(很酷!),然后"返回Assembly.Load(assembyRawBytes)".如果我此时点击F11(在OnResolveAssembly的开头有一个断点),我会再次调用同一个事件.这也是同一个程序集(args.Name是相同的).

如果我让这次运行我遇到堆栈溢出,因为我似乎永远不会逃避这个递归事件调用.

除了FileNotFoundException或BadImageFormatException之外,MSDN文档并没有真正说明Assembly.Load何时会失败.

在我调用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];
    }
}
Run Code Online (Sandbox Code Playgroud)

目前,我通过遍历所有资源并尝试使用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;
}
Run Code Online (Sandbox Code Playgroud)

它现在似乎工作正常("失败"程序集将在我的第二个片段中正常加载),但我有兴趣了解它为什么在第一个不起作用.

No *_*wer 4

从 byte[] 加载程序集是最终进入 .dll 地狱(您会遇到太多/复杂依赖项的地方)的好方法。这里的问题是,虽然您将 dll 加载到 AppDomain,但当您再次需要依赖类型时,它不会自动解析。我在这里评论了这个问题:AssemblyResolve does not fire

长话短说,程序集被加载到 AppDomain 内的不同“上下文”中。Load(byte[]) 使用的上下文不会自动解析程序集。

解决方案是跟踪已加载的程序集并返回已加载的程序集,而不是再次加载它。在我的回答中,这种方法有一个起点: Need to hookup AssemblyResolve event when DisallowApplicationBaseProbing = true

但我认为您的解决方法是正确的。

顺便提一句。加载程序集两次是获得相同但不兼容类型的一种方法。曾经将 MyType 对象从 MyAssembly 转换为同一程序集中的 MyType 并得到 null 吗?

这是一句温暖的“欢迎来到.dll 地狱”。