📜 ⬆️ ⬇️

Scripts in .NET / Mono by means of the platform itself

Introduction


Working here on one project, it took me that the project's functionality would expand on the fly and third-party developers, with the possibilities for expansion being as much as possible, with the possibility of editing the code on the fly. Accordingly, the plugins for this were not very suitable because of the need for their constant recompilation after any changes. Exit: scripts. Before that, I worked with scripts a long time ago and it was Lua in C ++. A good option, if not for a few drawbacks:

It was then that I wondered - maybe there is something in .NET / Mono to implement the scripts? And the answer was yes. The namespace " System.CodeDom.Compiler " was just what I needed - the ability to implement scripts that maximally connected to the .NET / Mono environment. True, if you look at my scripting mechanism from the inside, then it turns out and not even scripts, but the code written in C # is simply dynamically compiled and loaded into memory for execution. However, despite even such a "fake", I achieved the result - I could edit the code, recompiling it right on the fly in my application. And at the same time it will work even on machines where Visual Studio and other development tools are not installed, since compilers, at least, C # and VB.NET go straight along with .NET and Mono.
An example, which is discussed in the article, you can download here: http://zebraxxl.programist.ru/ScriptsInDotNet.zip

Namespace " System.CodeDom.Compiler "


Actually the most important thing in this article. It is here that classes are assembled for compiling code in a .NET / Mono assembly. The main class performing the work is the CodeDomProvider class. In essence, this is an interface above the compiler, which simply prepares the data for compilation and calls the compiler of the selected language with the necessary parameters. Well, let's start in order.

And what languages ​​do we support at all?


To figure this out, just call the static method CodeDomProvider.GetAllCompilerInfo . This method will return a CompilerInfo array. In theory, of course, it may be that a single compiler supports several languages ​​at once, but in practice I haven’t met this yet. But just in case of “multilingualism” of the compiler in .NET / Mono, it is done in such a way that first we get information about individual compilers, and only then we see who supports which language. Here are examples of the output of this information from the example:
 0 compiler languages: c# cs csharp 1 compiler languages: vb vbs visualbasic vbscript 2 compiler languages: js jscript javascript 3 compiler languages: vj# vjs vjsharp 4 compiler languages: c++ mc cpp 

As can be seen here - in fact, I have 5 languages ​​at my disposal: C #, VB.NET, J #, JScript, C ++. The same conclusion shows that each compiler serves one language.
Here you can also add that on other computers (without Visual Studio installed - my friend's machine, which has nothing to do with IT and my Ubuntu server is 11.04, Mono from the repository (2.6.7)) produces the same result.
And what does the support of many languages ​​give us? And it gives us the fact that we will be able to write scripts in various languages ​​and at the same time without thinking about their processing them in the application. And this in turn increases the level of entry of third-party developers.
')

And now she is! Compilation


Well, now try to compile something. Let's write some test class in C #. It can be found in the file "TestScript.cs". The class itself is fairly simple - just two methods. One is static, the other is not. Both simply output text to the console and return a string. Also in static one parameter is transferred.
To start compiling we need to get something to compile. To do this, run the static method " CodeDomProvider.CreateProvider ". As a parameter, it takes the name of the language in which we compile. We received the names in the previous step.
The next step is to fill in the compilation parameters. All parameters are stored in the " CompilerParameters " class. The most interesting fields:

Well, now everything is simple - we call CodeDomProvider.CompileAssemblyFrom * depending on where we get the source. There are three options:

The first parameter in all these methods is the " CompilerParameters " parameters. As a result, we get " CompilerResults ". From here you can get all the information about how the compilation went. The most interesting and necessary fields " CompilerResults ":

So, if CompiledAssembly is not null (that is, the build was compiled successfully), then there we can already use our script as a fully-fledged .NET / Mono build, accessing the functions and fields we need via reflection. I will not dwell on this part in more detail - after all, this is another topic.

Are the scripts simpler?


It is certainly good to use such a powerful language as scripts - it opens up tremendous opportunities for expanding the functionality of the application, but in some cases it can be a nailing microscope. The difference in the two variants of the complexity of the code is obvious:
 using System; namespace Script { public class Script { public static void M() { Console.WriteLine(“Hello world”); } } } 

Or this option:
 WriteLine(“Hello World”); 

Two codes that, in principle, do the same thing (provided that in the second version, WriteLine is meant as a call to System.Console.WriteLine ) - but as they say: "Feel the difference."
There are many ways to achieve this simplification. And each of them will have its own advantages and disadvantages. However, they will all be reduced to one thing: to the dynamic generation of a “big” code from a “small” one. For example, I took the easiest way to come up with:
  1. I created a separate class (hereinafter the implementation class) which contains all the methods that are global in the scripts (in the example, the MiniScriptWorker class).
  2. Created a template for generating “large” code - one child class of the implementation class with one empty method (file “ScriptTemplate.cs”)
  3. The “small” code is simply inserted into the implementation of that empty method of the template class (the “MiniScript.cs” file)


Conclusion


It is in this simple way that we get powerful enough scripts practically free of charge in the .NET / Mono application without having to pull additional dependencies. Yes - in some cases, this method will probably even be redundant, but everything still depends on the task - in my case, this implementation was the best and most convenient.
Once again about the advantages of this method: as scripts you will have a powerful language with almost complete access to the .NET / Mono environment (it’s up to you to decide which builds will be available during compilation during compilation). Also, simply by adding your application to the list of assemblies for the script, you will get almost complete integration with your application and the ability to modify it the way you, or the developer of the script, want.
However, from this grow and cons. Sometimes it may be necessary to hide some piece of the main application from the script. It can be protected from this by hiding such parts in the scope of " internal ". True, this protection can be circumvented with another problem — security suffers because of such wide access to the .NET / Mono environment. For example, we can not prevent anything using System.IO.FileStream to open any file with passwords and then quietly send its contents somewhere in the wrong hands. Unfortunately, I do not have a solution to this problem yet - I did not have time to sort the issue in more detail.

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


All Articles