Node.js의 코드는 C++과 JavaScript(이하 JS)로 구현되어 있고, JS 코드에서 C++ API를 호출하는 구조로 되어있는데, JS에서 어떻게 C++ API를 호출하는 걸까?


Node.js의 구성요소

V8 JavaScript Engine

  • 구글에서 개발된 JIT(Just-in-time) 가상 머신형식의 JS 엔진
  • ECMAScript(ECMA – 262) 3rd Edition 규격의 C++로 작성되었으며, 독립적으로 실행이 가능

native/builtin module

  • JS 코드를 실행하고 결과를 얻을수 있고, C++ API를 JS에서도 접근 할수 있음
  • JS로 구현할 수 없는 기능을 C++로 구현한 모듈을 builtin module이라 함
  • JS로 구현한 모듈을 native module이라 함

C++ API를 호출하는 방법을 알아보자!

  • src 폴더의 C++ 코드에는 JS에서 C++ API를 사용하게 하기 위한 부분들이 포함되어 있음

빌트인 모듈(builtin module) 등록 과정을 알아보자!

  • node_os.cc파일에서 볼 수 있듯이 Node.js에서 getHostname 함수는 C++ API를 사용해서 GetHostname를 호출함
  • SetMethod(target, "open", Open)의 경우 ‘open’을 Key, ‘Open’을 Value로 구성하여 target에 할당
  • 그리고 해당 모듈을 NODE_MODULE_CONTEXT_AWARE_BUILTIN을 사용해서 Node.js에 등록
/* https://github.com/nodejs/node/blob/master/src/node_file.cc */

[...]

void InitFs(Local<Object> target,  
            Local<Value> unused,
            Local<Context> context,
            void* priv) {

  Environment* env = Environment::GetCurrent(context);

  // Function which creates a new Stats object.
  target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "FSInitialize"),
              env->NewFunctionTemplate(FSInitialize)->GetFunction());
  [...]

  env->SetMethod(target, "open", Open);

  [...]

}

}  // end namespace node

NODE_MODULE_CONTEXT_AWARE_BUILTIN(fs, node::InitFs)  

SetMethod 내부

  • taht의 경우 V8의 JS Object이고, name이 Node.js에서 사용할 함수명이고, callback이 C++ API 함수임
  • that->set()함수를 통해서 JS Object(흔히 말하는 Handle Target)에 함수명과 함수를 등록하고 함수에 함수명을 설정(function->SetName()) 함
/* https://github.com/nodejs/node/blob/master/src/env-inl.h */

[...]

inline void Environment::SetMethod(v8::Local<v8::Object> that,  
                                   const char* name,
                                   v8::FunctionCallback callback) {
  v8::Local<v8::Function> function =
      NewFunctionTemplate(callback)->GetFunction();
  // kInternalized strings are created in the old space.
  const v8::NewStringType type = v8::NewStringType::kInternalized;
  v8::Local<v8::String> name_string =
      v8::String::NewFromUtf8(isolate(), name, type).ToLocalChecked();
  that->Set(name_string, function);
  function->SetName(name_string);  // NODE_SET_METHOD() compatibility.
}

NODEMODULECONTEXT_AWARE 내부

  • C++ 로 작성된 빌트인 코드의 마지막엔 항상 NODE_MODULE_CONTEXT_AWARE를 호출
  • 해당 매크로를 통해서 V8 엔진에서 C++ API를 인식할 수 있게 됨
/* https://github.com/nodejs/node/blob/master/src/node.h */

[...]

struct node_module {  
  int nm_version;
  unsigned int nm_flags;
  void* nm_dso_handle;
  const char* nm_filename;
  node::addon_register_func nm_register_func;
  node::addon_context_register_func nm_context_register_func;
  const char* nm_modname;
  void* nm_priv;
  struct node_module* nm_link;
};

[...]

#define NODE_C_CTOR(fn)                                               \
  NODE_CTOR_PREFIX void __cdecl fn(void);                             \
  __declspec(dllexport, allocate(".CRT$XCU"))                         \
      void (__cdecl*fn ## _)(void) = fn;                              \
  NODE_CTOR_PREFIX void __cdecl fn(void)
#else
#define NODE_C_CTOR(fn)                                               \
  NODE_CTOR_PREFIX void fn(void) __attribute__((constructor));        \
  NODE_CTOR_PREFIX void fn(void)
#endif

#define NODE_MODULE_CONTEXT_AWARE_X(modname, regfunc, priv, flags)    \
  extern "C" {                                                        \
    static node::node_module _module =                                \
    {                                                                 \
      NODE_MODULE_VERSION,                                            \
      flags,                                                          \
      NULL,                                                           \
      __FILE__,                                                       \
      NULL,                                                           \
      (node::addon_context_register_func) (regfunc),                  \
      NODE_STRINGIFY(modname),                                        \
      priv,                                                           \
      NULL                                                            \
    };                                                                \
    NODE_C_CTOR(_register_ ## modname) {                              \
      node_module_register(&_module);                                 \
    }                                                                 \
  }

#define NODE_MODULE(modname, regfunc)                                 \
  NODE_MODULE_X(modname, regfunc, NULL, 0)

#define NODE_MODULE_CONTEXT_AWARE(modname, regfunc)                   \
  NODE_MODULE_CONTEXT_AWARE_X(modname, regfunc, NULL, 0)

#define NODE_MODULE_CONTEXT_AWARE_BUILTIN(modname, regfunc)           \
  NODE_MODULE_CONTEXT_AWARE_X(modname, regfunc, NULL, NM_F_BUILTIN)   \
  • NODE_C_CTOR 매크로의 역활은 node_module_register 함수를 호출하는 역할을 담당
  • nodemoduleregister은 구조체를 modlist_builtin에 추가
// https://github.com/nodejs/node/blob/master/src/node.h

NODE_C_CTOR(_register_ ## modname) {                              \  
  node_module_register(&_module);                                 \
}                                                                 \

// https://github.com/nodejs/node/blob/master/src/node.cc

extern "C" void node_module_register(void* m) {  
  struct node_module* mp = reinterpret_cast<struct node_module*>(m);
  if (mp->nm_flags & NM_F_BUILTIN) {
    mp->nm_link = modlist_builtin;
    modlist_builtin = mp;
  } else if (!node_is_initialized) {
    // "Linked" modules are included as part of the node project.
    // Like builtins they are registered *before* node::Init runs.
    mp->nm_flags = NM_F_LINKED;
    mp->nm_link = modlist_linked;
    modlist_linked = mp;
  } else {
    modpending = mp;
  }
}