[nodejs]Nodejs + Cpp

πŸ‘‰πŸ» Nodejsμ—μ„œ C++ μ• λ“œμ˜¨(Native Addons)을 μ‚¬μš©ν•˜λŠ” μ΄μœ λŠ” Node.jsμ˜Β μ„±λŠ₯ μ œν•œμ„ κ·Ήλ³΅ν•˜κ³ , 기쑴의 C++ μƒνƒœκ³„μ™€ μžμ›μ„ ν™œμš©ν•˜κΈ° μœ„ν•¨μž…λ‹ˆλ‹€.
The reason for using C++ addons (Native Addons) in nodejs is to overcome the performance limitations of Node.js and to leverage the existing C++ ecosystem and resources.

πŸ‘‰πŸ» μ•„λž˜λŠ” λ§₯osμ—μ„œ μ‹€ν–‰λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
Below was run on macOS.

1.μ½”λ“œμž‘μ„± / Write code

1.1 sersver.js

// server.js
const express = require("express");
const app = express();
const port = 3000;

// 1. C++ μ• λ“œμ˜¨ λ‘œλ“œ / Loading C++ add-ons
// node-gyp λΉŒλ“œ ν›„ μƒμ„±λ˜λŠ” .node 파일의 경둜λ₯Ό μ§€μ •ν•©λ‹ˆλ‹€.
// Specifies the path to the .node file generated after building node-gyp.
// (λΉŒλ“œκ°€ μ„±κ³΅ν•˜λ©΄ build/Release/addon_module.node 파일이 μƒμ„±λ©λ‹ˆλ‹€.)
// (If the build is successful, the build/Release/addon_module.node file will be created.)
const cppAddon = require("./build/Release/addon_module");

app.use(express.json());

// 2. C++ μ• λ“œμ˜¨μ„ μ‚¬μš©ν•˜λŠ” API μ—”λ“œν¬μΈνŠΈ μ •μ˜
//    Defining API endpoints using C++ add-ons
app.get("/calculate", (req, res) => {
  // 쿼리 νŒŒλΌλ―Έν„°μ—μ„œ 두 숫자λ₯Ό μΆ”μΆœ
  // Extract two numbers from query parameters
  const num1 = parseFloat(req.query.a);
  const num2 = parseFloat(req.query.b);

  if (isNaN(num1) || isNaN(num2)) {
    return (
      res
        .status(400)
        //Please enter valid numbers for query parameters 'a' and 'b'.
        .send("쿼리 νŒŒλΌλ―Έν„° 'a'와 'b'에 μœ νš¨ν•œ 숫자λ₯Ό μž…λ ₯ν•˜μ„Έμš”.")
    );
  }

  try {
    // 3. C++ μ• λ“œμ˜¨μ˜ ν•¨μˆ˜ 호좜 / Calling functions in C++ add-ons
    const result = cppAddon.multiply(num1, num2);

    // 4. κ²°κ³Όλ₯Ό ν΄λΌμ΄μ–ΈνŠΈμ—κ²Œ 응닡 / Reply the results to the client
    res.json({
      input_a: num1,
      input_b: num2,
      source: "C++ Addon",
      result: result,
    });
  } catch (error) {
    console.error("C++ add-on call error:", error.message);
    res.status(500).send("Server internal error occurred");
  }
});

app.listen(port, () => {
  console.log(`Node.js server is running at http://localhost:${port} .`);
  console.log(`Test URL: http://localhost:${port}/calculate?a=123&b=4.5`);
});

1.2 addon.cpp

// addon.cpp
/*
 * 이 μ½”λ“œλŠ” "ν˜„μž¬ Node.js ν™˜κ²½(env)μ—μ„œ C++ λ³€μˆ˜ resultλ₯Ό μ΄μš©ν•˜μ—¬ μƒˆλ‘œμš΄ JavaScript 숫자 객체λ₯Ό λ§Œλ“€μ–΄ λ°˜ν™˜ν•˜λΌ"λŠ” μ˜λ―Έκ°€ λ©λ‹ˆλ‹€.
 * This code means "Create a new JavaScript numeric object
 * using the C++ variable result in the current Node.js environment (env) and return it."
 *
 * C++ μ• λ“œμ˜¨μ€ mainν•¨μˆ˜κ°€ μ—†λŠ” 이유:Node.js μžμ²΄μ— 이미 κ±°λŒ€ν•œ main ν•¨μˆ˜κ°€ ν¬ν•¨λ˜μ–΄ 있기 λ•Œλ¬Έμž…λ‹ˆλ‹€.
 * Why C++ addons don't have a main function: Because Node.js itself already contains a huge main function.
 *
 * Napi::NumberλŠ” node-addon-api λΌμ΄λΈŒλŸ¬λ¦¬μ—μ„œ μ œκ³΅ν•˜λŠ” C++ 클래슀둜, Node.js ν™˜κ²½μ—μ„œ 톡신할 λ•Œ JavaScript의 number νƒ€μž…μ„ λ‚˜νƒ€λƒ…λ‹ˆλ‹€.
 * Napi::Number is a C++ class provided by the node-addon-api library that represents the JavaScript number type when communicating in a Node.js environment.
 *
 * Napi::Number ν΄λž˜μŠ€λŠ” λ‚΄λΆ€μ μœΌλ‘œ C++의 κΈ°λ³Έ 숫자 νƒ€μž…μ„ JavaScriptκ°€ μ΄ν•΄ν•˜λŠ” 숫자 객체 포맷으둜 λ³€ν™˜ν•΄ μ€λ‹ˆλ‹€.
 * The Napi::Number class internally converts C++'s basic number type into a numeric object format that JavaScript understands.
*/

