Few years ago, I wanted to make an experiment with Electron, the cross-platform UI framework (also used from Visual Studio Code) that enables reusing web technologies to write desktop applications. After experimenting with Javascript first and Typescipt, I was still out of my comfort zone, due all the runtime errors that are typical of the dynamic languages.
For this reason I started thinking how I could implement some interop to use C# View Models in Electron. My primary goal was to avoid out-of process calls as ElectronNET did. I really wanted performance and avoid the n-th process opened from Electon. Crazy start but hey, I love experimenting.
Since Electron is based on NodeJs which in turn is based on V8 (Chrome engine), I quickly planned a number of basic but important proof of concepts:
Of course these were only the basic points, but enough to give me the proof of the basic building blocks of my project (and enough for this blog post of course)
This was pretty easy. Apart from starting from scratch with the examples provided in NodeJS and setting up the toolset chain, they worked pretty nicely. I immediately discovered important features hidden in NodeJS and V8 that every developer should know:
The good part of this first addon/plugin is that I could dynamically create new types. I mean literally add not only new types during the execution, but members as well.
By default C++ NodeJS addons declare types using macros and they are defined during the addon startup. But if you crack the macros and look what they do, turning them into pure method calls is easy, and you can call at any moment.
Phase 2: Hosting the CoreCLR from the C++ NodeJS plugin
At that time, the .NET Core hosting APIs were still not final, but I could easily write the needed code without too much efforts. I spent most of the time in writing a cross-platform version of hosting the CoreCLR. The APIs resemble the one used in the past to host the .NET Framework, but in .NET Core they are exposed just as pure C export invocations instead of COM calls.
Once you have hosted the CoreCLR, it is pretty easy to invoke some managed code using an old technique called Reverse PInvoke. It works like PInvoke but in the reverse direction, with C++ as a client and C# exposing the method called from the native code.
Instead of Reverse Pinvoke, starting from .NET 5 it will be possible to use C# instrinsic calls support used also by https://github.com/microsoft/CsWinRT to make this step more performant.
In the final code of the plugin I added several "service calls" that allows me to either benefit to live in the native or managed world.
At a certain time of your javascript code, you decide to invoke some C# code to access managed code. This makes use of one of those "service calls" asking the managed code to load an assembly and the managed type you want to use. From Javascript this is exposed as a call to my "xcore" plugin: the first argument is the full qualified name of the assembly (which includes the assembly name) and the type to be loaded in memory. Basically the same syntax and behavior of "Type.GetType".
xcore.loadClass("SampleLibrary.OrderSample.OrderManager, SampleLibrary");// xcore is the C++ plugin
Once the C# Sample library is loaded in memory, Javascript still doesn't know anything about it. Any dynamic invocation (like creating an instance, invoking static or instance methods) would hit the C++ plugin that nothing knows about this type. The result would be a legit runtime error.
For this reason, as soon as a type is loaded in memory, the very first thing to do is reading the metadata of the assembly so that the C++ plugin knows what members of that type should be added in V8. Of course, C++ the implementation of those methods is just a single method that grab the member name and its parameters that have to be forwarded to .NET for being dispatched to the real type.
In the current implementation I use managed reflection to load the metadata in memory and "copy" it in the native couterpart (C++). But some time ago I tried to adopt the ECMA-335 C++ code that Kenny Kerr used in cppwinrt. It didn't work because the goal of cppwinrt is to load winmd files. The winmd files are ECMA-335 compliant but .NET implementation is wider and some case was not implemented in the cppwinrt implementation.
No worries, because (last year) I created a pull-request on GitHub, made a few changes with the help of Kenny, and my fix was finally accepted. That metadata library was finally able to load the .NET metadata in C++. I didn't have time to adopt the library in my code, but this is something that can dramatically improve the performance metadata loading in my project.
With this code, the C++ addon was able to cheat the Javascript code and make it think that the class andits members really existed. Ultimately I could then satisfy the current executions from Javascript:
var om = new xcore.OrderManager("raf"); // creating an instance
console.log(om.SelectedOrder.Name);// reading the Name property
var sum =
om.Add(10, 20); // invoking methods
What happens when you invoke a member like the Add method?
The execution request is intercepted from the C++ plugin which takes the list of all the parameters that Javascript declared. At this point I validate whether the parameters match in terms of number and types:
The validation can't be perfect since we don't have enough information from Javascript. But once the number of arguments is satisfied we can "try" to dispatch the call to the managed world.
On the managed code side, the service call takes the invocation request and tries to dispatch it (invoke the real code).
There were two possible strategies for dispatching a member invocation: reflection (but it would be slow) or code generation. Code generation is much harder but after the first call has great performance, and so I did using expression trees that:
But the roundtrip is not fininished because we may have a return value.
Once the invokation is finished, I take the return value which is marshaled back to native memory and sent back to C++ via a service call. This way the C++ plugin can return the value to the javascript invocation.
This just gives an idea of what can be done. There are plenty of cases I supported in the plugin and each of those would require a separate post … I am not sure this would be interesting for many. The currently implemented hard cases are:
I didn't have much time to improve it but here there are few things that I would like to add:
The last but most important missing thing is "synchronizing" the Javascript Garbage Collector with .NET GC. Keeping trace of the objects lifetime is very tricky. There are new APIs in .NET that can help me but I definitely have to drill into this subject.
More info is published in a video you can find here: https://github.com/raffaeler/xcore
I never published the sources because they are very tricky. If anyone has some interest in this project, please let me know via Twitter @raffaeler.
Privacy | Legal Copyright © Raffaele Rialdi 2009, Senior Software Developer, Consultant, p.iva IT01741850992, hosted by Vevy Europe Advanced Technologies Division. Site created by Raffaele Rialdi, 2009 - 2015 Hosted by: © 2008-2015 Vevy Europe S.p.A. - via Semeria, 16A - 16131 Genova - Italia - P.IVA 00269300109