Guapo
Guapo

Reputation: 3482

Add new cs files on-the-fly to load on my running application?

I have a command handler which basically works like this:

ControlList.Handlers[CommandType.MyCommandComesHere].Handle(data);

Handlers is a Dictionary<CommandType, ICommandHandler> and CommandType is a enum.

Handle by its turn would lead it to this:

using System;
using log4net;

namespace My_Application
{
    public class MyCommand : ICommandHandler
    {
        private static readonly ILog Logger = LogManager.GetLogger(typeof(MyCommand));

        public void Handle(Events data)
        {
            Console.WriteLine("I can load cs files on the fly yay!!");
        }
    }
}

My question is how can I make so my application would compile and let me use that cs file while its running?

Any simple example of this would be greatly appreciated but not required as long as I can get some pointers as to what I need to look for as I am not even sure what do I need to make this happen.

To put it simple I am currently trying to understand how could I load a cs file into my application that is already compiled and is currently running.

Upvotes: 3

Views: 801

Answers (2)

Vojtěch Dohnal
Vojtěch Dohnal

Reputation: 8102

I have looked for different ways to achieve this and found cs script library lightweight and usable. Here is code snippet how I use it. It runs cs code within app domain so it presumes, that the cs script being compiled comes form trusted source.

using CSScriptLibrary;
using csscript;
using System.CodeDom.Compiler;
using System.Reflection;

    //Method example - variable script contains cs code
    //This is used to compile cs to DLL and save DLL to a defined location
    public Assembly GetAssembly(string script, string assemblyFileName)
    {
        Assembly assembly;
        CSScript.CacheEnabled = true;            
        try
        {
            bool debugBuild = false;
#if DEBUG
            debugBuild = true;
#endif
            if (assemblyFileName == null)
                assembly = CSScript.LoadCode(script, null);
            else
                assembly = CSScript.LoadCode(script, assemblyFileName, debugBuild, null);
            return assembly;
        }
        catch (CompilerException e)
        {
            //Handle compiler exceptions
        }
    }

    /// <summary>
    /// Runs the code either form script text or precompiled DLL
    /// </summary>
    public void Run(string script)
    {
        try
        {
            string tmpPath = GetPathToDLLs();  //Path, where you store precompiled DLLs

            string assemblyFileName;
            Assembly assembly = null;
            if (Directory.Exists(tmpPath))
            {
                assemblyFileName = Path.Combine(tmpPath, GetExamScriptFileName(exam));
                if (File.Exists(assemblyFileName))
                {
                    try
                    {
                        assembly = Assembly.LoadFrom(assemblyFileName); //Načtení bez kompilace
                    }
                    catch (Exception exAssemblyLoad)
                    {
                        Tools.LogError(exAssemblyLoad.Message);
                        assembly = null;
                    }
                }
            }
            else
                assemblyFileName = null;

            //If assembly not found, compile it form script string
            if (assembly ==null) 
                assembly = GetAssembly(script, assemblyFileName);
            AsmHelper asmHelper = new AsmHelper(assembly);

            //This is how I use the compiled assembly - it depends on your actual code
            ICalculateScript calcScript = (ICalculateScript)asmHelper.CreateObject(GetExamScriptClassName(exam));
            cex = calcScript.Calculate(this, exam);
            Debug.Print("***** Calculated {0} ****", exam.ZV.ZkouskaVzorkuID);
        }
        catch (Exception e)
        {
            //handle exceptions
        }
    }

Upvotes: 1

Theodoros Chatzigiannakis
Theodoros Chatzigiannakis

Reputation: 29233

Using CodeDOM, you need to first create a compiler provider. (You might want to set GenerateExecutable to false and GenerateInMemory to true for your purposes.)

    var csc = new CSharpCodeProvider();
    var parameters = new CompilerParameters(new[] { "mscorlib.dll", "System.Core.dll" }, "foo.exe", true);
    parameters.GenerateExecutable = false;
    parameters.GenerateInMemory = true;

Then, you can compile the assembly using CompileAssemblyFromSource and get the CompilerResults returned from it. From this returned object, get a reference to the generated assembly, using its CompiledAssembly property.

    var results = csc.CompileAssemblyFromSource(parameters, "contents of the .cs file");
    var assembly = results.CompiledAssembly;

Then you can use reflection to create instances from that assembly and call methods on them.

    var instance = assembly.CreateInstance("MyCommand");
    // etc...

Alternatively, if you're only interested in short code snippets, it might be worth it to use Roslyn instead. You need to create a ScriptEngine first.

var engine = new ScriptEngine();

Then you can just Execute strings on it - or Execute<T> if you're confident that the expression in the string returns a type assignable to T.

var myObject = engine.Execute("1+1");
var myInt = engine.Execute<int>("1+1");

It's definitely more immediate, so it's worth looking into if it serves your purpose.

Upvotes: 5

Related Questions