0

I'm working on a program that searches for "plugins" (i.e. .DLL files) in a given directory and then loads them (with Reflection). So far everything works fine. But if I now create a Dependency Property in a plugin project and want to use it, the program crashes with an error message:

Exception thrown: "System.Windows.Markup.XamlParseException" in PresentationFramework.dll The method or operation is not implemented.

This only happens if I create a DP in the plugin project. If I use an existing one (also added myself, but already present in the sense that it is included in the API) everything works fine, no errors or anything else. By the way, the error occurs, at least according to Visual Studio, at Activator.CreateInstance(t).

What's also interesting is that if I add a WPF project to the plugin's project (which is a class library), link to the plugin and API, and include the UserControl that uses the attached property in the mainwindow, everything works fine. So it must have something to do with me loading the plugin dynamically, but I don't know what it could be. Especially since, as I said, previously created DPs work fine, only the ones I create in the plugin don't.

I would like to write a "Steps to reproduce" code, but I'm afraid it won't be that easy. The program is already quite complex now, cutting it off just to isolate the one problem could be difficult. Here is the code that loads the plugins, the attached property, the base class for my attached property and the .xaml file I want to use it in.

Loading plugins:

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);
            }
        }
    }
}

Base class for my attached properties:

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) { }
}

The attached property itself:

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

The Textblock in SystemStatsUI.xaml (The UserControl where I want to use the property):

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

Visual Studio compiles without problems, no warning or anything else. But as soon as I start the main program and it tries to load the plugin, it crashes. But if I now comment out the TextBlock where I use the DP, everything works again, except that the DP is not used. But it does exist, or let's say it was at least created. And as I said, it only doesn't work when loading dynamically. If I add a WPF project to the plugin project and bind in the user control, I can use the DP without any problems. But not in the main program, there I can only use predefined DP of the main program without problems.

Implemented Jeffs idea:

MainWindowViewModel constructor:

    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);
}

It jumps into the callback function, but asks for a .resources file, which it doesn't find in the plugins folder. But I don't know where it should find it, it doesn't exist in my plugin folder either. Or should I only let through requests that have .dll as the end of the file?

CaptTaifun
  • 321
  • 5
  • 15
  • Out of curiosity, what happens if you remove the word “abstract” from your base class? The xaml parser has some weird bugs when it comes to base classes and generic types. Technically xaml doesn’t even support generic types...it’s only by closing the generic type explicitly that you are able to trick it. – Jeff Apr 11 '19 at 15:40
  • what is the reason to create a dependency ? – Frenchy Apr 11 '19 at 15:41
  • @Jeff Nothing happens, the error is still there. – CaptTaifun Apr 11 '19 at 15:43
  • @Frenchy I have created a UserControl in the plugin (a display for values between 0-100, like a progress bar, only circular). And I want to be able to set certain values when I use the control somewhere (the UserControl also uses a ViewModel, not the CodeBehind file). Just like things like the current value, a SubHeading and maybe the color or something. – CaptTaifun Apr 11 '19 at 15:46
  • @Jeff I have also tried to create a DP without my base class. But it comes to the same error. – CaptTaifun Apr 11 '19 at 15:47

1 Answers1

0

I believe the problem may be the load context of your dynamically loaded assembly. It’s in the LoadFrom context and so the xaml parser won’t resolve it correctly, since it is running in the default load context.

You will need to add a custom assembly resolver that handles runtime resolution for the xaml parser.

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;

also - make ABSOLUTELY sure you don't have any duplicate dlls between your plugin dlls directory and your bin directory - you'll get "the component does not have a resource identifier" kind of errors you mentioned.

Jeff
  • 35,755
  • 15
  • 108
  • 220
  • When I do that, the program comes with errors because it can't find MySql.Data.Dll or dependencies on it anymore. And just shoot down anything that somehow has to do with MySQL is not as easy as you might expect. – CaptTaifun Apr 11 '19 at 15:54
  • Ok, then go straight to the fix. I’m pretty confident this is your problem. Add an event handler to AppDomain.CurrentDomain.AssemblyResolve. If it is requesting one of your dynamically loaded assemblies, return it. Put a breakpoint to verify your handler is getting hit. – Jeff Apr 11 '19 at 15:56
  • I've updated the question with your idea. I hope that's what you meant. – CaptTaifun Apr 11 '19 at 16:08
  • I edited the main thread again, maybe the new error helps a bit. – CaptTaifun Apr 11 '19 at 16:34
  • I might be misremembering, but I think args.Name is never a file name - it’s an assembly name, so it will never have a .dll extension – Jeff Apr 11 '19 at 16:39
  • Oh, yeah. Right. My mistake, so it's worthless.. ^^ In the worst case I just work with static properties in the ViewModel. I don't think it's very good, but it's about our thesis, so time is of the essence. I mean, if you (or anyone else) can think of anything else, I'd still be interested. But thanks in any case for the help. – CaptTaifun Apr 11 '19 at 16:41