Mở rộng ứng dụng Node.js với native C++ modules (Native addon)

Ngoài việc nạp các thành phần mở rộng được viết bằng js; Node.js còn có thể được mở rộng bởi các module được viết bằng c++. Mặc dù sử dụng c++ module có nhiều điểm lợi thế nhưng nó không có nghĩa rằng chúng ta nên thay thế toàn bộ các mã js của mình bằng c++ nhé. Trong bài này chúng ta sẽ cùng tìm hiểu xem native addon trong Node.js là gì và có thể sử dụng nó như thế nào.

Tại sao nên sử dụng Node.js native addon

  1. Truy cập trực tiếp tới các thư viện C / C ++ hiện có. Thay vì việc phải compile chúng ra các execuable file và gọi các ứng dụng này theo phong cách "execute command", chúng ta có thể nhúng trực tiếp mã c++ vào source code hiện tại. Bằng cách này chúng ta cũng có thể tương tác trực tiếp với các low-level api của hệ điều hành
  2. Hiệu suất. Trong nhiều trường hợp, mã nguồn được viết với c++ cho tốc độ xử lý nhanh hơn rất nhiều so với mã làm nhiệm vụ tương đương nhưng được viết bằng js. Disclaimer: Không phải trong tất cả các trường hợp thì c++ addon đều nhanh hơn js. Có rất nhiều hàm trong Node.js mà ngay từ khi thiết kế đã được tối ưu toàn diện, viết lại các hàm đó bằng c++ đôi khi lại khiến cho ứng dụng của chúng ta chậm đi nhiều. Các bạn nên cân nhắc thật kỹ trước khi quyết định có nên viết một module mới bằng c++ không nhé

Thử viết ứng dụng Hello world với Node.js Native addon

Hãy thử bắt đầu với Node.js native addon bằng một ví dụ vô cùng đơn giản nhé Với mục đích của bài viết này, mình sẽ sử dụng Native Abstractions for Node.js (Nan), về cơ bản thì nó là một dạng tệp tin C++ header cung cấp namespace và tập hợp các macro hữu ích để tương tác dễ dàng hơn với V8 (JavaScript engine được sử dụng trong Node.js) API. Nó cũng làm cho code của bạn hữu dụng trong tương lai (đối với các phiên bản kế tiếp của Node.js) khi mà V8 API có xu hướng thay đổi khá là đáng kể giữa các phiên bản. Nan là một giải pháp được khuyên dùng trong tài liệu của Node.js

Tạo file main.cpp với nội dung như sau

#include <nan.h>

NAN_METHOD(Hello) {
    auto message = Nan::New("Hello from C++!").ToLocalChecked();
    info.GetReturnValue().Set(message);
}

NAN_MODULE_INIT(Initialize) {
    NAN_EXPORT(target, Hello);
}

NODE_MODULE(addon, Initialize);

trong đó:

  • NAN_METHOD là một macro của Nan giúp tạo ra các native function của Node.js một cách đơn giản và tường minh. Tham số đầu vào của nó chính là tên của method mà chúng ta sẽ tạo ra Hello
  • auto message = Nan::New("Hello from C++!").ToLocalChecked();: Tạo ra một đối tượng V8 string
  • info.GetReturnValue().Set(message);: info là một tham số ngầm, nó là một object cầu nối giữa C++ và JavaScript runtimes. Chúng ta vừa có thể dùng info để lấy ra những tham số được truyền vào cho method (Hello) và gán giá trị return cho method Hello
  • NAN_MODULE_INIT Khởi tạo module
  • NAN_EXPORT Export hàm Hello, tương đương với export function Hello() {} trong js
  • NODE_MODULE tạo module tên là addon và khởi tạo nó với phương thức Initialize

Lưu ý rằng các phương thức tạo ra trong C + + trả về void, chúng không trả lại bất kỳ giá trị nào. Thay vì trả về giá trị từ các phương thức này, chúng ta phải gán giá trị vào cho tham số cầu nối info (reference of type Nan::FunctionCallbackInfo, “implicitly” passed in a NAN_METHOD macro).

Để chạy được addon đã viết bên trên, chúng ta cần phải biên dịch nó. Node.js sẽ giúp bạn trong việc biên dịch này, bạn sẽ không phải dùng gcc để biên dịch gì cả.

  1. Khởi tạo một project mới với Node.js: npm init -y
  2. Cài đặt Nan và Node-gyp (Node-gyp là tập hợp các công cụ giúp bạn compile code c++ sang Node.js native addon): npm install nan node-gyp --save
  3. Thêm lệnh compile vào scripts của package.json node-gyp rebuild. Khi đó file package.json của bạn sẽ giống như sau
{
  "name": "nde-native",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "compile": "node-gyp rebuild",
    "start": "node index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "nan": "^2.6.2",
    "node-gyp": "^3.6.2"
  }
}

Có hai điểm cần chú ý ở đây là:

  1. "compile": "node-gyp rebuild": Dùng để biên dịch code c++
  2. "start": "node main.js": Chạy ứng dụng

Tiếp theo, tạo file cấu hình cho node-gyp: binding.gyp

{
  "targets": [
    {
      "include_dirs": [
        "<!(node -e \"require('nan')\")"
      ],
      "target_name": "addon",
      "sources": [ "main.cpp" ]
    }
  ]
}

Lưu ý: Bạn cần phải cài các công cụ hỗ trợ để có thể compile c++ code với node-gyp:

Linux:

  • python (v2.7 recommended, v3.x.x is not supported)
  • make
  • A proper C/C++ compiler toolchain, like GCC

Mac:

  • Xcode

Windows

npm install --global --production windows-build-tools hoặc cài đặt Visual Studio hoặc Sử dụng Linux subsystem rồi làm theo yêu cầu với Linux bên trên

node-gyp sẽ thông báo cho bạn nếu một trong các thành phần yêu cầu bị thiếu

Kiểm tra lại trước khi build Cuối cùng, chúng ta sẽ compile addon bằng lệnh npm run compile. Để kiểm tra xem addon vừa compile có chạy tốt hay không. Hãy tạo file index.js với nội dung sau

const {Hello} = require('./build/Release/addon');
console.log(Hello());

Chạy test: npm start Như vậy là chúng ta đã tìm hiểu qua về Node.js native addon. Để có thể áp dụng kỹ thuật này hiệu quả hơn thì chúng ta cần thêm kiến thức về c++, tuy nhiên với những lợi thế của c++ và sự support mạnh mẽ của Nan và Node-gyp thì mình nghĩ sẽ không quá khó để có thể bắt đầu với Node.js native addon

All Rights Reserved