
"Dream of a lazy person" or a scripting engine on itself
Application developers often need to embed a scripting language in their product that would solve some of the tasks that were not described in detail at the time of system design. It’s really convenient: there is a possibility of expanding functionality, and the laboriousness of creating such a solution is, at first glance, small.
This old dream could be called the “lazy dream” if the available public embedded scripting tools were simple. Ready-made tools have existed for a long time, for example, on the Windows platform; back in the last century, you could use the VBScript and Jscript interfaces through the IActiveScriptSite COM interface. Currently, there are a large number of other solutions, for example, based on Lua, but they all have one unpleasant feature that greatly limits the desire to use them.
The scripts work fine on their own, they can perform both logic and arithmetic, but there is absolutely no benefit from them if it is difficult or not possible:
• add functions and objects to access the objects of the developed system,
• perform syntactic control of the source script and generate messages about syntax errors,
• execute the script in a step-by-step mode, like a debugger, with notification of the execution point and status.
And yet, I would like it to be done all this simply and intuitively and would not have to spend sleepless nights reading numerous documents on the new API. Alas, this is far from always successful and very infrequent.
Application software is now very often written in C #, and I would like to have something familiar, but flexible, and allowing you to write scripts. There is such a solution, and it deserves close attention. This is the System.CodeDom.Compiler namespace with its CSharpCodeProvider class. All this appeared back in .NET 4.0, but for some reason, most C # publications did not address the issue of writing scripts in C #, using C # itself as the base language. And it is very, very convenient for writing and further maintaining the product.
In this case, the most important and interesting method is CompileAssemblyFromSource (), which compiles, generates error messages, and we can easily write “Hello world!”
Run for execution:

So, in its simplest form, a C # script works successfully. By simply changing the script text, we can influence its operation. Actually, it remains only to transfer to the script as an example any object from the main program.
An object of type string is quite suitable as such an object:
Run for execution:

It can be seen that now we can transfer and return values from the script code. If we pass as a parameter not a link to a string, but some internal object of the information system, then we can well act on the system from a script.
This mechanism has a runtime in debug mode, for this you need to connect a .pdb file, there are many other interesting features.
The only drawback of this approach is that during compilation a dll is created in the OS temporary directory.
The way to resolve this shortcoming leads us to use the System.Reflection.Emit space, but this is a rather voluminous material suitable for a separate article. This is difficult, because in this case, the compiler and generation will have to be written independently. But what opportunities are there for inventing your own syntax! Yes, and you can name a new programming language in honor of yourself or your favorite cat.
Good luck
Arkady Pchelintsev, project architect
This old dream could be called the “lazy dream” if the available public embedded scripting tools were simple. Ready-made tools have existed for a long time, for example, on the Windows platform; back in the last century, you could use the VBScript and Jscript interfaces through the IActiveScriptSite COM interface. Currently, there are a large number of other solutions, for example, based on Lua, but they all have one unpleasant feature that greatly limits the desire to use them.
The scripts work fine on their own, they can perform both logic and arithmetic, but there is absolutely no benefit from them if it is difficult or not possible:
• add functions and objects to access the objects of the developed system,
• perform syntactic control of the source script and generate messages about syntax errors,
• execute the script in a step-by-step mode, like a debugger, with notification of the execution point and status.
And yet, I would like it to be done all this simply and intuitively and would not have to spend sleepless nights reading numerous documents on the new API. Alas, this is far from always successful and very infrequent.
Application software is now very often written in C #, and I would like to have something familiar, but flexible, and allowing you to write scripts. There is such a solution, and it deserves close attention. This is the System.CodeDom.Compiler namespace with its CSharpCodeProvider class. All this appeared back in .NET 4.0, but for some reason, most C # publications did not address the issue of writing scripts in C #, using C # itself as the base language. And it is very, very convenient for writing and further maintaining the product.
In this case, the most important and interesting method is CompileAssemblyFromSource (), which compiles, generates error messages, and we can easily write “Hello world!”
using System;
using System.IO;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// готовим текст скрипта
StringBuilder sb = new StringBuilder();
sb.AppendLine("using System;");
sb.AppendLine("namespace ConsoleApplication1");
sb.AppendLine("{");
sb.AppendLine(" public class MyScripter");
sb.AppendLine(" {");
sb.AppendLine(" public void Hello()");
sb.AppendLine(" {");
sb.AppendLine(" Console.WriteLine(\"Hello world!\");");
sb.AppendLine(" }");
sb.AppendLine(" }");
sb.AppendLine("}");
// компилируем
CSharpCodeProvider codeProvider = new CSharpCodeProvider();
CompilerResults compileResults = codeProvider.CompileAssemblyFromSource(
new CompilerParameters(), new string[] { sb.ToString() });
// выводим ошибки, если они есть
foreach (CompilerError err in compileResults.Errors)
Console.WriteLine("Error({0:1}): {2} {3}", err.Line, err.Column,
err.ErrorNumber, err.ErrorText);
if (compileResults.Errors.HasErrors) return;
// загружаем получившуюся dll в память
byte[] dllBytes = File.ReadAllBytes(compileResults.PathToAssembly);
Assembly asmDll = Assembly.Load(dllBytes, null);
Type objType = asmDll.GetType("ConsoleApplication1.MyScripter");
// создаём объект класса из скрипта
object oClassInst = Activator.CreateInstance(objType);
// получаем точка входа и выполняем её
MethodInfo entry = objType.GetMethod("Hello", new Type[] {});
entry.Invoke(oClassInst, null);
}
}
}
Run for execution:

