📜 ⬆️ ⬇️

"Dream lazy" or the script engine on itself

Developers of application software very often have the need to build in their product a kind of scripting language that would solve some of the tasks that are not described in detail at the time of system design. Really convenient: the possibility of expanding the functionality is there, and the complexity of creating such a solution is, at first glance, not great.

This old dream could be called a “dream of a bummer” if the existing publicly available embedded scripting tools would be simple. Finished tools existed for a long time, for example, on the Windows platform, in the last century it was possible to use 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.

Scripts work fine on their own, you can perform both logic and arithmetic on them, but there is absolutely no use for them if it is difficult or not possible:
')
• add functions and objects to access objects of the developed system,
• conduct syntax control of the source script and generate syntax error messages,
• run the script step by step, 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 documentation on the new API. Alas, this is not always possible and very rarely.

Application software is now often written in C #, and I would like to have something familiar, but flexible, and allows you to write scripts. There is such a solution, and it deserves close attention. This namespace System.CodeDom.Compiler with its class CSharpCodeProvider. All this appeared in .NET 4.0, but for some reason most of the publications in C # did not address the issue of writing scripts in C #, using the very same C # language as the base. And it is very, very convenient for writing and further support of 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:

image

So, in its simplest form, the 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 the string type is quite suitable for 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:

image

It can be seen that now we can transfer and return values ​​from the script code. If, as a parameter, we transfer not a link to a string, but some internal object of the information system, then we can well influence the system from the script.

This mechanism has a debugger runtime mode, for this you need to connect a .pdb file, there are many other interesting features.

The disadvantage of the approach can be considered only that when compiling dll is created in the OS temporary directory.

The way to resolve this deficiency leads us to the use of the space System.Reflection.Emit, 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 write independently. But what opportunities to invent 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

Source: https://habr.com/ru/post/327096/


All Articles