📜 ⬆️ ⬇️

Inheriting the .NET type from a JavaScript object with overloads and private methods

Yes, that's exactly the way no tricks are. This idea came to my head about two months ago in the process of thinking about an article on Algorithms and Solutions . The .NET types in that engine are easy to use, but is it possible to do the opposite ...

Little about the engine
In the previous article I tried to write less about features and more about what might be useful in isolation from scripting in general, so that the article was less similar to self-promotion. Literally, from the first comments, I realized that this was a mistake.
Speed
He is fast. Very fast Over the past month, a lot of code has been cracked and a number of special cases analyzed and conclusions made. For example, depending on how the function is written and what language tools are used, it can be called up in one of more than 10 scenarios from equivalent inline to fair allocation of memory for each variable and argument and full context initialization.
Easy integration
The entire heavy "kitchen" to provide access to platform types is hidden behind one modest function.
JSObject TypeProxy.Proxy(object) 

You simply give away almost any object and assign the result to a variable.
 var script = new Script(" megaObject.alert('Hello from javascript') "); script.Context.DefineVariable("megaObject") .Assign(TypeProxy.Proxy(new { alert = new Action<string>(x => MessageBox.Show(x)) }); script.Invoke(); 

For the types that implement the IList interface, there is a special wrapper NiL.JS.Core.TypeProxing.NativeList , which masks such an object as a native js array.
You can register a type constructor and create objects already during the execution of the script. A variable with the type name is added.
 script.Context.AttachModule(typeof(System.Windows.Forms.Form)); 

If you are too lazy to add types one at a time, you can add a whole namespace
 Context.GlobalContext.DefineVariable ("forms") //  ,      . .Assign(new NamespaceProvider ("System.Windows.Forms")); //  ,     . 

Not only performance
Each syntax tree node is accessible from outside the assembly. You can implement your virtual machine, a translator into another language, a static analyzer (if it is not integrated enough) or something else that your imagination is capable of. Each use of all variables stores a reference to the corresponding descriptor, which can tell some information about it. For example, show all the locations of use that have “survived” after optimization. A couple of weeks ago, the so-called Visitor was implemented with the help of which everything listed above is made even easier.

General scheme


In order to inherit a type in the .NET platform, you need an assembly lying on a disk that stores information about the type (metadata). But while the JS file is on the disk, there are no types there. They will appear there only at runtime, when the logic of this script divides functions into, in fact, functions and constructors. The solution to this snag was found almost immediately - I added a function to the global context that takes a constructor as input ("registerClass"). Thus, I, as it were, ask me to show for which js functions it is worth generating metadata. However, this requires idle start.
After execution is over, using System.Reflection.Emit creates a saved assembly in which one class is declared for each registered constructor, plus another static one that will store the javascript code and run it the first time it is accessed. At this stage, running registerClass () is already used to associate types in an assembly with types in a script. All types of wrappers are inherited from one type in which the basic interaction mechanisms are described. The composition of the types is based on what was found in the prototype of the constructor.
 function ctor() { } ctor.prototype //        

Thus, if you add a non-enumerable property to the prototype, it will not fall into the metadata and become “private”.

Great, now you can create JS objects as .NET objects, but where are the promised inheritance and overloads ?!

It was decided easier. In order to understand which methods are overloaded, a pass through all functions in the type is performed in the constructor of the base type (I understand that this is not a good place, but for the prototype of the implementation this is sufficient) and the ancestor of the type that declared them is checked. All overridden methods become declared in the derived type. All methods found by such a check are added to the JS type implementation. Everything.
Now even new added functions will be available in js, they do not need to be overloaded.
')
The code is not great, you can watch it on GitHub

PS This solution, at least at this stage, is not suitable for serious use. This is an experiment.

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


All Articles