// C++ κ°œλ°œμžκ°€ Node.js의 κ³ μ„±λŠ₯ λ„€μ΄ν‹°λΈŒ κΈ°λŠ₯을 μ‚¬μš©ν•˜κΈ°μœ„ν•œ 헀더
// Header for C++ developers to use high-performance native features of Node.js
#include <napi.h>
#include <iostream>

// JavaScriptμ—μ„œ 호좜될 C++ ν•¨μˆ˜ μ •μ˜
// Define a C++ function to be called from JavaScript
Napi::Number Multiply(const Napi::CallbackInfo& info) {

    // env: ν˜„μž¬ Node.js의 μ‹€ν–‰ ν™˜κ²½(Napi::Env)을 λ‚˜νƒ€λƒ…λ‹ˆλ‹€.
    // env: Represents the current Node.js execution environment (Napi::Env).
    Napi::Env env = info.Env();

    // 인자 개수 확인 / Check number of arguments
    if (info.Length() < 2 || !info[0].IsNumber() || !info[1].IsNumber()) {
        Napi::TypeError::New(env, "두 개의 숫자λ₯Ό μž…λ ₯ν•΄μ•Ό ν•©λ‹ˆλ‹€.").ThrowAsJavaScriptException();
        return env.Null().As<Napi::Number>();
    }

    // JavaScript 인자λ₯Ό C++ 숫자둜 λ³€ν™˜
    // Convert JavaScript arguments to C++ numbers
    double a = info[0].As<Napi::Number>().DoubleValue();
    double b = info[1].As<Napi::Number>().DoubleValue();

    // C++ 둜직 μ‹€ν–‰
    // Execute C++ logic
    double result = a * b;
    std::cout << "C++ μ• λ“œμ˜¨μ—μ„œ κ³±μ…ˆ 둜직 싀행됨: " << a << " * " << b << std::endl;

    // κ²°κ³Όλ₯Ό JavaScript Number νƒ€μž…μœΌλ‘œ λ³€ν™˜ν•˜μ—¬ λ°˜ν™˜
    // Convert the result to JavaScript Number type and return it
    // New()λŠ” μƒˆλ‘œμš΄ JavaScript 객체λ₯Ό μƒμ„±ν•˜λŠ” λ©”μ„œλ“œ
    // New() is a method that creates a new JavaScript object.
    return Napi::Number::New(env, result);
}

// λͺ¨λ“ˆ μ΄ˆκΈ°ν™” (exports 객체에 ν•¨μˆ˜ 등둝)
// Initialize module (register functions in exports object)
Napi::Object Init(Napi::Env env, Napi::Object exports) {
    exports.Set(
        // μžλ°”μŠ€ν¬λ¦½νŠΈμ—μ„œ addon.multiply(a, b);둜 μ‚¬μš©λ¨.
        // Used in JavaScript as addon.multiply(a, b);
        Napi::String::New(env, "multiply"),

        // Functionνƒ€μž…μ€ μžλ°”μŠ€ν¬λ¦½νŠΈμ—μ„œ ν•¨μˆ˜λ‘œ μ‚¬μš© ν•  수 있게 ν•΄μ€Œ.
        // The Function type allows you to use it as a function in JavaScript.
        // μ‹€μ œλ‘œ 싀행될 c++ ν•¨μˆ˜μž…λ‹ˆλ‹€. / This is the c++ function that will actually be executed.
        Napi::Function::New(env, Multiply)
    );
    return exports;
}

// Node.js λͺ¨λ“ˆλ‘œ 등둝
// Register as a Node.js module
NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)

1.3 binding.gyp

# GYP (Generate Your Projects): 이 ν˜•μ‹μ€ Googleμ—μ„œ κ°œλ°œλ˜μ—ˆμœΌλ©°,
# μ›λž˜λŠ” Chromium ν”„λ‘œμ νŠΈλ₯Ό μœ„ν•΄ μ‚¬μš©λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
# μ΄λŠ” λ‹€μ–‘ν•œ ν”Œλž«νΌ(macOS, Linux, Windows)μ—μ„œ C++ ν”„λ‘œμ νŠΈλ₯Ό λΉŒλ“œν•˜κΈ° μœ„ν•œ λΉŒλ“œ μ‹œμŠ€ν…œ μ„€μ • μ–Έμ–΄μž…λ‹ˆλ‹€.

# GYP (Generate Your Projects): This format was developed by Google and was originally used for the Chromium project.
# It is a build system configuration language for building C++ projects on various platforms (macOS, Linux, Windows).

