Run user-entered code in C#


[user-entered code]

This example shows how to compile and run user-entered code at run time. The form includes the following using statements.

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

The following code executes when you click the Run button.

// 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");

    // 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("SayHi");
            if (method_info != null) 
            {
                // Make the parameter list.
                object[] method_params = new object[]
                {
                    "This is the parameter string. Isn't it great?"
                };

                // Execute the method.
                DialogResult method_result =
                    (DialogResult)method_info.Invoke(null,
                        method_params);

                // Display the returned result.
                MessageBox.Show(method_result.ToString());
            }
        }
    }
}

The code first clears any old results. It then creates a CodeDomProvider to work with C# code. (My next post will show how to figure out what other languages you can use.)

Next the code creates a parameters object. It sets the GenerateInMemory property to true to indicate that the compiler should compile into memory (as opposed to creating a compiled file). It sets the GenerateExecutable property to false to indicate that it should create an assembly that this program can invoke (as opposed to a standalone executable).

The program then adds a reference to the System.Windows.Forms library that the code needs to use MessageBox.Show. If the user-entered code needs other libraries, you should include them here.

Now the program compiles the user-entered code. It calls the code provider’s CompileAssemblyFromSource method passing it the parameters selected by the code and the user-entered code. When the CompileAssemblyFromSource method returns, the program examines the results returned by the method.

If the results indicate errors, the program loops through the errors and displays their line numbers, error codes, and error messages. (You should try introducing an error into the code to see what happens. In a real application, you might list the errors in a ListBox and jump to the appropriate line when the user clicks on one.

If there are no errors in the user-entered code, then the code has been compiled successfully so the program displays a success message. It then uses reflection to examine the object types created by the compiled code. It looks for a class that is public and tries to create a MethodInfo object describing that class’s SayHi method. That is just the convention that I decided to use. You can look for whatever class and method you find appropriate for your program.

If the program succeeds in creating the MethodInfo object, then the SayHi method exists for this class and the program invokes it. The code creates an array to hold parameters for the method, in this case a single string. It then calls the MethodInfo object’s Invoke method, passing it the array of parameters. The null used as the first parameter would represent the object for which the method should be invoked, but the SayHi method defined in this example is a static method so it doesn’t need an object. This example’s SayHi method returns a DialogResult (to show which button the user clicks on the MessageBox) so the program casts the return result into this data type and displays it.

The following code shows the text that the program initially uses for its code entered at run time.

using System.Windows.Forms;

public static class MyScriptClass
{
    public static DialogResult SayHi(string msg)
    {
        return MessageBox.Show(msg, "Message",
            MessageBoxButtons.YesNoCancel);
    }
}

This code defines a static class named MyScriptClass. That class defines the single static method SayHi. The SayHi method displays whatever parameter it is passed and returns the DialogResult returned by the call to MessageBox.Show.

Note: You should be very wary of user-entered code. The user-entered code could potentially perform any actions that the user could. For example, the code might be able to download and install a virus, erase a hard drive, or perform other destructive actions. It might make sense to let the program execute scripts that you have written or that a power user writes, but you probably should not execute any old code that you find floating around on the Internet without carefully examining it first.


Download Example   Follow me on Twitter   RSS feed   Donate




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

12 Responses to Run user-entered code in C#

  1. Sandra says:

    What if in method_params invalid arguments are passed, for example something resulting in an index outside bounds error? While CompilerErrors include lines MethodInfo.Invoke just throws a TargetInvocationException.

    Any ideas how to return the exact line of the source, too?

  2. Rod Stephens says:

    Good question. The example deals with syntax errors well but what if the method compiles okay and then fails at run time.

    Unfortunately the only thing I know to do is to wrap the call in a try-catch block and then look at the exception’s InnerException property. That will tell you what went wrong, although as far as I know it won’t tell you the line number.

    You can also look at the InnerException’s StackTrace property to see what method you are in, but that won’t give you the line number either.

    Here’s the code:

     // Execute the method.
    try
    {
    DialogResult method_result =
    (DialogResult)method_info.Invoke(null, method_params);
    // Display the returned result.
    MessageBox.Show(method_result.ToString());
    }
    catch (Exception ex)
    {
    MessageBox.Show(ex.Message + '\n' +
    ex.InnerException.Message + '\n' +
    ex.InnerException.StackTrace);
    } 
  3. Sandra says:

    Thank you, but this is no solution to my problem, as the inner exception is not helpful at all if the sourcecode includes bigger codeblocks that might be the problem. I need to identify at least the line or the method with argument in the source.

  4. Rod Stephens says:

    Yes, I said it didn’t do it, but it’s the best we can do as far as I know. The inner exception’s stack trace does identify the method that threw the exception but doesn’t give the line number.

  5. Sandra says:

    Maybe you are interested in, too. Or someone else will run into a similar problem and find this via google. So here is what I found out in the meanwhile: The stacktrace of the inner exception will contain the exact line number of the source if the following CompilerParameters are set:

    GenerateInMemory = false;
    TempFiles.KeepFiles = true;
    IncludeDebugInformation = true;

    An unwanted sideeffect is an “unhandled exception” (while everything is wrapped in fact) that will be thrown now. Workaround: Disable Just My Code in Tools -> Options -> Debugging

  6. Thanks for this fantastik kode. Exactly what i was looking for.

  7. Sony says:

    how to execute method
    use without DialogResult

  8. Sony says:

    thank you very much : )

Leave a Reply

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