使用Method.Invoke时无法传递类

时间:2019-06-17 09:41:16

标签: c# winforms plugins

...新手在这里:D

我正在尝试研究插件机制

我在Winform中有一个exe和dll,并且主应用程序都有一个文本框,其中包含两个用于设置和获取文本框文本的函数,例如,这两个函数就是API。

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

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

好的,然后我创建了一个类来保存函数并将其添加到dll和exe中:

plugin_interface.cs

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

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

在dll中,我做了一个接受app_api的函数,并将其命名为Load(app_api apibridge)

现在,当我尝试反射来调用并将app_api传递给dll时,出现此错误:

  

System.ArgumentException:'类型为'Plugin_Mech_Study.app_api'的对象   无法转换为'pluginTest.app_api'类型。'

这是我调用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


    }

如何解决此问题? 如果问题不清楚,我可以上传源代码 谢谢

编辑2:


此处是最小代码

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 [类库] => 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 [类库] => 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 [类库] => 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 [类库] => 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;
    }
}

还有here's source code

2 个答案:

答案 0 :(得分:1)

想象一下这样的事情:

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

您有一些程序集(我们称之为数据)。在此,您具有用于实现插件的接口。然后,您可以在任何位置实现它。您只需添加对该程序集的引用,即可知道该接口是什么。
现在,您可以使用此LoadPlugin方法,该方法获取程序集的路径和插件类型的全名(例如pluginTest.plugin)。
然后,您可以将该插件的新实例强制转换为接口(因为您需要这些方法)。
现在调用函数或使用它做任何您想做的事情。

请注意,我没有添加任何错误检查。我强烈建议您检查

  1. 程序集存在(File.Exists
  2. 类型存在(检查type == null还是使用异常重载)
  3. 可以从界面(pluginType.IsAssignableFrom(typeof(IAppAPI)))分配类型
  4. 该类型是一个类,具有无参数构造函数,并且不是静态或抽象(pluginType.IsClass && !pluginType.IsAbstract)。 IsAbstract还清除静态部分(请参见this answer)。有关无参数的构造函数,请参见this question

也许您可以/应该做更多的检查。

正如与Jamiec讨论的那样,这可能无法回答您的直接问题。 Jamiec和我都认为让插件直接使用TextBox在许多方面都是一个坏主意。查看我们的对话内容:

  

您为如何编写插件提供了一个很好的通用答案,但没有解决实际问题。

     

我的意思是,为插件添加对文本框的引用并不难,但这确实很糟糕,因为那样您就有了UI-Element意大利面。

     

我同意100%

因此,请确保插件解决方案适合您的用例,并严格将插件与UI分开(UI插件更难,我什至不想谈论它们)。

祝你好运!

答案 1 :(得分:1)

撇开设计考虑,实际上您的代码只有两个非常简单的问题

  1. 您在2个地方定义了接口api_interface,然后尝试将其中一个视为另一个。尽管知道它们在编译器方面是相同的(相同的属性/方法/任何内容),但它们是2个完全不同的接口。

  2. load_plugin内创建插件的2个单独实例,并尝试在一个实例上调用GetControl,在另一个实例上调用Load-这是因为您拥有插件实例(private plugin_UI pluginUI;上的内部状态,因此第二次调用会丢失此状态。

修复这两个问题非常简单。

对于1.创建一个第三类库并将api_interface移动到该程序集。然后从winforms和插件中引用此新程序集。 (当然,还要从2个地方中删除该定义/修正using参考)

对于2。只需使用相同的实例:

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

通过这两个更改,我使您的代码正常运行,我怀疑您是期望的。