Использование пользовательских подключенных свойств в плагине приводит к ошибке с Reflection / CreateInstance - PullRequest
0 голосов
/ 11 апреля 2019

Я работаю над программой, которая ищет «плагины» (т.е. файлы .DLL) в заданном каталоге, а затем загружает их (с помощью Reflection). Пока все отлично работает. Но если я теперь создаю свойство зависимости в проекте плагина и хочу его использовать, программа вылетает с сообщением об ошибке:

Исключение: «System.Windows.Markup.XamlParseException» в PresentationFramework.dll Метод или операция не реализованы.

Это происходит только в том случае, если я создаю DP в проекте плагина. Если я использую существующий (тоже добавил себя, но уже присутствует в том смысле, что он включен в API), все работает нормально, без ошибок или чего-либо еще. Кстати, ошибка возникает, по крайней мере, в соответствии с Visual Studio, в Activator.CreateInstance (t).

Интересно также то, что если я добавлю проект WPF в проект плагина (который является библиотекой классов), сошлюсь на плагин и API и включу UserControl, который использует свойство присоединенного в главном окне, все работает нормально. Так что это как-то связано со мной, загружающей плагин динамически, но я не знаю, что это может быть. Тем более, что, как я уже сказал, ранее созданные DP работают нормально, только те, которые я создаю в плагине, не работают.

Я хотел бы написать код «Шаги по воспроизведению», но боюсь, это будет не так просто. Программа уже довольно сложна, отрезать ее просто для того, чтобы выделить одну проблему, может быть сложно. Вот код, который загружает плагины, прикрепленное свойство, базовый класс для моего присоединенного свойства и файл .xaml, в котором я хочу его использовать.

Загрузка плагинов:

public void LoadPlugins()
{
    var path = Directory.GetCurrentDirectory() + "\\plugins\\";

    Assembly asm;
    foreach (var plugin in Directory.GetFiles(path, "*.dll"))
    {
        asm = Assembly.LoadFrom(plugin);
        foreach (var t in asm.GetTypes())
        {
            if (t.GetInterface("RemotyAPI") != null && (t.Attributes & TypeAttributes.Abstract) != TypeAttributes.Abstract)
            {
                var pluginInstance = (RemotyAPI)Activator.CreateInstance(t);

                var p = new Plugin
                {
                    Name = t.Name,
                    Title = pluginInstance.Name,
                    Version = pluginInstance.Version,
                    LogoPath = pluginInstance.LogoPath,
                    Instance = pluginInstance
                };

                p.Instance.SendMessage += Server.GetInstance().SendMessage;

                Plugins.Add(p);
            }
        }
    }
}

Базовый класс для моих прикрепленных свойств:

public abstract class BaseAttachedProperty<Parent, Property>
    where Parent : new()
{
    public event Action<DependencyObject, DependencyPropertyChangedEventArgs> ValueChanged = (sender, e) => { };

    public static Parent Instance { get; private set; } = new Parent();

    public static readonly DependencyProperty mValueProperty = DependencyProperty.RegisterAttached("Value", typeof(Property), typeof(BaseAttachedProperty<Parent, Property>), new UIPropertyMetadata(new PropertyChangedCallback(OnValuePropertyChanged)));

    private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        (Instance as BaseAttachedProperty<Parent, Property>)?.OnValueChanged(d, e);

        (Instance as BaseAttachedProperty<Parent, Property>)?.ValueChanged(d, e);
    }

    public static Property GetValue(DependencyObject d)
    {
        return (Property)d.GetValue(mValueProperty);
    }

    public static void SetValue(DependencyObject d, Property value)
    {
        d.SetValue(mValueProperty, value);
    }

    public virtual void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { }
}

Само прикрепленное свойство:

public class SubHeading : BaseAttachedProperty<SubHeading, string> { }

Textblock в SystemStatsUI.xaml (UserControl, где я хочу использовать свойство):

<TextBlock local:SubHeading.Value="Test" />

Visual Studio компилируется без проблем, без предупреждений или чего-либо еще. Но как только я запускаю основную программу и она пытается загрузить плагин, происходит сбой. Но если я сейчас закомментирую TextBlock, где я использую DP, все снова работает, за исключением того, что DP не используется. Но он существует или, скажем, по крайней мере, был создан. И, как я уже сказал, он не работает только при динамической загрузке. Если я добавлю проект WPF в проект плагина и свяжу его в пользовательском элементе управления, я могу использовать DP без проблем. Но не в основной программе, там я могу без проблем использовать только предопределенный DP основной программы.

Реализована идея Джеффса:

Конструктор MainWindowViewModel:

    AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
    ....
}

CurrentDomain_AssemblyResolve:

private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
    var path = Directory.GetCurrentDirectory() + "\\plugins\\";
    return Assembly.LoadFrom(path + args.Name);
}

Он переходит в функцию обратного вызова, но запрашивает файл .resources, которого он не находит в папке плагинов. Но я не знаю, где он должен найти его, он также не существует в моей папке плагинов. Или я должен пропустить только запросы, которые имеют .dll в качестве конца файла?

1 Ответ

0 голосов
/ 11 апреля 2019

Я полагаю, что проблема может быть в контексте загрузки вашей динамически загружаемой сборки. Он находится в контексте LoadFrom, поэтому синтаксический анализатор xaml не сможет решить его правильно, поскольку он работает в контексте загрузки по умолчанию.

Вам потребуется добавить пользовательский преобразователь сборок, который обрабатывает разрешение среды выполнения для анализатора xaml.

asm = Assembly.LoadFrom(plugin); // your current code...just add the below line of code
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => 
  new AssemblyName(args.Name).Name == asm.GetName().Name ? asm : null;

также - убедитесь, что АБСОЛЮТНО убедитесь, что у вас нет дубликатов dll между каталогом dll-плагина и каталогом bin - вы получите ошибки типа «компонент не имеет идентификатора ресурса», которые вы упомянули.

...