本文是基于win7 32位系统。 通过阅读nodejs项目的源代码可以看到,nodejs由 c/c++和javascript俩部分实现,其中js部分的核心为node.js文件。 既然分为2部分 那么中间通讯配合的问题如何解决,其中有一个很关键的对象process就与此有关。我们来看看此对象。 这个对象在js层面是一个全局对象可以直接使用。他的实现代码在src\node.cc中 主要看看其中的SetupProcessObject函数,下面是部分代码截图:
这些原生函数都对应了js中process对象的方法。比如js代码 process. binding( 'xxx') 实际上就调用了c/c++代码中的Binding函数,同理process.dlopen实际上是调用了c/c++代码中的DLOpen函数.而DLOpen中又调用了uv_dlopen, 对于windows版本的uv_dlopen函数位于deps\uv\src\win\dl.c中,如图:
最终调用了LoadLibaryExW加载dll文件。
回过头来再看看 process. binding 这个函数,那么这个函数到底是干什么用的呢?这个函数实际上是用来绑定原生代码中的内置模块用的,简单来说就是js代码利用process. binding( 'xxx') 获取由c/c++实现的内置模块'xxx'的引用,然后js代码就可以通过这个引用访问内置模块'xxx'所提供的各种功能了。
我们知道node可以加载两种模块 一种由js代码编写的模块,后缀名为.js, 还有一种由c/c++实现的原生模块,后缀名为.node。我们看看nodejs中是如何处理的,实现代码在
lib\moudle.js中,如图:
其中加载.json文件比较简单,这里不做讨论。 我们先看看如何加载.js模块。 可以看到代码将进入module._compile函数,在_compile函数中又经过层层调用获取了内置模块
'contextify'的引用,代码如:var binding = process. binding( 'contextify'); 然后通过这个引用调用runInThisContext等内置模块中提供的功能。此函数位于src\node_contextify.cc中,
如图:
RunInThisContext函数:
RunInThisContext函数中又将调用EvalMachine函数,然后EvalMachine函数中会利用v8引擎执行js代码。至此.js模块加载完毕。
接下来看看如何加载.node原生模块,这个就比较简单了,直接调用process.dlopen就加载原生模块了。如前所叙.node模块其实就是原生模块,比如在windows下面.node模块其实就是dll文件,linux下面其实就是so文件。process.dlopen(js代码)=>DLOpen(c/c++)=>uv_dlopen(c/c++)=>LoadLibaryExW(windows版本)。
自此,我们在js代码中就可以通过全局对象process直接或间接的加载各种各样的模块了,也正因为有了如此能力node平台基本上是无所不能了。
那么js代码中这个全局对象process本身又是怎么获取到的了,答案就在node.js这个文件中。代码如下:
整个文件的入口是个函数,函数只有一个参数 就是process,然后程序会将这个process保存为全局对象,代码如下:
那这个process是哪里传进来的了,这个js入口函数又是由谁来调用的呢,调用代码就在src/node.cc中的LoadEnvironment函数中:
void LoadEnvironment(Environment* env) {
HandleScope handle_scope(env->isolate());
V8::SetFatalErrorHandler(node::OnFatalError);
V8::AddMessageListener(OnMessage);
// Compile, execute the src/node.js file. (Which was included as static C
// string in node_natives.h. 'natve_node' is the string containing that
// source code.)
// The node.js file returns a function 'f'
atexit(AtExit);
TryCatch try_catch;
// Disable verbose mode to stop FatalException() handler from trying
// to handle the exception. Errors this early in the start-up phase
// are not safe to ignore.
try_catch.SetVerbose( false);
Local<String> script_name = FIXED_ONE_BYTE_STRING(env->isolate(), "node.js");
Local<Value> f_value = ExecuteString(env, MainSource(env), script_name);
if (try_catch.HasCaught()) {
ReportException(env, try_catch);
exit(10);
}
assert(f_value->IsFunction());
Local<Function> f = Local<Function>::Cast(f_value);
// Now we call 'f' with the 'process' variable that we've built up with
// all our bindings. Inside node.js we'll take care of assigning things to
// their places.
// We start the process this way in order to be more modular. Developers
// who do not like how 'src/node.js' setups the module system but do like
// Node's I/O bindings may want to replace 'f' with their own function.
// Add a reference to the global object
Local<Object> global = env->context()->Global();
#if defined HAVE_DTRACE || defined HAVE_ETW
InitDTrace(env, global);
#endif
#if defined HAVE_PERFCTR
InitPerfCounters(env, global);
#endif
// Enable handling of uncaught exceptions
// (FatalException(), break on uncaught exception in debugger)
//
// This is not strictly necessary since it's almost impossible
// to attach the debugger fast enought to break on exception
// thrown during process startup.
try_catch.SetVerbose( true);
NODE_SET_METHOD(env->process_object(), "_rawDebug", RawDebug);
Local<Value> arg = env->process_object();
f->Call(global, 1, &arg);
}
参见代码最后 f->Call(global, 1, &arg); 就是调用之前的js入口函数,其中arg就是传给js层面的process对象, Local<Value> arg = env->process_object();
当然c/c++与js之间具体的交互细节就属于v8这个js引擎的工作了,这个又是后话了。