Search code examples
c#compiler-constructioncompiler-errors

Compiling and Executing single lines of code


I am creating a /kind of/ custom compiler for a project. What I am doing is having users enter lines of code into either a textbox, or they can import some from text files.

I've been trying to test this for the last few days, with no results. I have a class, called Pressure, where I have a public method called 'emit' which simply shows a text box, like so...

public void emit()
{
    MessageBox.Show("HOORA!");
}

and I have a text file named "testCompile.txt" as follows:

PressureTransducer pt = new PressureTransducer(0,0);
pt.emit();

which, when inserted into VS compiles just fine, as it should. Afterwards, I try to compile the file like so...

String sourceName = @"C:\Users\Devic\Desktop\CompileTester\testCompile.txt";

CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");

CompilerParameters cp = new CompilerParameters();
cp.GenerateExecutable = true;
//cp.OutputAssembly = null;
cp.GenerateInMemory = true;
cp.TreatWarningsAsErrors = false;
CompilerResults cr = provider.CompileAssemblyFromFile(cp,
        sourceName);
if (cr.Errors.Count > 0)
{
    // Display compilation errors.
    Console.WriteLine("Errors building {0} into {1}",
        sourceName, cr.PathToAssembly);
    foreach (CompilerError ce in cr.Errors)
    {
        Console.WriteLine("  {0}", ce.ToString());
        Console.WriteLine();
    }
}
else
{
    // Display a successful compilation message.
    Console.WriteLine("Source {0} built into {1} successfully.",
        sourceName, cr.PathToAssembly);
}

but VS gives me the error:

c:\Users\Devic\Desktop\CompileTester\testCompile.txt(1,29) : error CS1518: Expected class, delegate, enum, interface, or struct

The thread 0x1290 has exited with code 0 (0x0).

Any ideas as to what is going on?


Solution

  • You need to encapsulate the code from your text file into a usable class and method.

    Below is code I've been using for a few years that allows C# scripts to run within my app and it even passes in a user defined variable. I had other parameters being passed in within my code to let the script writer have full access to other existing class instances, but I stripped those out as they are unique to my software. You could do the same if you want to provide access to any existing classes or forms in your app.

    To use your class PressureTransducer you will need to ensure the DLL which declares that type is properly referenced and the namespace is included in the using section of the Fake code encapsulation. However I have a section built in to automatically reference all assemblies currently referenced by your running program, so that usually takes care of everything automatically.

    Also, this takes the code in as a string for the source code and generates the assembly into memory, so there is no disk access - it runs very fast.

    NOTE: There is use of an obsolete function in there, codeProvider.CreateCompiler();, but it's still working for me. I probably should update it eventually though.

    private static object RunCSharpCode(string CSharpCode, bool ShowErrors, string StringParameter)
    {
        try
        {
            #region Encapsulate Code into a single Method
            string Code =
                "using System;" + Environment.NewLine +
                "using System.Windows.Forms;" + Environment.NewLine +
                "using System.IO;" + Environment.NewLine +
                "using System.Text;" + Environment.NewLine +
                "using System.Collections;" + Environment.NewLine +
                "using System.Data.SqlClient;" + Environment.NewLine +
                "using System.Data;" + Environment.NewLine +
                "using System.Linq;" + Environment.NewLine +
                "using System.ComponentModel;" + Environment.NewLine +
                "using System.Diagnostics;" + Environment.NewLine +
                "using System.Drawing;" + Environment.NewLine +
                "using System.Runtime.Serialization;" + Environment.NewLine +
                "using System.Runtime.Serialization.Formatters.Binary;" + Environment.NewLine +
                "using System.Xml;" + Environment.NewLine +
                "using System.Reflection;" + Environment.NewLine +
    
                "public class UserClass" + Environment.NewLine +
                "{" + Environment.NewLine +
                "public object UserMethod( string StringParameter )" + Environment.NewLine +
                "{" + Environment.NewLine +
                "object Result = null;" + Environment.NewLine +
                Environment.NewLine +
                Environment.NewLine +
    
                CSharpCode +
    
                Environment.NewLine +
                Environment.NewLine +
                "return Result;" + Environment.NewLine +
                "}" + Environment.NewLine +
                "}";
            #endregion
    
            #region Compile the Dll to Memory
    
            #region Make Reference List
            Assembly[] FullAssemblyList = AppDomain.CurrentDomain.GetAssemblies();
    
            System.Collections.Specialized.StringCollection ReferencedAssemblies_sc = new System.Collections.Specialized.StringCollection();
    
            foreach (Assembly ThisAssebly in FullAssemblyList)
            {
                try
                {
                    if (ThisAssebly is System.Reflection.Emit.AssemblyBuilder)
                    {
                        // Skip dynamic assemblies
                        continue;
                    }
    
                    ReferencedAssemblies_sc.Add(ThisAssebly.Location);
                }
                catch (NotSupportedException)
                {
                    // Skip other dynamic assemblies
                    continue;
                }
            }
    
            string[] ReferencedAssemblies = new string[ReferencedAssemblies_sc.Count];
            ReferencedAssemblies_sc.CopyTo(ReferencedAssemblies, 0);
            #endregion
    
            Microsoft.CSharp.CSharpCodeProvider codeProvider = new Microsoft.CSharp.CSharpCodeProvider();
            System.CodeDom.Compiler.ICodeCompiler CSharpCompiler = codeProvider.CreateCompiler();
            System.CodeDom.Compiler.CompilerParameters parameters = new System.CodeDom.Compiler.CompilerParameters(ReferencedAssemblies);
            parameters.GenerateExecutable = false;
            parameters.GenerateInMemory = true;
            parameters.IncludeDebugInformation = false;
            parameters.OutputAssembly = "ScreenFunction";
    
            System.CodeDom.Compiler.CompilerResults CompileResult = CSharpCompiler.CompileAssemblyFromSource(parameters, Code);
            #endregion
    
            if (CompileResult.Errors.HasErrors == false)
            { // Successful Compile
                #region Run "UserMethod" from "UserClass"
                System.Type UserClass = CompileResult.CompiledAssembly.GetType("UserClass");
                object Instance = Activator.CreateInstance(UserClass, false);
                return UserClass.GetMethod("UserMethod").Invoke(Instance, new object[] { StringParameter });
                #endregion
            }
            else // Failed Compile
            {
                if (ShowErrors)
                {
                    #region Show Errors
                    StringBuilder ErrorText = new StringBuilder();
    
                    foreach (System.CodeDom.Compiler.CompilerError Error in CompileResult.Errors)
                    {
                        ErrorText.Append("Line " + (Error.Line - 1) +
                            " (" + Error.ErrorText + ")" +
                            Environment.NewLine);
                    }
    
                    MessageBox.Show(ErrorText.ToString());
                    #endregion
    
                }
            }
        }
        catch (Exception E)
        {
            if (ShowErrors)
                MessageBox.Show(E.ToString());
        }
    
        return null;
    }