1

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
    Define *"but it didn't work"* in this context. – Flydog57 Jul 13 '20 at 00:30
  • @Flydog57 I tried it object objForm = null; System.Reflection.Assembly assembly = AppDomain.CurrentDomain.Load( File.ReadAllBytes(@"D:\Projetos C#\XMLValidator\XMLValidator\CompareXMLTools.exe")); objForm = assembly.CreateInstance("CompareXMLTools.Main", true, System.Reflection.BindingFlags.Default, null, new object[] { }, null, null); return an error (**System.MissingMethodException: 'Constructor in type 'CompareXMLTools.Main' not found.'**) The Project B wait for arguments on startup. **namespace CompareXMLTools { public partial class Main : Form {.......** – Marcos Aurelio Jul 13 '20 at 02:22
  • Is Main is a method and not a type? I'm curious why you didn't just use `Assembly.Load`. You should put this code in your question (you can edit it) – Flydog57 Jul 13 '20 at 03:00
  • @Marcos **Please edit the information you've posted in these comments into your question.** – Ian Kemp Jul 13 '20 at 12:24
  • @Ian Kemp I changed it, it's better now? – Marcos Aurelio Jul 13 '20 at 13:02
  • Without knowing exactly why you require this setup, it would seem that an architecture refactor would be a better solution. Something along the lines of having three projects within a solution- project a and project b would reference the shared codebase within project c. This would eliminate having to load the assembly externally. – Andy Stagg Jul 13 '20 at 17:52

1 Answers1

0

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;
    }
}
Jimi
  • 29,621
  • 8
  • 43
  • 61
  • Thank you @Jimi. After analyzing your code, I discovered my mistake. I was creating the wrong arg params, after I used your params object declaration the code worked. `object[] args = new object[1] { new string[] { "Args String", "Args Value", "Other args" } };` `AssemblyObjForm = Form)AssemblyObject.CreateInstance("CompareXMLTools.Main",true, System.Reflection.BindingFlags.Default, null, args, System.Globalization.CultureInfo.CurrentCulture, null);` `AssemblyObjForm.MdiParent = this;` `AssemblyObjForm.show();` – Marcos Aurelio Jul 14 '20 at 21:04