笔记
C++和Javascript交互
通过v8源码的示例process.cc和count-hosts.js, 我们可以了解C++和Javascript代码是如何进行交互的.
通过在C++代码中使用v8引擎提供的接口, 可以在Javascript运行上下文中插入使用C++定义的变量(或函数); 同时, 也可以取出Javascript在此上下文中定义的变量(或函数等), 在C++代码中执行.
在Javascript代码中使用通过C++定义的函数
首先创建全局对象, 用于存放build-in函数log
source
Handle<ObjectTemplate> global = ObjectTemplate::New(); global->Set(String::New("log"), FunctionTemplate::New(LogCallback));
在Javascript中, 就可以使用log函数输出日志 source
log("Processing " + request.host + request.path + " from " + request.referrer + "@" + request.userAgent);
在C++中获得使用Javascript定义的函数
在count-hosts.js中定义全局函数Process
function Process(request) { ... }
在process.cc中, 先取出该函数 source
Handle<String> process_name = String::New("Process"); Handle<Value> process_val = context->Global()->Get(process_name); Handle<Function> process_fun = Handle<Function>::Cast(process_val);
之后再调用它
const int argc = 1; Handle<Value> argv[argc] = { request_obj }; v8::Local<v8::Function> process = v8::Local<v8::Function>::New(GetIsolate(), process_); Handle<Value> result = process->Call(context->Global(), argc, argv);
Node.js初始化
Node.js的初始化调用链是这样的, main -> Start -> CreateEnvironment -> Load,
在Start过程中启用了event loop
int Start(int argc, char** argv) { ... Locker locker(node_isolate); Environment* env = CreateEnvironment(node_isolate, argc, argv, exec_argc, exec_argv); // This Context::Scope is here so EnableDebug() can look up the current // environment with Environment::GetCurrentChecked(). // TODO(bnoordhuis) Reorder the debugger initialization logic so it can // be removed. Context::Scope context_scope(env->context()); * uv_run(env->event_loop(), UV_RUN_DEFAULT); EmitExit(env); RunAtExit(env); env->Dispose(); env = NULL; ... }
在node:Load加载了node.js, node.js负责初始化Node.js, 包括初始化全局变量和函数, 如setTimeout, nextTick等.
Node.js模块
Node.js中, 模块是通过require
来加载的, 而其背后的实现在src/node.js中.
NativeModule.require
首先检测模块是否在缓存中(已经被require的模块就会缓存), 如果没有则读取该模块文件内容, 并在当前上下文中执行.
读取模块文件内容使用NativeModule._sources
,
NativeModule.getSource = function(id) { return NativeModule._source[id]; }
而NativeModule._sources
是通过process.binding
获取的
NativeModule._source = process.binding('natives');
和读取模块内容一样, 在当前上下文执行代码最终也是通过process.binding
获取背后的黑盒来实现的.
var fn = runInThisContext(source, { filename: this.filename });
var ContextifyScript = process.binding('contextify').ContextifyScript; function runInThisContext(code, options) { var script = new ContextifyScript(code, options); return script.runInThisContext(); }
追查源码, 可以在node_contextify.cc看到contextify最终的C++实现.
这里可以看到, process.binding
作为一个桥梁, 使得Node.js可以调用C++中实现的代码.
process.binding
重新review之前提到的Node.js初始化代码, 可以发现process.binding
的实现.
在node:CreateEnvironment过程中, 会初始化process
对象
SetupProcessObject(env, argc, argv, exec_argc, exec_argv);
node:SetupProcessObject会设置process.binding
方法
Binding方法接受参数, 然后通过调用get_buildin_module
返回使用C++编写的模块
node_module_struct* mod = get_builtin_module(*module_v);
get_builtin_module通过事先注册的模块列表node_module_list
来加载模块, node_module_list
是通过宏实现的.
在src/node_extensions.h中定义宏NODE_EXT_LIST, 其中包含了使用C++编写的模块
在src/node_extensions.cc中, 调用宏, 展开过程中使用到得诸如node_fs_module
变量则是在每个C++模块底部定义的
NODE_MODULE_CONTEXT_AWARE(node_contextify, node::InitContextify);
这个宏展开后的结果是
extern "C" { node::node_module_struct node_contextify_module = { 13 , __null , __FILE__ , __null , ( node::InitContextify ), "node_contextify"} ; };
get_builtin_module
中获取了C++模块后, 通过使用register_context_func
模块自己制定的注册函数完成注册的步骤.
mod->register_context_func(exports, unused, env->context());
异步实现
追踪fs.readFile回调
为了追查异步调用的实现, 我们先从一个常用的异步方法fs.readFile开始,
fs.readFile
使用fs.read来读取数据, 而fs.read最终调用了
binding.read(fd, buffer, offset, length, position, wrapper);
其中binding
是这样定义的
var binding = process.binding('fs');
也就是说, node_file.cc为文件操作提供了最终实现.
fs.read
在node_file.cc中实现为Read, 这个实现是对read(2)
的一个包装.
在Read
中, 获取了异步调用的回调函数, 并将其传入ASYNC_CALL
cb = args[5]; if (cb->IsFunction()) { ASYNC_CALL(read, cb, fd, buf, len, pos); } else { SYNC_CALL(read, 0, fd, buf, len, pos) args.GetReturnValue().Set(SYNC_RESULT); }
对async
进行宏展开,
Environment* env = Environment::GetCurrent(args.GetIsolate()); FSReqWrap* req_wrap = new FSReqWrap(env, "read" ); int err = uv_fs_read (env->event_loop(), &req_wrap->req_, fd , buf , len , pos , After); req_wrap->object()->Set(env->oncomplete_string(), cb ); req_wrap->Dispatched(); if (err < 0) { uv_fs_t* req = &req_wrap->req_; req->result = err; req->path = __null ; After(req); } args.GetReturnValue().Set(req_wrap->persistent());
在libuv中, uv_file_read
的定义是这样的,
UV_EXTERN int uv_fs_read(uv_loop_t* loop, uv_fs_t* req, uv_file file,void* buf, size_t length, int64_t offset, uv_fs_cb cb);
它使用事件循环loop
, 当文件读取操作完成后, 将会调用回调函数cb
. 后面一节会描述libuv是如何实现完成事件调用函数的功能的.