Не удается передать класс при использовании Method.Invoke - PullRequest
2 голосов
/ 17 июня 2019

... Новичок здесь: D

Я пытаюсь изучить механизм плагинов

У меня есть exe и dll и в Winform, и в основном приложении есть текстовое поле с двумя функцияминапример, который устанавливает и получает текст текстового поля, и эти две функции являются 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:


Вот Минimal коды

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

И вот исходный код .

Ответы [ 2 ]

1 голос
/ 17 июня 2019

Если принять во внимание дизайн, на самом деле есть только 2 очень простые проблемы с вашим кодом

  1. Вы определяете интерфейс api_interface в двух местах и ​​пытаетесь рассматривать одну как другую,Даже если вы знаете, что они одинаковы (те же свойства / методы / что угодно), поскольку компилятор считает, что это два совершенно разных интерфейса.

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

С этими двумя изменениями я получил вашкод работает, как я подозреваю, вы ожидали.

1 голос
/ 17 июня 2019

Представьте себе что-то вроде этого:

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

У вас есть какая-то сборка (назовем это Data).В этом у вас есть интерфейс для реализации плагина.Тогда вы можете реализовать это где угодно.Вам нужно только добавить ссылку на сборку, чтобы она знала, что такое интерфейс.
Теперь у вас может быть этот метод LoadPlugin, который берет путь к сборке и полное имя типа плагина (например, * 1006).*).
Затем вы можете привести новый экземпляр этого плагина к интерфейсу (потому что вам нужны эти методы).
Теперь вызывайте ваши функции или делайте с ним что угодно.

Обратите внимание, что я не добавил никаких проверок ошибок.Я настоятельно рекомендую вам проверить, существует ли

  1. сборка (File.Exists)
  2. тип (проверить, если type == null, или использовать перегрузки исключений)
  3. тип можно назначить из интерфейса (pluginType.IsAssignableFrom(typeof(IAppAPI)))
  4. тип является классом и имеет конструктор без параметров и не является статическим или абстрактным (pluginType.IsClass && !pluginType.IsAbstract).IsAbstract также очищает статическую часть (см. этот ответ ).Для конструктора без параметров см. этот вопрос .

Возможно, есть еще больше проверок, которые вы могли бы / должны сделать.

Как обсуждалось с Jamiec, это может не ответить на прямой вопрос о вас.Jamiec и я оба думаем, что иметь плагин для непосредственного использования вашего TextBox - плохая идея во многих отношениях.Посмотрите этот фрагмент нашего разговора:

Вы предоставили хороший, общий ответ о том, как написать плагин, но не о том, как решить их актуальную проблему.

Я имею в виду, что было бы не сложно добавить ссылку на текстовое поле для плагина, но было бы очень плохо, потому что тогда у вас есть спагетти UI-Element.

Я согласен на 100%

Поэтому, пожалуйста, убедитесь, что решение для плагинов подходит для вашего случая использования, а также строго отделите ваши плагины от вашего UI (плагины UI еще сложнее, я даже не хочу говорить об этом пока).

Удачи!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...