1

... Newbie here :D

I'm trying to study plugin mechanism

I have an exe and a dll both in Winform and main application has a text box with two functions which set and get text of the textbox and this two function are API for example.

public void SetDataX(string data)
{
    textboxx.Text = data;
}

public string GetDataX()
{
    return textboxx.Text;
}

Okay , then I created a class to hold the functions and added it to both dll and exe :

plugin_interface.cs

namespace Plugin_Mech_Study
{
    public class app_api
    {
        public Action<string> SetData { get; set; }

        public Func<string> GetData { get; set; }
    }
}

In dll I made a function which accepts app_api and I name it Load(app_api apibridge)

Now when I'm trying reflection to invoke and pass app_api to dll I get this error :

System.ArgumentException: 'Object of type 'Plugin_Mech_Study.app_api' cannot be converted to type 'pluginTest.app_api'.'

Here's how I'm invoking dll :

  private void load_plugin(string pluginadd)
    {

        var loadplugin = Assembly.LoadFile(pluginadd);
        Type t = loadplugin.GetType("pluginTest.plugin");

        app_api newapi = new app_api();
        newapi.SetData = SetDataX;
        newapi.GetData = GetDataX;

        var apimethod = t.GetMethod("Load");
        if (apimethod == null)
        {
            MessageBox.Show("Can't Generate API!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            Environment.Exit(501);
        }

        var o2 = Activator.CreateInstance(t);
        var result2 = apimethod.Invoke(o2, new object[] { newapi }); /// Error Happens Here


    }

How can I solve this issue ? If question is not clear enough I can upload source codes Thanks

Edit 2 :


Here's Minimal Codes

Plugin_Mech_Study[Winform exe] => Program.cs

using System;
using System.Windows.Forms;

namespace Plugin_Mech_Study
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.Run(new mainapp());
        }
    }
}

Plugin_Mech_Study[Winform exe] => mainapp.cs

using System;
using System.Windows.Forms;
using System.Reflection;

namespace Plugin_Mech_Study
{
    public partial class mainapp : Form
    {
        public mainapp()
        {
            InitializeComponent();
        }

        public void SetDataX(string data)
        {
            textboxx.Text = data;
        }

        public string GetDataX()
        {
            return textboxx.Text;
        }


