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.
xcore.loadClass("SampleLibrary.OrderSample.OrderManager, SampleLibrary");// xcore is the C++ plugin
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.
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?
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.
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:
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