ChakraCore: A Powerful C++ JavaScript Engine Library

In modern software development, JavaScript has long transcended the boundaries of the browser, becoming an important language for embedded scripting, server-side programming, and cross-platform application development. Embedding a JavaScript engine into native applications can bring great flexibility and dynamic capabilities.ChakraCore is a high-performance, open-source JavaScript engine developed by Microsoft for this purpose, enabling C++ developers to easily integrate a complete JavaScript runtime environment into their applications.

Development Background and Open Source Journey

ChakraCore originated from the “Chakra” JavaScript engine of the old Microsoft Edge browser. In 2016, Microsoft open-sourced its core components, naming it ChakraCore and hosting it on GitHub. This initiative aimed to allow a broader developer community to leverage an industrial-grade, high-performance JavaScript engine and promote its application in various scenarios, from IoT devices to server-side applications.

After being open-sourced, ChakraCore quickly evolved into a truly cross-platform engine, supporting Windows, Linux, and macOS operating systems, and compatible with various processor architectures such as x86, x64, and ARM.

Core Features and Characteristics

  1. High-Performance JavaScript ExecutionChakraCore employs advanced technologies of modern JavaScript engines, including:

  • Just-In-Time Compilation (JIT): Compiles JavaScript code into highly optimized machine code, significantly enhancing execution speed.
  • Advanced Garbage Collection (GC): Implements a generational and concurrent garbage collection mechanism, effectively reducing pause times.
  • Support for the Latest ECMAScript Standards: Continuously updated to support new features in ES6, ES7, and subsequent standards, such as <span>async/await</span>, proxies, generators, etc.
  • Cross-Platform SupportChakraCore’s design allows it to seamlessly integrate into C++ applications on any platform, providing great convenience for developing cross-platform applications. Developers can write application logic in JavaScript while using C++ for performance-critical parts or native interactions.

  • Easy-to-Embed APIChakraCore provides a simple yet powerful C API (easily callable from C++) that allows developers to:

    • Create and manage independent JavaScript runtime environments.
    • Execute JavaScript code strings or precompiled bytecode at runtime.
    • Implement bidirectional function calls and data exchange between C++ and JavaScript.

    Architecture Overview

    The API of ChakraCore is primarily built around the following core concepts:

    • Runtime: An independent JavaScript execution environment with its own heap memory and garbage collector. A process can create multiple runtimes.
    • Context: Similar to the Window object in a browser, it is a sandboxed execution environment for running scripts. A runtime can contain multiple contexts.
    • Value: Any value in JavaScript (numbers, strings, objects, functions, etc.) is represented in C++ by <span>JsValueRef</span>.
    • Property ID: A unique ID used to identify object properties, more efficient than using strings directly.

    Code Example: Embedding ChakraCore in C++

    The following example demonstrates how to create a simple ChakraCore environment, execute a piece of JavaScript code, and achieve interaction between C++ and JavaScript.

    Step 1: Include Header Files and LibrariesFirst, you need to obtain the ChakraCore library files. You can compile the source code from GitHub or use precompiled binaries. Include the header files in your code and link the corresponding libraries.

    // Ensure your project includes the ChakraCore header file path and library files
    #include <iostream>
    #include <string>
    #include "ChakraCore.h"
    
    // On Windows, you may need to link ChakraCore.lib
    // On Linux/macOS, link libChakraCore.so or libChakraCore.dylib
    

    Step 2: Write a C++ Callback Function for JavaScript to Call

    // This function will be exposed to JavaScript and can be called from JS code
    JsValueRef CALLBACK NativeLog(
        JsValueRef callee, 
        bool isConstructCall,
        JsValueRef* arguments, 
        unsigned short argumentCount, 
        void* callbackState) {
    
        // The first parameter is the function itself, so start from the second parameter for user-provided arguments
        for (unsigned short i = 1; i < argumentCount; i++) {
            const wchar_t* message;
            size_t length;
    
            // Convert JavaScript value to string
            JsValueRef stringValue;
            JsConvertValueToString(arguments[i], &stringValue);
            JsStringToPointer(stringValue, &message, &length);
    
            // Output to console
            std::wcout << L"[C++ Log] " << message << std::endl;
        }
    
        // Return undefined
        JsValueRef undefinedValue;
        JsGetUndefinedValue(&undefinedValue);
        return undefinedValue;
    }
    

    Step 3: Main Function – Initialize and Execute JavaScript

    int main() {
        JsRuntimeHandle runtime;
        JsContextRef context;
        JsValueRef result;
        unsigned currentSourceContext = 0;
    
        // 1. Create JavaScript runtime
        JsCreateRuntime(JsRuntimeAttributeNone, nullptr, &runtime);
    
        // 2. Create an execution context
        JsCreateContext(runtime, &context);
    
        // 3. Set the current execution context
        JsSetCurrentContext(context);
    
        // 4. Get the global object
        JsValueRef globalObject;
        JsGetGlobalObject(&globalObject);
    
        // 5. Create a JavaScript function wrapping our C++ callback
        JsValueRef nativeLogFunction;
        JsCreateFunction(NativeLog, nullptr, &nativeLogFunction);
    
        // 6. Set the function as a property of the global object, making it available in JS
        JsPropertyIdRef logPropertyId;
        JsGetPropertyIdFromName(L"nativeLog", &logPropertyId);
        JsSetProperty(globalObject, logPropertyId, nativeLogFunction, true);
    
        // 7. JavaScript code to execute
        // This code calls our C++ function and performs some calculations
        const wchar_t* script = L"
            nativeLog('Hello from JavaScript!');
            let x = 10;
            let y = 20;
            nativeLog('Calculating: ' + x + ' + ' + y);
            let sum = x + y;
            nativeLog('Result: ' + sum);
            sum; // Return this value to C++
        ";
    
        // 8. Run JavaScript code
        JsRunScript(script, currentSourceContext++, L"", &result);
    
        // 9. Convert JavaScript return value to C++ type and output
        double sumValue;
        JsNumberToDouble(result, &sumValue);
        std::cout << "JavaScript returned: " << sumValue << std::endl;
    
        // 10. Clean up resources
        JsSetCurrentContext(JS_INVALID_REFERENCE);
        JsDisposeRuntime(runtime);
    
        return 0;
    }
    

    Code Analysis

    1. Initialization and Context Creation: The code first creates a runtime and an execution context, which are the foundational environments for all JavaScript operations.

    2. Bidirectional Interaction:

    • **JavaScript Calls C++**: A JavaScript function is created using <span>JsCreateFunction</span>, which actually wraps the C++ function <span>NativeLog</span>. This function is added to the global object, making it callable from JavaScript code.
    • C++ Calls JavaScript: The code executes a string of JavaScript code using <span>JsRunScript</span> and retrieves its return value.
  • Type Conversion: ChakraCore uses <span>JsValueRef</span> as a handle for JavaScript values. The code demonstrates how to convert a JavaScript string to a wide string in C++ (<span>JsStringToPointer</span>) and how to convert a JavaScript number to a C++ <span>double</span> (<span>JsNumberToDouble</span>).

  • Resource Management: Finally, the code correctly cleans up the runtime and context to avoid memory leaks.

  • Practical Application Scenarios

    1. Application Scripting: Allows users to customize and extend the functionality of applications using JavaScript.
    2. Plugin Systems: Develop plugin systems for applications, where plugins can be written in JavaScript.
    3. Game Development: Use JavaScript to write game logic while using C++ for high-performance tasks like graphics rendering and physics calculations.
    4. Server-Side Applications: Create high-performance JavaScript server runtimes, similar to Node.js, but with tighter integration with C++ modules.

    Conclusion

    ChakraCore is a powerful, efficient, and easy-to-embed JavaScript engine that provides C++ developers with a complete solution for integrating JavaScript into their applications. Its cross-platform features and support for the latest ECMAScript standards make it an excellent choice for projects that require scripting capabilities, plugin systems, or wish to leverage the JavaScript ecosystem. Although the Microsoft Edge browser has transitioned to using the Chromium V8 engine, ChakraCore continues to be maintained and developed as an independent project, providing ongoing value to the developer community. With its simple API, developers can easily bridge the gap between C++ and JavaScript, creating software that combines native performance with application flexibility.

    Leave a Comment