Node.js source code analysis - registration of native modules (C + + Modules)

Posted by Warmach on Sun, 20 Feb 2022 04:55:36 +0100

title: Node.js source code analysis - registration of native modules (C + + Modules)
date: 2018-11-28 21:04:49

- Node.js
- Node.js Source code analysis
- Source code analysis


- Node.js Source code analysis

This article was first published on the personal website four years ago, and is now migrated to this site for re posting. The original link is:
Directory page of Node.js source code analysis series:

In the previous article, it was mentioned that RegisterBuiltinModules() registered the native C + + module and did not expand it in detail. Here we will expand it from this function.

<!-- more -->

Expand RegisterBuiltinModules() layer by layer

/* src/ */
void RegisterBuiltinModules() {
#define V(modname) _register_##modname();
#undef V

Firstly, a macro V is defined as_ register_##modname(), you can see that after V expansion, there is a function call, similar to this:_ register_xx();

Subsequently, RegisterBuiltinModules() is actually a macro NODE_BUILTIN_MODULES(V). Let's see its definition:

/* src/node_internals.h:147 */
// ...

Take a closer look at node_ BUILTIN_ STANDARD_ Definition of modules (V):

/* src/node_internals.h:106 */
    V(async_wrap)       \
    V(buffer)           \
    V(cares_wrap)       \
    V(config)           \
    V(contextify)       \
    V(domain)           \
    V(fs)               \
    V(fs_event_wrap)    \
    V(heap_utils)       \
    V(http2)            \
    V(http_parser)      \
    V(inspector)        \
    V(js_stream)        \
    V(messaging)        \
    V(module_wrap)      \
    V(options)          \
    V(os)               \
    V(performance)      \
    V(pipe_wrap)        \
    V(process_wrap)     \
    V(serdes)           \
    V(signal_wrap)      \
    V(spawn_sync)       \
    V(stream_pipe)      \
    V(stream_wrap)      \
    V(string_decoder)   \
    V(symbols)          \
    V(tcp_wrap)         \
    V(timer_wrap)       \
    V(trace_events)     \
    V(tty_wrap)         \
    V(types)            \
    V(udp_wrap)         \
    V(url)              \
    V(util)             \
    V(uv)               \
    V(v8)               \
    V(worker)           \

Macro V is called many times in this macro definition. Remember this macro, defined above: #define V (modname)_ register_## modname();, Then we expand it to:

/* src/node_internals.h:106 */

Finally, after RegisterBuiltinModules() is expanded, it is roughly as follows:

void RegisterBuiltinModules() {
  // ...
  // ...

After layers of macro expansion, we can see the original appearance of RegisterBuiltinModules(), which calls some global registration functions, so we can understand it.

Next, we're going to see where these registration functions are defined. I searched the whole code directory globally, but I didn't find any of these functions. It seems that they are defined through macros.

Then let's choose the source code of a native module to see if there is the definition of the above registration function. I chose the module named OS, whose source code is located in src/

View the source code of a native module

/* src/ */
namespace node {
namespace os {
// ...
static void GetHostname(const FunctionCallbackInfo<Value>& args) {
  // ...
static void GetOSType(const FunctionCallbackInfo<Value>& args) {
  // ...
static void GetOSRelease(const FunctionCallbackInfo<Value>& args) {
  // ...
static void GetCPUInfo(const FunctionCallbackInfo<Value>& args) {
  // ...
static void GetFreeMemory(const FunctionCallbackInfo<Value>& args) {
  // ...
static void GetTotalMemory(const FunctionCallbackInfo<Value>& args) {
  // ...
static void GetUptime(const FunctionCallbackInfo<Value>& args) {
  // ...
static void GetLoadAvg(const FunctionCallbackInfo<Value>& args) {
  // ...
static void GetInterfaceAddresses(const FunctionCallbackInfo<Value>& args) {
  // ...
static void GetHomeDirectory(const FunctionCallbackInfo<Value>& args) {
  // ...
static void GetUserInfo(const FunctionCallbackInfo<Value>& args) {
  // ...
static void SetPriority(const FunctionCallbackInfo<Value>& args) {
  // ...
static void GetPriority(const FunctionCallbackInfo<Value>& args) {
  // ...

// This initialization function is defined by each native module, and its parameters are consistent
void Initialize(Local<Object> target,
                Local<Value> unused,
                Local<Context> context) {
  Environment* env = Environment::GetCurrent(context);
  env->SetMethod(target, "getHostname", GetHostname);
  env->SetMethod(target, "getLoadAvg", GetLoadAvg);
  env->SetMethod(target, "getUptime", GetUptime);
  env->SetMethod(target, "getTotalMem", GetTotalMemory);
  env->SetMethod(target, "getFreeMem", GetFreeMemory);
  env->SetMethod(target, "getCPUs", GetCPUInfo);
  env->SetMethod(target, "getOSType", GetOSType);
  env->SetMethod(target, "getOSRelease", GetOSRelease);
  env->SetMethod(target, "getInterfaceAddresses", GetInterfaceAddresses);
  env->SetMethod(target, "getHomeDirectory", GetHomeDirectory);
  env->SetMethod(target, "getUserInfo", GetUserInfo);
  env->SetMethod(target, "setPriority", SetPriority);
  env->SetMethod(target, "getPriority", GetPriority);
  target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "isBigEndian"),
              Boolean::New(env->isolate(), IsBigEndian()));

}  // namespace os
}  // namespace node


The os module first defines some functions, and the last line of the code is a macro call. The macro takes the module name os and Initialize function as its parameters. We find its definition as follows:

/* src/node_internals.h:169 */
#define NODE_BUILTIN_MODULE_CONTEXT_AWARE(modname, regfunc)   \
  NODE_MODULE_CONTEXT_AWARE_CPP(modname, regfunc, nullptr, NM_F_BUILTIN)

Another macro definition. Continue:

/* src/node_internals.h:152*/
#define NODE_MODULE_CONTEXT_AWARE_CPP(modname, regfunc, priv, flags) \
  static node::node_module _module = { \
    flags,      \
    nullptr,    \
    __FILE__,   \
    nullptr,    \
    (node::addon_context_register_func) (regfunc),  \
    NODE_STRINGIFY(modname),    \
    priv,   \
    nullptr \
  };    \
  void _register_ ## modname() {    \
    node_module_register(&_module); \

It seems that we have seen the code we are looking for in the definition of this macro. We can put node here_ BUILTIN_ MODULE_ CONTEXT_ Aware (OS, node:: OS:: initialize) is fully expanded:

// Create a node_module object_ module
static node::node_module _module = {
    (node::addon_context_register_func) (node::os::Initialize),  

// Define what we're looking for_ register_os() function
void _register_os() {    

In this way, we can understand the call in RegisterBuiltinModules() function. register_ Where is OS () defined? Then I looked at the code of all the native modules. The last line defines the corresponding in the same way_ register_xx().

Where node::node_module type represents the information of a module.

The so-called registered os module actually calls node_ module_ The register (node_module *) function is completed. Let's continue to look at node_module_register() function and node::node_module:

Module registration implementation

/* src/node.h:518*/
struct node_module {
  int nm_version;
  unsigned int nm_flags;
  void* nm_dso_handle;
  const char* nm_filename;
  // In the above example, the Initialize function is assigned to nm_ register_ Funcli
  node::addon_register_func nm_register_func;  
  node::addon_context_register_func nm_context_register_func;
  const char* nm_modname; // Module name
  void* nm_priv;
  struct node_module* nm_link;  
/* src/ */
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) {
    // Linked list operation
    mp->nm_link = modlist_builtin;
    modlist_builtin = mp;
  } else if (mp->nm_flags & NM_F_INTERNAL) {
    // Linked list operation
    mp->nm_link = modlist_internal;
    modlist_internal = 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 {
    uv_key_set(&thread_local_modpending, mp);

It is clear from here that the so-called registration of native modules is actually a type of node:: node_ The module object of module is added to the global linked list of different categories.

The above code uses three global linked lists: modlist_builtin modlist_internal modlist_linked stores different types of modules. In this paper, we are talking about BUILTIN, that is, the first one.

I send out the defined positions of these linked lists:

/* src/ */
static node_module* modlist_builtin;   // For now, we only focus on the builtin module
static node_module* modlist_internal;
static node_module* modlist_linked;
static node_module* modlist_addon;


The registration process of this native module is written here. The logic is still relatively simple, but the continuous macro definition makes the code less intuitive.

After loading and writing the native module, we will continue to write the loading part of the native module.

By Maslow (, laf.js Author. Open source cloud development platform, the front end becomes the full stack, and there is no need for the server.

Topics: node.js TypeScript