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

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

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

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

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

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

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

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

3 ответа

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

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

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

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

Плюсы: - нет блокировки длл. - каждый раз, когда целевая сборка перекомпилируется, вы получаете доступ к новой версии (поэтому.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;
            }
        }
    }
}

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

Другие вопросы по тегам