Могу ли я заставить AppDomain по умолчанию использовать теневые копии определенных сборок?

Краткое объяснение, почему я хочу это сделать:

Я занят написанием подключаемого модуля для Autodesk Revit Architecture 2010. Тестирование кода моего подключаемого модуля чрезвычайно утомительно, так как мне приходится перезапускать Autodesk для каждого сеанса отладки, вручную загружать проект Revit, щелкать вкладку «Надстройки», а затем запускать мой подключаемый модуль. Это просто занимает слишком много времени.

Я написал второй плагин, в котором размещается интерпретатор IronPython. Таким образом, я могу поиграть с API, предоставляемым Revit. Но в конечном итоге код приходится переписывать на C# и отлаживать.

Легко, подумал я: просто загрузите DLL-плагины из скрипта IronPython и потренируйтесь. Это работает, но после загрузки я не могу перекомпилировать в Visual Studio, так как DLL теперь загружается в Revits AppDomain.

Легко, подумал я (с небольшой помощью StackOverflow): просто загрузите DLL в новый AppDomain. Увы, объекты RevitAPI нельзя маршалировать в другой домен приложения, поскольку они не расширяют MarshalByRefObject.

Я думаю, я мог бы быть на что-то с теневыми копиями. ASP.NET, кажется, делает это. Но, читая документацию в MSDN, кажется, что я могу указать это только при создании AppDomain.

Могу ли я изменить это для текущего (по умолчанию) AppDomain? Могу ли я заставить его использовать теневые копии DLL из определенного каталога?


person Daren Thomas    schedule 03.09.2009    source источник


Ответы (3)


Я не знаю, что вы пытаетесь сделать, но есть несколько устаревших методов включения ShadowCopy в текущем AppDomain.

AppDomain.CurrentDomain.SetCachePath(@"C:\Cache");
AppDomain.CurrentDomain.SetShadowCopyPath(AppDomain.CurrentDomain.BaseDirectory);
AppDomain.CurrentDomain.SetShadowCopyFiles();
person lubos hasko    schedule 03.09.2009
comment
документы говорят, что они устарели - это то же самое, что устарело? Я попробую. Спасибо! - person Daren Thomas; 03.09.2009
comment
Меня устраивает. Я изменил свой ответ с рабочим примером. Скопируйте и вставьте его в свой метод Main(). Также убедитесь, что ваш метод Main() не ссылается напрямую на другие ваши сборки, потому что .NET загрузит их до того, как будет вызван SetShadowCopyFiles() - person lubos hasko; 04.09.2009

Иногда невозможно изменить код метода Main(), потому что, например, вы пишете подключаемый модуль, и он создается менеджером.

В этом случае я предлагаю вам скопировать сборку и pdb (и зависимые в событии AssemblyResolve) во временное место и загрузить их оттуда с помощью Assembly.LoadFile() (не LoadFrom()).

Плюсы: - нет блокировки dll. - каждый раз, когда целевая сборка перекомпилируется, вы получаете доступ к новой версии (поэтому .LoadFile()). - вся сборка полностью доступна в AppDomain.CurrentDomain.

Минусы: - необходимо копирование файлов. - сборки не могут быть выгружены, что может быть неудобно, так как ресурсы не освобождаются.

С уважением,

PD: Этот код работает.

/// <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;
            }
        }
    }
}
person Eu_UY    schedule 21.08.2010
comment
Спасибо. Я в значительной степени использую эту технику здесь: code.google.com/p/revitpythonshell/ вики/ - person Daren Thomas; 24.08.2010
comment
Проблема с методом, на который вы ссылаетесь, заключается в том, что если вы перекомпилируете сборку, на которую ссылается ваша основная сборка, и вы уже загрузили ее, эта сборка, на которую ссылаются, не перезагружается (обновляется). Например. вы загружаете сборку с именем A, которая зависит от другой сборки с именем B. - person Eu_UY; 23.09.2010
comment
Например: 1) вы загружаете сборку с именем A, которая содержит тип A1, который использует другой тип B1, расположенный в другой сборке с именем B (сборка A ссылается на сборку B). 2) не закрывая домен приложения (не закрывая ваше приложение), вы добавляете новый тип B2 в сборку B и ссылку B2 из типа A1 из сборки A. 3) загружаете снова: здесь новый тип B2 не может будет найден, и вы получите сообщение об ошибке, потому что во второй раз .Net снова не разрешает сборку B (она была разрешена только один раз на шаге 1). Не знаю, достаточно ли я ясно выразился. С уважением, - person Eu_UY; 23.09.2010

Теперь есть подключаемый модуль Revit для динамической загрузки/выгрузки других подключаемых модулей Revit, чтобы вы могли изменять, перекомпилировать и тестировать без повторного открытия проекта Revit. Я нашел его в Building Coder. блог. Он поставляется с Revit SDK.

person skeletank    schedule 07.12.2012
comment
спасибо, я знаю и о блоге, и о AddInManager. Это действительно работает! (за исключением, кажется, форм XAML WPF, но это другое дело, я не собираюсь заниматься FTM) - person Daren Thomas; 11.12.2012