# binding.gyp
{
  "targets": [
    {
      # target_name : μ΅œμ’…μ μœΌλ‘œ 생성될 .node 파일의 이름 / The name of the .node file that will ultimately be created
      # sources : λΉŒλ“œν•  μ†ŒμŠ€ μ½”λ“œ 파일 λͺ©λ‘μ„ μ§€μ •ν•©λ‹ˆλ‹€. / Specifies a list of source code files to build.
      # include_dirs : C++ μ½”λ“œμ—μ„œ #include <napi.h>λ₯Ό μ‚¬μš©ν•  수 μžˆλ„λ‘ ν•©λ‹ˆλ‹€. / Enables you to use #include <napi.h> in your C++ code.

      "target_name": "addon_module",
      "sources": [ "addon.cpp" ],
      "include_dirs": [
        "<!@(node -p \"require('node-addon-api').include\")"
      ],
      "defines": [ "NAPI_CPP_EXCEPTIONS" ],

      # C++ 컴파일러 ν”Œλž˜κ·Έ μ„€μ •: μ˜ˆμ™Έ 처리 ν™œμ„±ν™” / Setting C++ Compiler Flags: Enable Exception Handling
      # -- C++의 핡심 κΈ°λŠ₯인 try, catch, throw ꡬ문이 μ»΄νŒŒμΌλ˜μ–΄ λŸ°νƒ€μž„μ— μ œλŒ€λ‘œ μž‘λ™ν•  수 μžˆλ„λ‘ λ§Œλ“­λ‹ˆλ‹€.
      #    The try, catch, and throw statements, which are core features of C++, are compiled so that they work properly at runtime.

      "cflags_cc": [
          "-fexceptions"
      ],

      # (macOS μ „μš©) XCode/Darwin λΉŒλ“œ μ„€μ • μ˜€λ²„λΌμ΄λ“œ
      # (macOS only) Override XCode/Darwin build settings

      # μ˜ˆμ™Έμ²˜λ¦¬ κΈ°λŠ₯이 λΉ„ν™œμ„±ν™”λ˜λ©΄ 였λ₯˜κ°€ λ°œμƒν•©λ‹ˆλ‹€.
      # μ•„λž˜μ˜ μ½”λ“œλŠ” λ§₯os의 μ˜ˆμ™Έμ²˜λ¦¬ κΈ°λŠ₯을 ν™œμ„±ν™”ν•˜λŠ” μ½”λ“œ μž…λ‹ˆλ‹€.
      # An error will occur if exception handling is disabled.
      # The code below enables exception handling in macOS.

      'xcode_settings': {
          'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', # C++ μ˜ˆμ™Έ ν™œμ„±ν™”
          'OTHER_CPLUSPLUSFLAGS': [ '-fexceptions' ] # μΆ”κ°€ ν”Œλž˜κ·Έ
      },

      # μ•ˆμ „μ„ μœ„ν•΄ κΈ°λ³Έ -fno-exceptionsλ₯Ό 제거 (λ§Œμ•½ node-gyp κΈ°λ³Έ 섀정에 μžˆλ‹€λ©΄)
      # Remove default -fno-exceptions for safety (if it's in node-gyp default config)

      # cflags! : C 컴파일러의 μ˜ˆμ™Έ λΉ„ν™œμ„±ν™” μ˜΅μ…˜ 제거 / Removed the option to disable exceptions from the C compiler.
      # cflags_cc! : C++ 컴파일러의 μ˜ˆμ™Έ λΉ„ν™œμ„±ν™” μ˜΅μ…˜ 제거 / Removed the option to disable exceptions from the C++ compiler.

      "cflags!": [ "-fno-exceptions" ],
      "cflags_cc!": [ "-fno-exceptions" ]
    }
  ]
}
2.1.μ˜μ‘΄μ„± μ„€μΉ˜ / install dependencies
npm install node-gyp node-addon-api express --save

2.2.C++ μ• λ“œμ˜¨ λΉŒλ“œ / C++ add-on build

npx node-gyp configure
npx node-gyp build

2.3.μ„œλ²„μ‹€ν–‰ / server run

node server.js

2.4 ν„°λ―Έλ„μ—μ„œ ν…ŒμŠ€νŠΈ / Test in terminal

curl http://localhost:3000/calculate?a=123&b=4.5

2.5 λΈŒλΌμš°μ € ν…ŒμŠ€νŠΈ κ²°κ³Ό / Browser test results

{
  "input_a": 123,
  "input_b": 4.5,
  "source": "C++ Addon",
  "result": 553.5
}
3. μ‹€ν–‰ / Run
4.μ„€μ •νŒŒμΌμ„ μˆ˜μ •ν•œ 경우 / If you have modified the settings file

4.1 λΉŒλ“œνŒŒμΌμ •λ¦¬ / Build file organization

npx node-gyp clean

4.2 ꡬ성 μž¬μ‹€ν–‰ / Rerun configuration

npx node-gyp configure

4.3 λΉŒλ“œ μž¬μ‹€ν–‰ / Rerun the build

npx node-gyp build

Leave a Reply

Your email address will not be published. Required fields are marked *