        private void load_plugin(string pluginadd)
        {

            var loadplugin = Assembly.LoadFile(pluginadd);
            Type t = loadplugin.GetType("pluginTest.plugin");

            var guimethod = t.GetMethod("GetControl");
            if (guimethod == null)
            {
                MessageBox.Show("Can't Load GUI!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

            var o = Activator.CreateInstance(t);
            var result = guimethod.Invoke(o, null);
            plug_ui.Controls.Add((UserControl)result);

            app_api newapi = new app_api();
            newapi.SetData = SetDataX;
            newapi.GetData = GetDataX;

            var apimethod = t.GetMethod("Load");
            if (apimethod == null)
            {
                MessageBox.Show("Can't Generate API!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

            var o2 = Activator.CreateInstance(t);
            var result2 = apimethod.Invoke(o2, new object[] { newapi });
        }

        private void button1_Click(object sender, EventArgs e)
        {
            load_plugin(Environment.CurrentDirectory + @"\tzplugins\pluginTest.dll");
        }
    }
}

Plugin_Mech_Study[Winform exe] => mainapp.Designer.cs

namespace Plugin_Mech_Study
{
    partial class mainapp
    {
        private System.ComponentModel.IContainer components = null;

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        private void InitializeComponent()
        {
            this.button1 = new System.Windows.Forms.Button();
            this.textboxx = new System.Windows.Forms.TextBox();
            this.plug_ui = new System.Windows.Forms.Panel();
            this.SuspendLayout();
            this.button1.BackColor = System.Drawing.Color.SteelBlue;
            this.button1.Font = new System.Drawing.Font("Microsoft Sans Serif", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.button1.ForeColor = System.Drawing.Color.AntiqueWhite;
            this.button1.Location = new System.Drawing.Point(253, 24);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(147, 52);
            this.button1.TabIndex = 0;
            this.button1.Text = "Load Plugin";
            this.button1.UseVisualStyleBackColor = false;
            this.button1.Click += new System.EventHandler(this.button1_Click);
            this.textboxx.BackColor = System.Drawing.SystemColors.Info;
            this.textboxx.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.textboxx.Location = new System.Drawing.Point(12, 24);
            this.textboxx.Multiline = true;
            this.textboxx.Name = "textboxx";
            this.textboxx.Size = new System.Drawing.Size(235, 170);
            this.textboxx.TabIndex = 2;
            this.textboxx.Text = "This is a Test";
            this.plug_ui.BackColor = System.Drawing.SystemColors.Info;
            this.plug_ui.Location = new System.Drawing.Point(253, 82);
            this.plug_ui.Name = "plug_ui";
            this.plug_ui.Size = new System.Drawing.Size(147, 112);
            this.plug_ui.TabIndex = 3;
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.BackColor = System.Drawing.Color.RoyalBlue;
            this.ClientSize = new System.Drawing.Size(417, 210);
            this.Controls.Add(this.plug_ui);
            this.Controls.Add(this.textboxx);
            this.Controls.Add(this.button1);
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
            this.Name = "mainapp";
            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
            this.Text = "Plugin Loader";
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.Button button1;
        private System.Windows.Forms.TextBox textboxx;
        private System.Windows.Forms.Panel plug_ui;
    }
}

Plugin_Mech_Study[Winform exe] => plugin_interface.cs

using System;

namespace Plugin_Mech_Study
{

    public interface api_interface
    {
         Action<string> SetData { get; set; }
         Func<string> GetData { get; set; }
    }

        public class app_api : api_interface
    {
        public Action<string> SetData { get; set; }
        public Func<string> GetData { get; set; }
    }


}

pluginTest[Class Library] => plugin.cs

using System.Windows.Forms;

namespace pluginTest
{
    public class plugin : plugin_interface
    {
        private plugin_UI pluginUI;
        public UserControl GetControl() {
            var new_gui = new plugin_UI();
            pluginUI = new_gui;
            return new_gui;
        }

        public void Load(api_interface apibridge) {
            pluginUI.LoadPlugin(apibridge);
        }

    }
}

pluginTest[Class Library] => plugin_interface.cs

using System;
using System.Windows.Forms;

namespace pluginTest
{
    public interface plugin_interface
    {
        UserControl GetControl();
        void Load(api_interface apibridge);
    }
    public interface api_interface
    {
        Action<string> SetData { get; set; }

        Func<string> GetData { get; set; }
    }

    public class app_api : api_interface
    {
        public Action<string> SetData { get; set; }

        public Func<string> GetData { get; set; }
    }
}

pluginTest[Class Library] => plugin_UI.cs

using System;
using System.Windows.Forms;

namespace pluginTest
{
    public partial class plugin_UI : UserControl
    {
        api_interface bridgedAPI;

        public void LoadPlugin(api_interface apibridge)
        {
            bridgedAPI = apibridge;
        }

        public plugin_UI()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            textBox1.Text = bridgedAPI.GetData();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            bridgedAPI.SetData(textBox1.Text);
        }
    }
}

pluginTest[Class Library] => plugin_UI.Designer.cs

namespace pluginTest
{
    partial class plugin_UI
    {
        private System.ComponentModel.IContainer components = null;

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Component Designer generated code

        private void InitializeComponent()
        {
            this.button1 = new System.Windows.Forms.Button();
            this.button2 = new System.Windows.Forms.Button();
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.SuspendLayout();
            this.button1.BackColor = System.Drawing.SystemColors.HotTrack;
            this.button1.Font = new System.Drawing.Font("Microsoft Sans Serif", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.button1.ForeColor = System.Drawing.SystemColors.Control;
            this.button1.Location = new System.Drawing.Point(15, 14);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(118, 25);
            this.button1.TabIndex = 0;
            this.button1.Text = "Get Data";
            this.button1.UseVisualStyleBackColor = false;
            this.button1.Click += new System.EventHandler(this.button1_Click);
            this.button2.BackColor = System.Drawing.SystemColors.HotTrack;
            this.button2.Font = new System.Drawing.Font("Microsoft Sans Serif", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.button2.ForeColor = System.Drawing.SystemColors.Control;
            this.button2.Location = new System.Drawing.Point(15, 47);
            this.button2.Name = "button2";
            this.button2.Size = new System.Drawing.Size(118, 25);
            this.button2.TabIndex = 1;
            this.button2.Text = "Set Data";
            this.button2.UseVisualStyleBackColor = false;
            this.button2.Click += new System.EventHandler(this.button2_Click);
            this.textBox1.BackColor = System.Drawing.SystemColors.Info;
            this.textBox1.ForeColor = System.Drawing.Color.Red;
            this.textBox1.Location = new System.Drawing.Point(15, 79);
            this.textBox1.Name = "textBox1";
            this.textBox1.Size = new System.Drawing.Size(118, 20);
            this.textBox1.TabIndex = 2;
            this.textBox1.Text = "Test Data";
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.BackColor = System.Drawing.SystemColors.Info;
            this.Controls.Add(this.textBox1);
            this.Controls.Add(this.button2);
            this.Controls.Add(this.button1);
            this.Name = "plugin_UI";
            this.Size = new System.Drawing.Size(147, 112);
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.Button button1;
        private System.Windows.Forms.Button button2;
        private System.Windows.Forms.TextBox textBox1;
    }
}

And here's source code.

  • The clue to your problem is actually in the name of your file - `plugin_interface.cs` - you should have an *interface* somewhere that your plugins implement. It is hard (if not impossible) to diagnose the exact problem with your code from what you have posted. Dont upload the *entire code* just show a [mcve] – Jamiec Jun 17 '19 at 09:47
  • You'll need to add a new dll which contains the app_api-class. You can then add a reference to this dll in all the other apps and you can then use the app_api from there. If you have a class app_api in more than one assembly, it doesn't know which one you want and it also can't determine if they are the same. That's why you get the error, it doesn't know that those two classes are the same. – Joelius Jun 17 '19 at 09:47
  • And yes I agree with @Jamiec. Using an interface which can be implemented is a lot better for your case. I only mentioned why you got the error, not how you can improve the code overall because there are a bunch of things. – Joelius Jun 17 '19 at 09:48
  • @Jamiec thank you, yes first I tried to use interface but interface cannot contain fields , so how can I add my functions to it ? aslo I will add minimal code soon... –  Jun 17 '19 at 10:11
  • 1
    Interfaces can contain *properties* - but in any case your interface is just 2 *methods*. – Jamiec Jun 17 '19 at 10:12
  • @WilliamFender why do you want to use properties for those functions? If you used an interface you would directly implement those functions instead of storing them in a property. – Joelius Jun 17 '19 at 10:26
  • @Jamiec I used interface but still same error `'Object of type 'Plugin_Mech_Study.app_api' cannot be converted to type 'pluginTest.api_interface'.'` –  Jun 17 '19 at 10:31
  • @Joelius can you provide a simple example ? –  Jun 17 '19 at 10:31
  • @WilliamFender that error message is *exactly as it seems* - you cant convert one type to another because they are entirely different types (even if they have the same properties/methods/etc.) Provide some more code with a [mcve] and I'll do my best to correct your code – Jamiec Jun 17 '19 at 10:32
  • @Jamiec Post Updated and I added all codes. –  Jun 17 '19 at 13:14
  • @WilliamFender really simple fix. See answer – Jamiec Jun 17 '19 at 14:08

2 Answers2

1

Imagine something like this:

// in assembly Data
interface IAppAPI
{
    void SetData(string data);
    string GetData();
}

// wherever you want. You only need a reference to the Data assembly
class AppAPI : IAppAPI
{
    public string GetData()
    {
        // get and return data
    }

    public void SetData(string data)
    {
        // set data
    }
}


// wherever you want. You only need a reference to the Data assembly
void LoadPlugin(string pluginPath, string pluginTypeIdentifier) 
{
    Assembly pluginAssembly = Assembly.LoadFile(pluginPath);
    Type pluginType = pluginAssembly.GetType(pluginTypeIdentifier);

    IAppAPI plugin = (IAppAPI)Activator.CreateInstance(pluginType);

    plugin.SetData("whatever");
    string whatever = plugin.GetData();
}

You have some assembly (let's call it Data). In this you have the interface for implementing the plugin. Then you can implement it anywhere you'd like. You only have to add a reference to the assembly so it knows what the interface is.
Now you can have this LoadPlugin method which takes the path to an assembly and the full name of the plugin type (eg. pluginTest.plugin).
You can then cast the new instance of that plugin to the interface (because you want those methods).
Now call your functions or do whatever you want with it.

Note that I didn't add any error checks. I would highly advise you to check if

  1. the assembly exists (File.Exists)
  2. the type exists (check if the type == null or use the exception overloads)
  3. the type is assignable from the interface (pluginType.IsAssignableFrom(typeof(IAppAPI)))
  4. the type is a class and has a parameterless constructor and is not static or abstract (pluginType.IsClass && !pluginType.IsAbstract). IsAbstract also clears the static part (see this answer). For the parameterless constructor see this question.

Maybe there are even more checks you could/should do.

As discussed with Jamiec, this might not answer the direct question of you. Jamiec and I both think that having a plugin directly use your TextBox is a bad idea in many ways. See this bit of our conversation:

Youve provided a good, generic, answer on how to write a plugin, but not how to solve their actual problem.

I mean it wouldn't be hard to add a reference to the textbox for the plugin but it would be really bad because then you have UI-Element spaghetti.

I agree 100%

So please make sure a plugin solution is the right thing for your use case and also strictly separate your plugins from your UI (UI plugins are even harder, I don't even want to talk about these yet).

Good Luck!

Community
  • 1
  • 1
Joelius
  • 3,839
  • 1
  • 16
  • 36
  • I hope you dont mind - I edited out a part of the conversation which I think could easily come across as "unkind". It doesnt affect the actual sentiment of the conversation. – Jamiec Jun 17 '19 at 11:22
  • No it's fine :) Didn't even think about that, sorry. – Joelius Jun 17 '19 at 11:24
  • 1
    first thanks I give it a try , second I'm not typical example of a person trying to run before they can walk and I know basics of programming very well but I'm new in C# because I used C\C++ before and I worked on plugin mechanism in native but never tried and used managed so ... again , thank you :) –  Jun 17 '19 at 11:34
  • @WilliamFender apologies if any offense caused. I by no means meant any. You can still be an experienced programming on one platform and get ahead of yourself on another. – Jamiec Jun 17 '19 at 12:27
  • @Joelius you're answer is not even close , I knew this things all before and I did a part of it like what you did but the question is how to access functions in main application not accessing from main app to plugin functions. –  Jun 17 '19 at 12:48
  • @Jamiec No problem , in c++ I easily used getprocaddress and I get access to main application , the same way used in many famous SDKs , but in C# I don't know how it works. I'm posting plugin code. –  Jun 17 '19 at 12:50
1

Design consideration aside, there are in fact only 2 very simple problems with your code

  1. You define the interface api_interface in 2 places, and try to treat one as the other. EVen though you know they are the same (same properties/methods/whatever) as far as the compiler is concerned they are 2 completely different interfaces.

  2. Within load_plugin you create 2 separate instances of the plugin, and attempt to call GetControl on one and Load on the other - the problem with this is that you have internal state on the instance of the plugin (private plugin_UI pluginUI;) so this state is lost by the second call.

Fixing both is super simple.

For 1. Create a third class library and move the api_interface to that assembly. Then reference this new assembly from both the winforms and plugin. (And of course remove the definition from the 2 places/fix up using references)

For 2. Simply use the same instance:

private void load_plugin(string pluginadd)
{

    var loadplugin = Assembly.LoadFile(pluginadd);
    Type t = loadplugin.GetType("pluginTest.plugin");

    var guimethod = t.GetMethod("GetControl");
    if (guimethod == null)
    {
        MessageBox.Show("Can't Load GUI!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }

    var o = Activator.CreateInstance(t);
    var result = guimethod.Invoke(o, null);
    plug_ui.Controls.Add((UserControl)result);

    app_api newapi = new app_api();
    newapi.SetData = SetDataX;
    newapi.GetData = GetDataX;

    var apimethod = t.GetMethod("Load");
    if (apimethod == null)
    {
        MessageBox.Show("Can't Generate API!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }

    //var o2 = Activator.CreateInstance(t); <-- DONT DO THIS
    var result2 = apimethod.Invoke(o, new object[] { newapi });
}

With these 2 changes I got your code working as I suspect you expected.

Jamiec
  • 133,658
  • 13
  • 134
  • 193
  • lol ! don't believe one line made me waste half of a day! thanks bro , that one line was the point... –  Jun 17 '19 at 15:21