我能否使默认的AppDomain使用某些程序集的影子副本?

10

我为什么想这样做的简短解释:

我正在为Autodesk Revit Architecture 2010编写插件。我的插件代码测试非常麻烦,因为我必须在每个调试会话中重新启动Autodesk,手动加载Revit项目,单击“插件”选项卡,然后启动我的插件。这太费时间了。

我编写了第二个插件,它托管了一个IronPython解释器。这样,我就可以使用Revit提供的API进行实验。但最终,代码必须以C#方式重写并进行调试。

我认为很容易:只需从IronPython脚本中加载插件的DLL并运行它即可。这确实有效,但一旦加载,我就无法在Visual Studio中重新编译,因为DLL现在已在Revits AppDomain中加载。

我认为很容易(在StackOverflow的帮助下):只需在新的AppDomain中加载DLL。不幸的是,由于它们没有扩展MarshalByRefObject,因此无法将RevitAPI对象封送到另一个AppDomain中。

我觉得使用影子副本可能会有所发现。ASP.NET似乎正在这样做。但是,在MSDN上阅读文档时,似乎我只能在创建AppDomain时指定这一点。

我能为当前(默认)AppDomain更改这个吗?我能强制它使用来自特定目录的DLL的影子副本吗?

3个回答

6

我不知道你想做什么,但是现有的一些方法在当前AppDomain上启用ShadowCopy已经被弃用。

AppDomain.CurrentDomain.SetCachePath(@"C:\Cache");
AppDomain.CurrentDomain.SetShadowCopyPath(AppDomain.CurrentDomain.BaseDirectory);
AppDomain.CurrentDomain.SetShadowCopyFiles();

文档上说它们已经过时了,这和“废弃”是一样的吗?我会尝试一下。谢谢! - Daren Thomas
1
它对我有效。我已经修改了我的答案,并提供了一个可行的示例。将其复制并粘贴到您的Main()方法中。还要确保您的Main()方法不直接引用其他程序集,因为.NET会在调用SetShadowCopyFiles()之前加载它们。 - lubos hasko

3
有时候无法修改Main()方法的代码,比如说你正在编写一个插件并且它是由一个管理器实例化的。
在这种情况下,我建议你将程序集和pdb文件(以及AssemblyResolve事件中的依赖项)复制到临时位置,并使用Assembly.LoadFile()(而不是LoadFrom())从那里加载它们。
优点: -没有dll锁定。 -每次目标程序集重新编译时,您都可以访问新版本(这就是为什么要使用.LoadFile())。 -整个程序集在AppDomain.CurrentDomain中完全可用。
缺点: -需要复制文件。 -程序集无法卸载,这可能会不方便,因为资源没有被释放。
谢谢。
附注:此代码可以完成工作。
/// <summary>
/// Loads an assembly without locking the file
/// Note: the assemblys are loaded in current domain, so they are not unloaded by this class
/// </summary>
public class AssemblyLoader : IDisposable
{
    private string _assemblyLocation;
    private string _workingDirectory;
    private bool _resolveEventAssigned = false;

    /// <summary>
    /// Creates a copy in a new temp directory and loads the copied assembly and pdb (if existent) and the same for referenced ones. 
    /// Does not lock the given assembly nor pdb and always returns new assembly if recopiled.
    /// Note: uses Assembly.LoadFile()
    /// </summary>
    /// <param name="assemblyOriginalPath"></param>
    /// <returns></returns>
    public Assembly LoadFileCopy(string assemblyLocation)
    {
        lock (this)
        {
            _assemblyLocation = assemblyLocation;

            if (!_resolveEventAssigned)
            {
                _resolveEventAssigned = true;

                AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(AssemblyFileCopyResolveEvent);
            }

            //  Create new temp directory
            _workingDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
            Directory.CreateDirectory(_workingDirectory);

            //  Generate copy
            string assemblyCopyPath = Path.Combine(_workingDirectory, Path.GetFileName(_assemblyLocation));
            System.IO.File.Copy(_assemblyLocation, assemblyCopyPath, true);

            //  Generate copy of referenced assembly debug info (if existent)
            string assemblyPdbPath = _assemblyLocation.Replace(".dll", ".pdb");
            if (File.Exists(assemblyPdbPath))
            {
                string assemblyPdbCopyPath = Path.Combine(_workingDirectory, Path.GetFileName(assemblyPdbPath));
                System.IO.File.Copy(assemblyPdbPath, assemblyPdbCopyPath, true);
            }

            //  Use LoadFile and not LoadFrom. LoadFile allows to load multiple copies of the same assembly
            return Assembly.LoadFile(assemblyCopyPath);
        }
    }

