0

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?

5
  • I don't know anything about writing your own compiler, but it looks like the code file you're reading doesn't know anything about the PressureTransducer class. Either declare the class in the file you're compiling, or import the class somehow. Commented Dec 21, 2015 at 3:21
  • And the code in your file doesn't exist in a class. Perhaps you need a main method? Commented Dec 21, 2015 at 3:26
  • So would I need to write a self-executing class? Commented Dec 21, 2015 at 3:30
  • You don't have the benefit of a .csproj file, so I would assume it would have to be self-contained or else import somehow. It doesn't know about the existence of any other class. That said, this is all speculation, I've never used these features. Commented Dec 21, 2015 at 3:34
  • 1
    The error message seems pretty clear: "Expected class, delegate, enum, interface, or struct". You don't have any of those things; you have statements. Wrap them in a method, and wrap that in a class, so it's a valid C# code file. Commented Dec 21, 2015 at 3:50

2 Answers 2

2

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;
}
Sign up to request clarification or add additional context in comments.

5 Comments

Couple questions if you dont mind me asking... 1. when you output your assembly, is "ScreenFunction" something I should change? 2. where would I be inserting code to allow for my custom classes? Or is that taken care of when you iterate through the referenced assemblies?
1)ScreenFunction is just the name of the compiled assembly. I used it because this is a script class for my dynamic forms I call: "Screens" 2)You'll need to ensure those classes are in another project/dll which your main project references and also add a 'using' into the Encapsulate section. (The compiler will need to be able to access the dll like any other project reference/dll, so you cannot have your classes defined in your calling exe project. However you can make an Interface class in another project/dll and pass in your local class providing it implements that interface.)
Just as a sidenote also, I tested this and it works perfectly, if it doesn't need an external class (Like my pressuretransducer class). If so, it just errors. But I'm confused as my PT class is in the same namespace of the rest of the project. Or does that not even matter?
So would if my namespace is CompilerNamespace, would I be 'using ComilerNamespace;' or 'using CompilerNamespace.PressureTransducer;'?
The likely problem is your pressuretransducer class is in your calling program. You need to move that to another project and then add a reference to that project from both your code and also the compiler within this function. If you build the reference into your main exe project, then my code SHOULD automatically add it into the compiler. But it needs separated out into it's own project/dll and referenced in your main project still..
1

You might consider looking at the new Roslyn compiler. You pass a string to the Execute method in the script engine class and it will execute the code on-the-fly.

public class CSharpScriptEngine
{
    private static Script _previousInput;
    private static Lazy<object> _nextInputState = new Lazy<object>();
    public static object Execute(string code)
    {
        var script = CSharpScript.Create(code, ScriptOptions.Default).WithPrevious(_previousInput);
        var endState = script.Run(_nextInputState.Value);
        _previousInput = endState.Script;
        _nextInputState = new Lazy<object>(() => endState);
        return endState.ReturnValue;
    }
}

See this article for credit and a complete implementation.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.