create new WinForm of an external project with Assembly.Load

650 views Asked by At

I have 2 windowsForm projects (Project A and B) in C #, but I would like to add in Project B the reference to Project A by code and call Project A from within Project B. I used Assembly.Load and it is only working if I remove the Main void arguments.

The form of Project A should be open as MdiParent of Project B.

I tried using Assembly.load and activator.createinstance but it didn't work when I try to pass the method parameter.

With param args is returning an error (System.MissingMethodException: 'Constructor in type' CompareXMLTools.Main 'not found.')

#using  System.Reflection.Assembly

Project A Program.cs

namespace CompareXMLTools
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main(string[] args)
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Main(args));

        }
    }
}

WindowsForm

namespace CompareXMLTools
{
    public partial class Main : Form
    {
        public Main(string[] args)
        {
            InitializeComponent();
            ArgsPassed = args;
        }
    }
}

Project B

namespace XMLValidator
{
    public partial class frmMain : Form
    {
        public frmMain(string[] args)
        {
            InitializeComponent();
            ArgsPassed = args;
        }
    }

    private void tsbrnCompareXML_Click(object sender, EventArgs e)
    {
        object dllUserControl = null;
        System.Reflection.Assembly assembly2 = AppDomain.CurrentDomain.Load(File.ReadAllBytes(@"D:\Projetos C#\XMLValidator\XMLValidator\CompareXMLTools.exe"));
        dllUserControl = assembly2.CreateInstance("CompareXMLTools.Main", true, System.Reflection.BindingFlags.Default, null, new string[] { }, System.Globalization.CultureInfo.CurrentCulture, null);

        ((frmMain)dllUserControl).MdiParent = this;
        ((frmMain)dllUserControl).Show();
    }
}

Note: The Project B command only works if I remove the ** string [] args ** field from the Main Method.

I need to pass arguments when call the new WinForm of project A, how can I do this?

1

There are 1 answers

1
Jimi On BEST ANSWER

I suggest to add a parameter-less constructor to Form1, so you can call it after from an external assembly that may not know what number of parameters it expects (if any) and add the logic required to handle null parameters.

namespace XMLValidator
{
    public partial class Main : Form
    {
        public Main() : this(null) { }

        public Main(string[] args) {
            InitializeComponent();
            [Something] = args;
        }
    }
}

After this, if you don't want / can't use Interfaces (to have a common understanding of the contract between these assemblies), you have to rely on your knowledge of what Forms you want to load and call them by Name.

Parameters can be passed using the Activator.CreateInstance overload tha accepts a Type (you know what type you want to load, a Form) and the arguments in the form of params object[].

public static object CreateInstance (Type type, params object[] args);

Each object in params represents the arguments expected by a Constructor of the class you specify.
A parameter-less constructor doesn't expect any argument, so you pass null in args[0].
Otherwise, args[0] contains the arguments of the non-empty Constructor you want to call, to initialize the class specified.

object[] args = new object[1] { new string[] { "Args String", "Args Value", "Other args" } };

and call:

Activator.CreateInstance([Type], args) as [Type];

I suggest to build an intermediate class that handles the initialization of the external assembly, extract some useful information automatically (the NameSpace, resources of specific type - Forms, Project Resources, etc.). so you just need to provide the name of a Form to activate and show it.

E.g., from a Menu in your MDIParent Form:

public partial class MDIParent : Form
{
    private ResourceBag formResources = null;

    public MDIParent()
    {
        InitializeComponent();
        formResources = new ResourceBag([Assembly Path]);
    }

    // [...]

    // From a ToolStrip MenuItem, load with arguments
    private void loadExternalFormToolStripMenuItem_Click(object sender, EventArgs e)
    {
        object[] args = new object[1] { new string[] { "Args String", "Args Value", "Other args" } };
        Form form1 = formResources.LoadForm("Form1", args);
        form1.MdiParent = this;
        form1.Show();
    }

    // From another ToolStrip MenuItem, load using the default Constructor
    private void loadExternalFormNoParamsToolStripMenuItem_Click(object sender, EventArgs e)
    {
        Form form1 = formResources.LoadForm("Form1");
        form1.MdiParent = this;
        form1.Show();
    }

}

The ResourceBag helper class:

Maybe add an overload to public Form LoadForm(), to pass a different NameSpace, in case you want to load a class object that is not part of the default NameSpace.

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

internal class ResourceBag
{
    private string m_AssemblyName = string.Empty;
    private static Assembly asm = null;

    public ResourceBag() : this(null) { }

    public ResourceBag(string assemblyPath)
    {
        if (!string.IsNullOrEmpty(assemblyPath)) {
            this.AssemblyName = assemblyPath;
        }
    }

    public string NameSpace { get; set; }

    public string AssemblyName {
        get => m_AssemblyName;
        set {
            if (File.Exists(value)) {
                m_AssemblyName = value;
                asm = Assembly.LoadFrom(m_AssemblyName);
                this.NameSpace= asm.GetName().Name;
            }
            else {
                throw new ArgumentException("Invalid Assembly path");
            }
        }
    }

    public Form LoadForm(string formName, params object[] args)
    {
        if (asm == null) throw new BadImageFormatException("Resource Library not loaded");
        return Activator.CreateInstance(asm.GetType($"{NameSpace}.{formName}"), args) as Form;
    }
}