Compile code entered at run time, execute it, and get the return result in C#

This example uses 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 am 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 code entered at runtime uses other libraries, you should include them here.

Now the program compiles the code. It calls the code provider's CompileAssemblyFromSource method passing it the parameters selected by the code and the code entered at run time. When the code returns, in this example after the user closes the MessageBox, the program saves the results in a CompilerResults object.

If the results object's Errors collection is not empty, the program displays each error's line number, error code, and error text. (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 clicks on one.

If there were no errors, the code has been compiled successfully so the program displays a success message. It then uses reflection to examine the types provided 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.

If it 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 wary of the source for code executed at run time. For example, if the user enters this code (as you can in this example), then the program 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 execute any old code that you find floating around on the Internet without carefully examining it first.

   

 

What did you think of this article?




Trackbacks
  • No trackbacks exist for this post.
Comments

  • 5/11/2012 1:18 PM Sandra wrote:
    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?
    Reply to this
    1. 5/11/2012 3:05 PM Rod Stephens wrote:
      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);
      } 

      Reply to this
  • 5/11/2012 3:52 PM Sandra wrote:
    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.
    Reply to this
  • 5/12/2012 6:01 AM Rod Stephens wrote:
    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.
    Reply to this
  • 5/12/2012 6:57 AM Sandra wrote:
    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
    Reply to this
  • 1/14/2013 9:03 AM Jakup W Hansen wrote:
    Thanks for this fantastik kode. Exactly what i was looking for.
    Reply to this
Leave a comment

Submitted comments are subject to moderation before being displayed.

 Name

 Email (will not be published)

 Website

Your comment is 0 characters limited to 3000 characters.