views:

212

answers:

3

I have an app which is opening a series of DLLs on the filesystem and in the GAC, and which is holding a lock on those files. How can I release these handles explicitly so I can rebuild them in Visual Studio without closing my app?

Code follows:

private void BuildTabPage_AssemblyTree(string filename, string foldername)
{
    Assembly assembly;
    try
    {
    assembly = Assembly.LoadFrom(filename);
    }
    catch (Exception ex) 
    {
    MessageBox.Show("Error loading assembly " + filename + "\n" + ex.Message);
    return;
    }
    TreeNode tRoot = BuildNode(assembly, foldername);
    tvAssemblies.Nodes.Add(tRoot);
    tvAssemblies.ExpandAll();

    txtResults.Text =
    RefsFound.ToString() + " References Located in Filesystem\r\n" +
    RefsInFramework.ToString() + " References Located in Framework\r\n" +
    RefsInGac.ToString() + " References Located in GAC\r\n" +
    RefsNotFound.ToString() + " References Not Found: \r\n\r\n";
    foreach (string s in MissingFiles)
    txtResults.Text += s + "\r\n";
}

private TreeNode BuildNode(Assembly assembly, string foldername)
{
    TreeNode tn = new TreeNode(assembly.GetName().Name);
    tn.ToolTipText = assembly.FullName + "\n" + assembly.CodeBase;
    AssemblyName[] assemblies = assembly.GetReferencedAssemblies();

    foreach (AssemblyName a in assemblies)
    {
    string filename2 = foldername + a.Name + ".dll";
    TreeNode tn2;
    if (System.IO.File.Exists(filename2))
    { // File found in folder
        RefsFound++;
        Assembly assy = Assembly.LoadFile(filename2);
        tn2 = BuildNode(assy, foldername);
    }
    else if (a.Name.StartsWith("System"))
    { // Framework assemblies not included
        RefsInFramework++;
        tn2 = new TreeNode();
        tn2.Text = a.Name;
        tn2.ForeColor = System.Drawing.Color.Green;
        tn2.ToolTipText += "\n.NET Framework File";
    }
    else
    {
        try
        {   // Find file in GAC
        Assembly assy = Assembly.Load(a);
        tn2 = new TreeNode(a.Name);
        tn.ToolTipText = assembly.FullName + "\n" + assembly.CodeBase + "\nFile detected in GAC";
        tn2.Text = "(" + filename2.Substring(filename2.LastIndexOf("\\") + 1) + ")";
        tn2.ForeColor = System.Drawing.Color.Green;
        RefsInGac++;
        }
        catch (System.IO.FileNotFoundException)
        {   // File not Found
        RefsNotFound++;
        if (!MissingFiles.Contains(a.Name))
            MissingFiles.Add(a.Name);
        tn2 = new TreeNode();
        tn2.Text = "(" + filename2.Substring(filename2.LastIndexOf("\\") + 1) + ")";
        tn2.ToolTipText = "File Not Found";
        tn2.ForeColor = System.Drawing.Color.Red;
        }
    }
    tn.Nodes.Add(tn2);
    }

    return tn;
}
A: 

I think if you setup your AppDomain to use ShadowCopy it should work:

http://blogs.msdn.com/junfeng/archive/2004/02/09/69919.aspx

This link deals with your question in great detail (if I'm understanding you correctly): http://blogs.msdn.com/jasonz/archive/2004/05/31/145105.aspx

Jon
That explains the file lock; but why doesn't it unload the file when the variable goes out of scope?
tsilb
Well 'going out of scope' has a very different meaning in C#. Look into the Dispose pattern to understand resource management. Sure C# is garbage collected but that only goes so far. You still need to understand how to manage resources. But the another reason the assembly doesn't 'unload' is because assemblies cannot be unloaded from an app domain by design.
Brian Ensink
I don't think using the ShadowCopy is a good idea. Judging from the code this app would end up copying a very large number of assemblies, including many of the standard system assemblies. Plus the program would have no way of refreshing the properties of the assemblies because subsequent calls to load the assembly would return the already loaded assembly and not the one presumably just rebuilt.
Brian Ensink
@Brian - yes - One assembly I looked at traced through 2,000 dependencies. Before I disabled framework assembly lookups, I was getting StackOverflowExceptions :)
tsilb
+1  A: 

Once you load an assembly into your process you cannot unload it. Instead you can you create an app domain for each assembly you want to load, get the assemblies properties, then close the app domain.

Brian Ensink
+6  A: 

Unfortunately, assemblies loaded into a default AppDomain will not be unloaded until the application exits. However, you can create an AppDomain and load assemblies in a created AppDomain. When the created AppDomain is unloaded, assemblies loaded in the created AppDomain will be unloaded together.

Chansik Im
I don't see a way to AppDomain.Load() an assembly by path and filename. Most of the assemblies I'm targeting won't be in the GAC; how can I load a DLL by file into a new AppDomain?
tsilb
I would rather create an AppDomain with the assembly that contains BuildTabPage_AssemblyTree() and execute the method in the created AppDomain. Once you are done, you can unload the AppDomain you created. All assemblies loaded in the AppDomain should be unloaded together. I hope this can resolve your issue.
Chansik Im