Node.js 源码分析 - 原生模块(C++模块)的注册


title: Node.js 源码分析 - 原生模块(C++模块)的注册
date: 2018-11-28 21:04:49
tags:
- Node.js
- Node.js 源码分析
- 源码分析
categories:
- Node.js 源码分析


此文最初于四年前发布在个人站上的,现迁移至此重发,原链接:https://laogen.site/nodejs/nodejs-src/register-builtin-modules/
《Node.js 源码分析》 系列目录页:https://laogen.site/nodejs/nodejs-src/index/

上一篇提到 RegisterBuiltinModules() 注册了原生 C++ 模块没有详细展开,这里就从这个函数展开。

将 RegisterBuiltinModules() 层层展开

/* src/node.cc:3066 */
void RegisterBuiltinModules() {
#define V(modname) _register_##modname();
  NODE_BUILTIN_MODULES(V)
#undef V
}

首先定义了一个宏 V_register_##modname(), 可以看出 V 展开后是一个函数调用类似这样: _register_xx();

随后,RegisterBuiltinModules() 实际是宏 NODE_BUILTIN_MODULES(V) 来实现的,我们看看它的定义:

/* src/node_internals.h:147 */
#define NODE_BUILTIN_MODULES(V)    \
  NODE_BUILTIN_STANDARD_MODULES(V)
// ...

进一步查看 NODE_BUILTIN_STANDARD_MODULES(V) 的定义:

/* src/node_internals.h:106 */
#define NODE_BUILTIN_STANDARD_MODULES(V)  \
    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)           \
    V(zlib)

这个宏定义中多次调用宏 V,还记得这个宏吗,在上面定义的:#define V(modname) _register_##modname();,那我们把它展开后就是:

/* src/node_internals.h:106 */
#define NODE_BUILTIN_STANDARD_MODULES(V)  \
    _register_async_wrap();
    _register_buffer();
    _register_cares_wrap();
    _register_config();
    _register_contextify();
    _register_domain();
    _register_fs();
    _register_fs_event_wrap();
    _register_heap_utils();
    _register_http2();
    _register_http_parser();
    _register_inspector();
    _register_js_stream();
    _register_messaging();
    _register_module_wrap();
    _register_options();
    _register_os();
    _register_performance();
    _register_pipe_wrap();
    _register_process_wrap();
    _register_serdes();
    _register_signal_wrap();
    _register_spawn_sync();
    _register_stream_pipe();
    _register_stream_wrap();
    _register_string_decoder();
    _register_symbols();
    _register_tcp_wrap();
    _register_timer_wrap();
    _register_trace_events();
    _register_tty_wrap();
    _register_types();
    _register_udp_wrap();
    _register_url();
    _register_util();
    _register_uv();
    _register_v8();
    _register_worker();
    _register_zlib();

最终,RegisterBuiltinModules() 展开后大概是这样的:

void RegisterBuiltinModules() {
  _register_async_wrap();
  _register_buffer();
  // ...
  _register_os();
  // ...
}

经过层层的宏展开,我们看到 RegisterBuiltinModules() 的原貌,就是调用了一些全局注册函数,这样就能理解了。

接下来,我们打算看看这些注册函数是在哪里定义的。 我全局搜索了整个代码目录,也没找到这些函数中的任何一个,看来又是通过宏定义的。

那我们就挑一个原生模块的源码,来看看里面有没有上面注册函数的定义,我挑了模块名为 os 的模块,它的源码位于 src/node_os.cc

查看一个原生模块的源码

/* src/node_os.cc */
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) {
  // ...
}

// 这个初始化函数是每个原生模块都会定义的,它的参数也是一致的
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

NODE_BUILTIN_MODULE_CONTEXT_AWARE(os, node::os::Initialize)

这个 os 模块先是定义了一些函数,代码最后一行是个宏调用,这个宏把模块名 osInitialize 函数做为其参数,我们找到它的定义如下:

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

又是一个宏定义,继续跟下去:

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

这个宏的定义里好像看到了我们要找的代码,我们在这里就可以把 NODE_BUILTIN_MODULE_CONTEXT_AWARE(os, node::os::Initialize) 完全展开了:

