Weird FileLoadException when loading assembly referenced by WPF project using Assembly.ReflectionOnlyLoadFrom

孤街醉人 提交于 2019-12-10 18:16:30

问题


I have a custom MSBuild task that peeks inside an assembly to get some attribute meta-data.

Assembly assembly = Assembly.ReflectionOnlyLoadFrom(AssemblyFile)

This is used by our automated build/release process, and has been working perfectly against assemblies used by and referenced from class libraries, console apps and web projects. The MSBuild task is called after another MSBuild process has compiled the projects.

It stopped working yesterday when I added a WPF project which referenced this particular assembly - a .NET 3.5 class library.

System.IO.FileLoadException: API restriction: The assembly 'file:///bogus.dll' has already loaded from a different location. 
It cannot be loaded from a new location within the same appdomain. 
at System.Reflection.Assembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection) 
at System.Reflection.Assembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection) 
at System.Reflection.Assembly.InternalLoad(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection) 
at System.Reflection.Assembly.InternalLoadFrom(String assemblyFile, Evidence securityEvidence, Byte[] hashValue, AssemblyHashAlgorithm hashAlgorithm, Boolean forIntrospection, StackCrawlMark& stackMark) 
at System.Reflection.Assembly.ReflectionOnlyLoadFrom(String assemblyFile) 
at RadicaLogic.MSBuild.Tasks.GetAssemblyAttribute.Execute() 
at Microsoft.Build.BuildEngine.TaskEngine.ExecuteInstantiatedTask(EngineProxy engineProxy, ItemBucket bucket, TaskExecutionMode howToExecuteTask, ITask task, Boolean& taskResult)

I know it's WPF related because no exception is thrown if I change the AssemblyFile to point to another assembly in the same solution which isn't referenced by the WPF project.

The exception message mentions that

... already loaded from a different location.

It cannot be loaded from a new location within the same appdomain.

Note the part about the same appdomain.

So I modified the code to catch this particular exception and look in CurrentDomain:

Assembly assembly = null;
try
{
    assembly = Assembly.ReflectionOnlyLoadFrom(AssemblyFile);
}
catch (FileLoadException)
{
    List<string> searched = new List<string>();
    foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
    {
        if (Path.GetFileName(asm.CodeBase).Equals(Path.GetFileName(AssemblyFile), 
            StringComparison.OrdinalIgnoreCase))
        {
            message = string.Format("Found assembly {0} in current domain", 
                asm.CodeBase);
            MSBuildHelper.Log(this, message, MessageImportance.High);
            assembly = asm;
            break;
        }
        else
        {
            searched.Add(Path.GetFileName(asm.CodeBase));
        }
    }
    if (assembly == null)
    {
        message = string.Format(
            "Unable to find {0} after looking in current domain assemblies {1}",
            Path.GetFileName(AssemblyFile), string.Join(", ", searched.ToArray()));
        MSBuildHelper.Log(this, message, MessageImportance.High);                    
    }
}

It goes without saying that the assembly in question wasn't in the current domain (which may make sense, since another MSBuild process is spawned which does the compilation), so assuming the error message is true, how do I go about figuring out where it lives? It's confusing because the error message to me suggests that it should be CurrentDomain.

Or can someone with more WPF experience explain why this assembly is still floating around in an app domain after a successful build?

Here's another question from someone else who has hit this exception.


回答1:


My solution was to go open source :) Using Cecil to get Attribute from AssemblyFile:

bool found = false;
string value = string.Empty;
Type attributeType = Type.GetType(Attribute);
AssemblyDefinition assembly = AssemblyFactory.GetAssembly(AssemblyFile);
foreach (CustomAttribute attribute in assembly.CustomAttributes)
{
    if (attribute.Constructor.DeclaringType.Name == attributeType.Name)
    {
        value = attribute.ConstructorParameters[0].ToString();
        found = true;
    }
}

Update for Jays comment:

Typically I use: AssemblyFileVersion as the Attribute property value, and have the setter logic fill in the missing pieces :)

Here's how the property is defined:

string attribute;
[Required]
public string Attribute
{
    get { return attribute; }
    set
    {
        string tempValue = value;
        if (!tempValue.StartsWith("System.Reflection."))
        {
            tempValue = "System.Reflection." + tempValue;
        }
        if (!value.EndsWith("Attribute"))
        {
            tempValue += "Attribute";
        }
        attribute = tempValue;
    }
}

Unit test showing Attribute property value sans required prefix or suffix:

[Test]
public void Execute_WithoutSystemReflectionPrefixOrAttributeSuffix_ReturnsExpectedResult()
{
    string version = getAssemblyFileVersion();
    Assert.IsNotNull(version, "Expected AssemblyFileVersionAttribute to contain value");

    task.AssemblyFile = assemblyFile;
    task.Attribute = "AssemblyFileVersion";
    task.Value = "Bogus";

    result = task.Execute();

    Assert.IsTrue(result, "Expected execute to pass on valid assembly and attribute name");

    Assert.AreEqual(task.Value, version,
        "Expected task value to match assembly file version attribute value");
}


来源:https://stackoverflow.com/questions/1535549/weird-fileloadexception-when-loading-assembly-referenced-by-wpf-project-using-as

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!