views:

31

answers:

3

I’ve created my own plugin architecture based on the common practices but I’m stuck with icons.

First of all my plugins define Clients to be used by the host and each Client is defined with attributes like:

[Client("Heroes of Newerth", "Heroes of Newerth Chat Client", "hon_16.png")]

With this my host application can read the plugin/client's metainfo without actually creating an instance of it but I’m stuck with the icon part.

As in the above example a client can pass a bitmap file name and in my attributes implementation I can handle it like this:

[AttributeUsage(AttributeTargets.Class)]
public class ClientAttribute : Attribute
{
    private string _name;
    private string _description;
    private Bitmap _icon;

    public string Name { get { return this._name; } }
    public string Description { get { return this._description; } }
    public Bitmap Icon { get { return this._icon; } }

    public ClientAttribute(string Name, string Description, string IconFile = null)
    {            
        this._name = Name;
        this._description = Description;
        this._icon = new Bitmap(IconFile);
    }
}

The problem is that within this method I need to publish the icon files with my release as is and can't add them to resources. I'll be happy to hear a method that I can still embed the icons in resources.

A: 

can't add them to resources

Why not? You could define IconFile as the name of a resource within the same assembly as the plugin. Then, given an Assembly that contains the plugin, call assembly.GetManifestResourceStream and pass that stream to the Bitmap constructor.

Tim Robinson
I've tried that solution once but the problem is that i can't get the actual assembly the resources is in - within the Attribute's ctor.First of all i've 3 seperate assemblies in my project;the user-interface assembly, the host assembly and the plugin assembly.After the execution it seems that mscorlib is the caller, ui assembly is the entrant and the host assembly the executer of the code.If i could manage to get a reference to plugin assembly i could be implementing the method you mentioned.
raistlinthewiz
What you can do in an attribute constructor is pretty limited. It's limited by the fact that the attribute can't know what it's been applied to. A better approach is for the attribute to expose these three values as properties, and move the loading code to somewhere that can see the plugin `Type`.
Tim Robinson
A: 

You can add them to the resources, and then access the resources from the custom attribute. However, you need a reference to the plugin’s assembly, so you can’t do it in the constructor. I would do it like this:

[AttributeUsage(AttributeTargets.Class)]
public class ClientAttribute : Attribute
{
    private string _name;
    private string _description;
    private string _resourceName;
    private Bitmap _icon;

    public string Name { get { return this._name; } }
    public string Description { get { return this._description; } }
    public Bitmap Icon { get { return this._icon; } }

    public ClientAttribute(string name, string description, string resourceName = null)
    {
        _name = name;
        _description = description;
        _resourceName = resourceName;
    }

    public void ResolveResource(Assembly assembly)
    {
        if (assembly != null && _resourceName != null)
        {
            var stream = assembly.GetManifestResourceStream(_resourceName);
            if (stream != null)
                _icon = new Bitmap(stream);
        }
    }
}

Of course, this means that when you first retrieve the custom attribute, the Icon property will return null until you call Resolve with the correct assembly. I’ve written it so that it stays null if there are problems; you need to decide whether you want to throw exceptions instead. In particular, maybe the Icon property getter should throw an exception when _icon is null but _resourceName isn’t, because then it means that Resolve wasn’t called, or there was a problem. (You should still be aware that even Resolve can throw because GetManifestResourceStream or the Bitmap constructor might throw.)

Timwi
@timwi, it seems your answer is pretty viable, as for my architecture i do have a PluginInfo class where i do get metainfo's about plugins and ininstantiate them. It may be a good point to resolve the ClientAttribute's assembly as the PluginInfo knows the assembly.I'll try this implementation but i also searched on google for other sample plugin architectures where most of them exposes plugin attributes with name and description but none of them utilizes the icon attribute. It would be good to see how other's overcome with the problem.
raistlinthewiz
A: 

Thanks to timwi here's my final implementation;

    internal void ResolveResources(Assembly _assembly)
    {
        if (_assembly != null && this._icon_name != null)
        {                   
            var stream = _assembly.GetManifestResourceStream(string.Format("{0}.Resources.{1}",_assembly.GetName().Name,this._icon_name));
            if (stream != null) this._icon = new Bitmap(stream);
        }
    }

And in my PluginInfo class i call the resolver;

            else if(t.IsSubclassOf(typeof(Client)))
            {
                object[] _attr = t.GetCustomAttributes(typeof(ClientAttribute), true);
                (_attr[0] as ClientAttribute).ResolveResources(this._assembly);
            }
raistlinthewiz