// 创建一个 node_module 对象 _module
static node::node_module _module = {
    NODE_MODULE_VERSION,
    NM_F_BUILTIN,
    nullptr,
    __FILE__,
    nullptr,
    (node::addon_context_register_func) (node::os::Initialize),  
    NODE_STRINGIFY(os),
    nullptr,
    nullptr
};

// 定义我们要找的 _register_os() 函数
void _register_os() {    
  node_module_register(&_module); 
}

到此,我们就明白了 RegisterBuiltinModules() 函数中调用的 _register_os() 是在哪里定义的了,随后查看了所有原生模块的代码,最后一行都是以同样的方式定义相应的 _register_xx()

其中 node::node_module 类型就代表一个模块的信息。

所谓注册 os 模块实际是调用了 node_module_register(node_module *) 函数完成的,我们继续来看看 node_module_register() 函数和 node::node_module

模块注册实现

/* src/node.h:518*/
struct node_module {
  int nm_version;
  unsigned int nm_flags;
  void* nm_dso_handle;
  const char* nm_filename;
  // 上例中 Initialize 函数被赋到 nm_register_func 里
  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;  
};
/* src/node.cc:1094 */
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 (mp->nm_flags & NM_F_INTERNAL) {
    // 链表操作
    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);
  }
}

到这里就清晰了, 所谓原生模块的注册,实际上就是将一个类型为 node::node_module 的模块对象,添加到不同类别的全局链表中。

上述代码中用3个全局链表:modlist_builtin modlist_internal modlist_linked 分别保存不同类型的模块,本文我们说的是 BUILTIN 类型的,也就是第一个。

我把这几个链表的定义位置发出来:

/* src/node.cc:175 */
static node_module* modlist_builtin;   // 我们现在只关注 builtin 模块
static node_module* modlist_internal;
static node_module* modlist_linked;
static node_module* modlist_addon;

小结

这个原生模块的注册过程就写到这里,逻辑还是比较简单的,只是连续的宏定义让代码不那么直观。

原生模块加载写完后,接下来,会继续写原生模块的加载篇。

Maslow (wangfugen@126.com), laf.js 作者。

lafyun.com 开源云开发平台,前端变全栈,无需服务端。

原文地址:https://www.cnblogs.com/maslow/p/15918449.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


这篇文章主要介绍“基于nodejs的ssh2怎么实现自动化部署”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“基于nodejs...
本文小编为大家详细介绍“nodejs怎么实现目录不存在自动创建”,内容详细,步骤清晰,细节处理妥当,希望这篇“nodejs怎么实现目录不存在自动创建”文章能帮助大...
这篇“如何把nodejs数据传到前端”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这...
本文小编为大家详细介绍“nodejs如何实现定时删除文件”,内容详细,步骤清晰,细节处理妥当,希望这篇“nodejs如何实现定时删除文件”文章能帮助大家解决疑惑...
这篇文章主要讲解了“nodejs安装模块卡住不动怎么解决”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来...
今天小编给大家分享一下如何检测nodejs有没有安装成功的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文...
本篇内容主要讲解“怎么安装Node.js的旧版本”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎...
这篇“node中的Express框架怎么安装使用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家...
这篇文章主要介绍“nodejs如何实现搜索引擎”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“nodejs如何实现搜索引擎...
这篇文章主要介绍“nodejs中间层如何设置”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“nodejs中间层如何设置”文...
这篇文章主要介绍“nodejs多线程怎么实现”,在日常操作中,相信很多人在nodejs多线程怎么实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法...
这篇文章主要讲解了“nodejs怎么分布式”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“nodejs怎么分布式”...
本篇内容介绍了“nodejs字符串怎么转换为数组”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情...
这篇文章主要介绍了nodejs如何运行在php服务器的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇nodejs如何运行在php服务器文章都...
本篇内容主要讲解“nodejs单线程如何处理事件”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“nodejs单线程如何...
这篇文章主要介绍“nodejs怎么安装ws模块”,在日常操作中,相信很多人在nodejs怎么安装ws模块问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法...
本篇内容介绍了“怎么打包nodejs代码”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!
本文小编为大家详细介绍“nodejs接收到的汉字乱码怎么解决”,内容详细,步骤清晰,细节处理妥当,希望这篇“nodejs接收到的汉字乱码怎么解决”文章能帮助大家解...
这篇“nodejs怎么同步删除文件”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇...
今天小编给大家分享一下nodejs怎么设置淘宝镜像的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希