So, in its simplest form, a C # script works successfully. By simply changing the script text, we can influence its operation. Actually, it remains only to transfer to the script as an example any object from the main program.
An object of type string is quite suitable as such an object:
using System;
using System.IO;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string sMyStr = "Before Script.";
// готовим текст скрипта
StringBuilder sb = new StringBuilder();
sb.AppendLine("using System;");
sb.AppendLine("namespace ConsoleApplication1");
sb.AppendLine("{");
sb.AppendLine(" public class MyScripter");
sb.AppendLine(" {");
sb.AppendLine(" public void Hello(ref string s)");
sb.AppendLine(" {");
sb.AppendLine(" Console.WriteLine(\"Hello world!\");");
sb.AppendLine(" s=\"After Script.\";");
sb.AppendLine(" }");
sb.AppendLine(" }");
sb.AppendLine("}");
// компилируем
CSharpCodeProvider codeProvider = new CSharpCodeProvider();
CompilerResults compileResults = codeProvider.CompileAssemblyFromSource(
new CompilerParameters(), new string[] { sb.ToString() });
// выводим ошибки, если они есть
foreach (CompilerError err in compileResults.Errors)
Console.WriteLine("Error({0:1}): {2} {3}", err.Line, err.Column,
err.ErrorNumber, err.ErrorText);
if (compileResults.Errors.HasErrors) return;
// загружаем получившуюся dll в память
byte[] dllBytes = File.ReadAllBytes(compileResults.PathToAssembly);
Assembly asmDll = Assembly.Load(dllBytes, null);
Type objType = asmDll.GetType("ConsoleApplication1.MyScripter");
// создаём объект класса из скрипта
object oClassInst = Activator.CreateInstance(objType);
// получаем точка входа и готовим параметры
MethodInfo entry = objType.GetMethod("Hello",
new Type[] { typeof(string).MakeByRefType() });
Object[] param = new Object[] { sMyStr };
Console.WriteLine(param[0]); // до выполнения скрипта
entry.Invoke(oClassInst, param); // вызов метода
Console.WriteLine(param[0]); // после выполнения скрипта
}
}
}
Run for execution:

It can be seen that now we can transfer and return values from the script code. If we pass as a parameter not a link to a string, but some internal object of the information system, then we can well act on the system from a script.
This mechanism has a runtime in debug mode, for this you need to connect a .pdb file, there are many other interesting features.
The only drawback of this approach is that during compilation a dll is created in the OS temporary directory.
The way to resolve this shortcoming leads us to use the System.Reflection.Emit space, but this is a rather voluminous material suitable for a separate article. This is difficult, because in this case, the compiler and generation will have to be written independently. But what opportunities are there for inventing your own syntax! Yes, and you can name a new programming language in honor of yourself or your favorite cat.
Good luck
Arkady Pchelintsev, project architect