    /// <summary>
    /// Creates a new copy of the assembly to resolve and loads it
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    /// <returns></returns>
    private Assembly AssemblyFileCopyResolveEvent(object sender, ResolveEventArgs args)
    {
        string referencedAssemblyFileNameWithoutExtension = System.IO.Path.GetFileName(args.Name.Split(',')[0]);

        //  Generate copy of referenced assembly
        string referencedAssemblyPath = Path.Combine(Path.GetDirectoryName(_assemblyLocation), referencedAssemblyFileNameWithoutExtension + ".dll");
        string referencedAssemblyCopyPath = Path.Combine(Path.GetDirectoryName(args.RequestingAssembly.Location), referencedAssemblyFileNameWithoutExtension + ".dll");
        System.IO.File.Copy(referencedAssemblyPath, referencedAssemblyCopyPath, true);

        //  Generate copy of referenced assembly debug info (if existent)
        string referencedAssemblyPdbPath = Path.Combine(Path.GetDirectoryName(_assemblyLocation), referencedAssemblyFileNameWithoutExtension + ".pdb");
        if (File.Exists(referencedAssemblyPdbPath))
        {
            string referencedAssemblyPdbCopyPath = Path.Combine(Path.GetDirectoryName(args.RequestingAssembly.Location), referencedAssemblyFileNameWithoutExtension + ".pdb");
            System.IO.File.Copy(referencedAssemblyPath, referencedAssemblyCopyPath, true);
        }

        //  Use LoadFile and not LoadFrom. LoadFile allows to load multiple copies of the same assembly
        return Assembly.LoadFile(referencedAssemblyCopyPath);
    }


    public void Dispose()
    {
        Dispose(true);
    }

    private void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (_resolveEventAssigned)
            {
                AppDomain.CurrentDomain.AssemblyResolve -= new ResolveEventHandler(AssemblyFileCopyResolveEvent);

                _resolveEventAssigned = false;
            }
        }
    }
}

谢谢。我在这里基本上使用了这种技术:http://code.google.com/p/revitpythonshell/wiki/FeaturedScriptLoadplugin - Daren Thomas
你所提到的技术存在一个问题,即如果重新编译了主程序集引用的一个程序集,并且你已经加载了它,那么这个被引用的程序集不会被重新加载(更新)。例如,你加载了一个名为A的程序集,它依赖于另一个名为B的程序集。 - Eu_UY
1
  1. 你加载了一个名为A的程序集,其中包含使用位于不同程序集B中的另一种类型B1的类型A1(程序集A引用程序集B)。
  2. 在不关闭应用程序域(不关闭应用程序)的情况下,您向程序集B添加了一个新类型B2,并从程序集A中的类型A1引用B2。
  3. 再次加载:此时找不到新类型B2,因为第二次.NET不会再次解析程序集B(它只在步骤1中解析了一次),因此会出现错误。 不知道我是否表达清楚。 问候,
- Eu_UY

1
现在有一个Revit插件,可以动态加载/卸载其他Revit插件,这样您就可以在不必重新打开Revit项目的情况下进行更改、重新编译和测试。我在Building Coder blog上找到了它。它随同Revit SDK一起提供。

谢谢,我知道博客和AddInManager。这真的很有效!(除了XAML WPF表单似乎不行,但这是另一件事,我不会去追求FTM) - Daren Thomas

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