Run user-entered code that modifies the program’s form in C#

[user-entered code]

The example Run user-entered code in C# shows how you can compile and execute code entered at run time. That example simply displays a message box and returns a DialogResult indicating which button the user pressed.

This example shows how code entered at run time can interact with the main form.

Like the previous example, this one uses the following using directives. The main differences between this code and the previous version are highlighted in blue.

using System.CodeDom.Compiler;
using System.Reflection;

When you click the Run button, the following code executes.

// Compile and execute the code.
private void btnRun_Click(object sender, EventArgs e)
{
    txtResults.Clear();
    CodeDomProvider code_provider = CodeDomProvider.CreateProvider("C#");

    // Generate a non-executable assembly in memory.
    CompilerParameters parameters = new CompilerParameters();
    parameters.GenerateInMemory = true;
    parameters.GenerateExecutable = false;

    // Add references used by the code. (This one is used by MessageBox.)
    parameters.ReferencedAssemblies.Add("System.Windows.Forms.dll");
    parameters.ReferencedAssemblies.Add("System.dll");
    parameters.ReferencedAssemblies.Add(
        Assembly.GetEntryAssembly().Location);

    // Compile the code.
    CompilerResults results =
        code_provider.CompileAssemblyFromSource(parameters, txtCode.Text);

    // If there are errors, display them.
    if (results.Errors.Count > 0)
    {
        foreach (CompilerError compiler_error in results.Errors)
        {
            txtResults.AppendText(
                "Line: " + compiler_error.Line + ", " +
                "Error Number: " + compiler_error.ErrorNumber + ", " +
                compiler_error.ErrorText + "\n");
        }
    }
    else
    {
        // There were no errors.
        txtResults.Text = "Success!";

        // Get the compiled method and execute it.
        foreach (Type a_type in results.CompiledAssembly.GetTypes())
        {
            if (!a_type.IsClass) continue;
            if (a_type.IsNotPublic) continue;

            // Get a MethodInfo object describing the SayHi method.
            MethodInfo method_info = a_type.GetMethod("ModifyForm");
            if (method_info != null)
            {
                // Make the parameter list.
                object[] method_params = new object[] { this };

                // Execute the method.
                try
                {
                    method_info.Invoke(null, method_params);

                    // Display a success message.
                    MessageBox.Show("OK");
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message + '\n' +
                        ex.InnerException.Message + '\n' +
                        ex.InnerException.StackTrace);
                }
            }
        }
    }
}

See the previous example for the basic idea.

This version adds two new references to the compiled code. First, it adds a reference to System.dll. That is necessary because the code entered at run time uses the program’s main form, which has type Form1. That form is a System.Windows.Form object, and that class inherits from System.ComponentModel.Component. The code needs the reference to System.dll so it can understand the Form1 class’s complete inheritance hierarchy.

Second, the code includes a reference to the executing form’s assembly, which defines the Form1 class.

The code continues as in the previous version by compiling the user-entered code and checking for errors. It then loops through the results looking for a public class that has a method named ModifyForm.

The program then makes an array of parameters to pass into the ModifyForm method. This time the program will pass a reference to the program’s main form to the method so it places this in the parameter array.

The code then invokes the method. In this example the method doesn’t return anything so the program doesn’t look for a return result. (In general it might be better to have the method at least return a flag indicating success or failure.)

There’s one final detail. In this example the sample code moves the form’s btnRun button. By default in C# all controls are private so only the code inside the form can see them. To allow the script code to see the button, I set its Modifiers property to Public at design time. If you want to allow the script to modify other controls on the form, you would need to make them similarly Public.

Another approach would be to define a public DoSomething method in the form. Then the script code could simply invoke that method. Because that method is defined inside the form, it has access to all of the form’s controls even if they are private.

Of course then the script code couldn’t determine what actions are taken at run time. It would just call DoSomething and that method would know what to do.


Download Example   Follow me on Twitter   RSS feed   Donate




About RodStephens

Rod Stephens is a software consultant and author who has written more than 30 books and 250 magazine articles covering C#, Visual Basic, Visual Basic for Applications, Delphi, and Java.

This entry was posted in algorithms, miscellany, programs, reflection and tagged , , , , , , , , , , , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *