Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Raffaele Rialdi (Vevy Europe), A deep dive into writing .NET Core Plugins for NodeJS, CodeFest 2017

CodeFest
January 31, 2018

Raffaele Rialdi (Vevy Europe), A deep dive into writing .NET Core Plugins for NodeJS, CodeFest 2017

https://2017.codefest.ru/lecture/1154

The world is more and more etherogeneous and each echosystem has pros and cons that must be carefully evaluated.

The target of the session is to show how can NodeJS and .NET Core be bridged, and taking advantage of the best of the two worlds.

Instead of just showing of how the bridge works, we will see each interoperability step, from NodeJS to C++, hosting the Core CLR, invoking asynchronous task-based .NET methods and finally marshalling back to the world of NodeJS.

CodeFest

January 31, 2018
Tweet

More Decks by CodeFest

Other Decks in Programming

Transcript

  1. A deep dive into writing NET Core Plugins for NodeJS

    Raffaele Rialdi Senior Software Architect Vevy Europe @raffaeler [email protected]
  2. Who am I? • I am Raffaele Rialdi, Senior Software

    Architect in Vevy Europe – Italy • I am also consultant in many industries (manufacturing, racing, healthcare, financial, …) • But also Speaker in italian and international conferences, and Trainer as well • And very proud to have been awarded as a Microsoft MVP since 2003 @raffaeler [email protected] github.com/raffaeler
  3. Node.JS and V8 • Node.js® is a well-known runtime environment

    for javascript • It leverages the power of "V8", the engine of Google Chrome • V8 conforms to the ECMAScript 262 standard and runs cross-platforms • NodeJs has a vivid ecosystem with tons of 3rd party libraries • V8 can be extended with native addons (C++): • When you need more performance • To access operating system's resources and services • To interoperate with external systems / hardware https://nodejs.org
  4. The basic structure of a C++ V8 addon • Including

    the libraries • Declare the Entry-Point using the NODE_MODULE macro • Implement the entry-point and declare javascript-friendly methods • The "Method" will be available as "hello" to javascript #include <node.h> #include <v8.h> NODE_MODULE(addon, init) void init(Local<Object> exports) { NODE_SET_METHOD(exports, "hello", Method); } void Method(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = args.GetIsolate(); args.GetReturnValue().Set( String::NewFromUtf8(isolate, "world")); } // javascript code const addon = require('./myAddon'); console.log(addon.hello()); // 'world'
  5. The toolchain • Bulding the addon requires a C++ toolchain

    • Python, GCC for Linux, GCC / Xcode on Mac, VC++ on Windows • The node-gyp tool • Avoid the mess to manually download the node libraries and include files • Abstracts the build operation from the mentioned toolchains • Instead of creating a 'makefile' there is a 'binding.gyp' file npm install -g node-gyp { "targets": [ { "target_name": "myaddon", "sources": [ "src/myaddon.cpp" ] } ] } npm config set msvs_version 2015 configuring gyp to use VC++2015 toolchain
  6. V8 Object Wrapping • By deriving the ObjectWrap class, we

    can expose objects to JS • At initialization time, we define the members of the object • Constructors, methods, properties, indexers • We "set" the exports with the class name and the template declaring the members • All member handlers are static void functions • Receive a "const FunctionCallbackInfo<Value>& args" • Don't forget javascript is dynamic. The args contain the runtime values of the arguments • Retrieve the context of the wrapper instance using ObjectWrap:Unwrap • Set the return value using "args.GetReturnValue().Set(…);" exports->Set(v8className, constructorTemplate->GetFunction());
  7. V8 Type System and marshalling • The V8 type system

    is different from the C++ types • This is because V8 types are managed through the Garbage Collector • Marshalling is needed to copy from/to C++ types • There more V8 types than expected • Int32, Number, String, Boolean, Function, Object, Function, … • Strings are UTF8 while .NET use UTF16. Both are needed. static std::string FromV8String(const Local<Value>& value) { String::Utf8Value utf8(value); std::string str(*utf8); return str; } static std::wstring FromV8StringW(const Local<Value>& value) { String::Utf8Value utf8(value); auto str = raf_helpers::Helpers::utf8_to_wstring(*utf8); return str; }
  8. Threading and the libuv threading library • It is the

    library used by NodeJs and V8 to manage threads • The JS code must be always executed in the main thread • Javascript callbacks are often needed for time-expensive methods • When a .NET method signature return a Task the result must be marshalled back to the main thread • The libuv library give a very basic support for enqueuing messages in its threads • Every execution coming from another thread must be queued back to the main thread
  9. Hosting the CoreCLR • What does it mean hosting the

    CLR? • A native (C++) application can load the CLR, some assemblies and run managed code • SQL Server is a great example of native application hosting the CLR • You can do it too! • The CLR in .NET Framework could be hosted only on Windows • It is based on the COM infrastructure • The new Net Core can be hosted as well but cross-platform • It mimics the COM interfaces, but the infrastructure is not COM-dependent
  10. Hosting the CLR • Including the libraries • Load the

    coreclr.dll • Get the entrypoint • Get the RuntimeHost • Start the CLR • Create the AppDomain #include <mscoree.h> auto loader = LoadLibrary(fullCoreClr.c_str()); auto prhf = (FnGetCLRRuntimeHost)::GetProcAddress( (HMODULE)loader, "GetCLRRuntimeHost"); ICLRRuntimeHost2* prh = nullptr; hr = prhf(IID_ICLRRuntimeHost2, (IUnknown**)&prh); prh->Authenticate(CORECLR_HOST_AUTHENTICATION_KEY); hr = prh->Start(); hr = prh->CreateAppDomainWithManager(…)
  11. Why xcore? • C++ can only call static .NET methods

    • There is no semantics for the object lifecycle, properties, events and async • xcore analyze and caches.NET metadata • Generates optimized code to access each object's instance • Expression Trees are then compiled and delegates are cached • Object Instances are maintained and tied to the JS garbage collector • The Marshalling code is optimized • What about the graphs • Every time a child object is accessed, xcore dynamically load metadata
  12. xcore in action • Load and initialize xcore • Load

    the .net metadata • Just the first needed class • Create an instance • Call methods • Walk the graph • new metadata is loaded automatically • Asynchronous calls (and much more!) var xcore = require('bindings')('xcore'); xcore.initialize(__dirname + "\\Sample", "SampleLibrary.dll", "SampleLibrary.Initializer"); xcore.loadClass("SampleLibrary.OrderSample. OrderManager, SampleLibrary"); var om = new xcore.OrderManager("raf"); console.log(om.Add("Hello, ", "world")); console.log(om.SelectedOrder.Name); om.AddAsync(2, 3, function(res){ console.log(res); });
  13. Performance profile • There are still many improvements that can

    be done • The add use-case is not realistic • it just measure the xcore infrastructure and marshalling time • xcore must marshal 4 times: (JS  C++  .NET  C++  JS) 1 Million calls js: 6.156 ms c++: 56.352 ms .net: 1294.254 ms console.time("net"); for(var i=0; i<loop; i++) { om.NetAdd(i, i); } console.timeEnd("net"); console.time("c++"); for(var i=0; i<loop; i++) { xcore.add(i, i); } console.timeEnd("c++"); console.time("js"); for(var i=0; i<loop; i++) { LocalAdd(i, i); } console.timeEnd("js"); js: 6.156ms c++: 56.352ms .net: 1294.254ms var om = new xcore.OrderManager(); om.Add(2, 2); // jitting LocalAdd(2,2); // jitting xcore.add(2, 2); // jitting var loop = 1000000; // 1 Million function LocalAdd(x, y) { return x + y; } prepare the benchmark local javascript C++ only xcore + C#
  14. Use-cases 1. Node.js applications 2. UI created with the Electron

    framework • Ability to create .NET ViewModels for Angular (by Google) 3. Using JS to script Windows Powershell 4. Nativescript Mobile applications • Nativescript is a project by Progress Software Corporation https://www.nativescript.org/ 5. Anything else based on V8 http://electron.atom.io
  15. Join me at the Exhibition zone • xcore in action

    • Raspberry PI running netcore (and more)