Monday, September 30, 2024

How to Create Desktop Application with Electron, Go and NodeJS

The pursuit of high-performance applications often leads developers to embrace compiled languages like C++, Go, and C#. While C++ reigns supreme in performance, its complexity can be daunting. Go, on the other hand, offers a compelling blend of simplicity and efficiency, making it an attractive choice for distributed and easily writable applications.

However, integrating Go code into existing Node.js projects can pose a challenge. This article dives into a practical solution using a C++ bridge to enable seamless communication between Go and Node.js, allowing you to leverage the strengths of both languages in your applications.

The Need for a Bridge

Go's powerful standard library and vibrant community have led to a wealth of valuable libraries written in pure Go. Integrating these libraries into Node.js projects unlocks exciting possibilities, but it requires a mechanism to bridge the language gap.

Direct approaches like using child_process or establishing a separate Go API are viable, but come with drawbacks. The former necessitates managing two separate executables, potentially raising security concerns, while the latter introduces an additional layer of complexity and server costs.

To address these limitations, this article explores a more elegant and efficient approach: creating a Node.js C++ Golang bridge.

Leveraging C++ as a Bridge

Node.js, built on the V8 JavaScript engine, relies on C++ for plugin development. This might seem like an unwelcome detour, but it provides a crucial link between Go and Node.js.

The key lies in compiling Go code in the c-shared or c-archive mode. This process yields a closed-source library (.DLL on Windows, .SO or .A on Linux/Unix) and an accompanying header file (.h). These outputs act as the crucial bridge between Go and C++.

By including the header file and specifying the library in C++ code, you gain the ability to execute Go functions within your C++ bridge. This bridge serves as a translator, enabling Node.js to interact with your Go code seamlessly.

Building the Bridge: A Practical Example

Let's illustrate this concept with a practical example. We'll create a simple Go project (awesome-lib) that performs a computationally intensive task, demonstrating how to integrate it with Node.js.

Step 1: Creating the Go Project

mkdir awesome-lib
cd awesome-lib
go mod init example/awesome-lib
touch awesome-lib.go
    

The awesome-lib.go file will contain the Go code:

package main

import (
    "fmt"
    "time"
)

func AwesomeJob(second int) {
    startTime := time.Now()

    fmt.Println("AwesomeJob started")

    // High CPU usage
    time.Sleep(time.Duration(second) * time.Second)

    endTime := time.Now()
    fmt.Println("AwesomeJob ended! ", endTime.Sub(startTime))
}

func main() {
    AwesomeJob(5)
}
    

This code defines a function AwesomeJob that simulates a CPU-intensive task by pausing for a specified duration.

Step 2: Compiling for C++ Integration

To make this function accessible to C++, we compile it using the c-shared build mode and add a comment to generate a header file (awesome-lib.h):

//export AwesomeJob
func AwesomeJob(second int) {
    // ... (rest of the code)
}
    

Compile the Go code:

      go build -o awesome-lib.so -buildmode=c-shared awesome-lib.go
    

This generates the necessary awesome-lib.so library and awesome-lib.h header file.

Step 3: Writing the C++ Bridge

Next, we'll write the C++ bridge code in a file named addon.cc. We'll use the node-addon-api package to simplify the process.

// addon.cc

#include <chrono>
#include <thread>
#include <assert.h>
#include "napi.h"

#include "awesome-lib.h" // Include the generated header file

// ... (Rest of the C++ bridge code)
    

The C++ code utilizes the node-addon-api to expose the Go function AwesomeJob to Node.js through a JavaScript function called awesomeJobBridge.

Step 4: Building the Node.js Addon

We use node-gyp to build the addon:

      yarn add node-addon-api
node-gyp rebuild
    

This will create a build/Release/awesome-lib.node file, which is our compiled Node.js addon.

Step 5: Integrating the Addon in Node.js

Finally, we create a index.js file to utilize the addon:

      const AwesomeLib = require('./build/Release/awesome-lib');

AwesomeLib.awesomeJob(4, (res) => {
    console.log("job is done, result:", res);
});
    

Running node index.js will execute the Go code (AwesomeJob) through the C++ bridge, printing the expected output to the console.

Addressing Asynchronous Communication

One crucial consideration is asynchronous communication between Go and Node.js. Since Node.js operates on an event loop, directly returning callback results to JavaScript from a separate Go thread is not feasible. We need a mechanism to ensure seamless integration within the Node.js event loop.

The node-addon-api provides thread-safe functions, offering a robust solution to this challenge. By encapsulating Go callback functions within these thread-safe functions, we can ensure that the Node.js event loop is notified when the Go code completes, preventing blocking behavior.

Enhancing the Bridge: Callbacks and Promises

For more intricate scenarios, the need for multi-valued results and asynchronous communication arises. Callbacks and promises provide effective mechanisms to handle these complexities.

We can modify the Go code to accept a callback function as a parameter, allowing it to communicate intermediate results to the Node.js environment. By leveraging thread-safe functions, the callback can be invoked from the Go thread within the Node.js event loop, ensuring smooth interaction.

Furthermore, promises provide a structured way to handle asynchronous operations. We can return a promise from the Node.js bridge, resolving it once the Go code completes and the callback has been executed. This pattern allows for graceful handling of asynchronous operations in Node.js.

Conclusion: Empowering Node.js with Go

By creating a C++ bridge, we unlock a powerful mechanism for integrating Go code seamlessly into Node.js projects. This approach enables developers to harness the strengths of both languages, combining the efficiency of Go with the flexibility of Node.js.

Whether you're seeking to integrate existing Go libraries or build new applications leveraging the power of both languages, the C++ bridge offers a robust and practical solution, pushing the boundaries of what's possible in your Node.js development journey.

0 comments:

Post a Comment