Node.js不仅可以单独运行,还可以作为一个库来使用。本文介绍如何将Node.js嵌入到您自己的项目中。第一步是下载Node.js源码,然后根据Node.js文档编译安装。这样我们就可以得到Node.js提供的头文件和库文件了。接下来根据官方demo写一个测试程序。#include"node.h"#include"uv.h"#includeusingnode::CommonEnvironmentSetup;usingnode::Environment;usingnode::MultiIsolatePlatform;usingv8::Context;usingv8::HandleScope;使用v8::Isolate;使用v8::Locker;使用v8::MaybeLocal;使用v8::V8;使用v8::Value;staticintRunNodeInstance(MultiIsolatePlatform*platform,conststd::vector&args,conststd::vector&exec_args);intmain(intargc,char**argv){argv=uv_setup_args(argc,argv);}std::vectorargs(argv,argv+argc);std::vector执行参数;std::vector错误;intexit_code=node::InitializeNodeWithArgs(&args,&exec_args,&errors);for(conststd::string&error:errors)fprintf(stderr,"%s:%s\n",args[0].c_str(),error.c_str());如果(退出代码!=0){返回退出代码;}std::unique_ptr平台=MultiIsolatePlatform::Create(4);V8::InitializePlatform(platform.get());V8::初始化();intret=RunNodeInstance(platform.get(),args,exec_args);V8::Dispose();//V8::DisposePlatform();returnret;}intRunNodeInstance(MultiIsolatePlatform*platform,conststd::vector&args,conststd::vector&exec_args){intexit_code=0;std::vector错误;std::unique_ptrsetup=CommonEnvironmentSetup::Create(platform,&errors,args,exec_args);if(!setup){for(conststd::string&err:errors)fprintf(stderr,"%s:%s\n",args[0].c_str(),err.c_str());返回1;隔离*isolate=setup->isolate();环境*env=setup->env();{储物柜储物柜(隔离);隔离::作用域isolate_scope(isolate);HandleScopehandle_scope(隔离);上下文::范围context_scope(setup->context());MaybeLocalloadenv_ret=node::LoadEnvironment(env,"constpublicRequire="require('module').createRequire(process.cwd()+'/');""globalThis.require=publicRequire;""publicRequire('./test')");if(loadenv_ret.IsEmpty())//出现JS异常return1;exit_code=node::SpinEventLoop(env).FromMaybe(1);node::Stop(env);}returnexit_code;}大部分都是默认进程,我们暂时可以忽略,关键是LoadEnvironment函数LoadEnvironment的逻辑最终会执行我们传入的字符串代码for.这段代码中,前面是Node.js提供的demo,最后一句是我加的,在test.js中简单输出helloworld,下面编译一下。g++Node.ccsrc/node_code_cache_stub.ccsrc/node_snapshot_stub.cc-I/node/v17.9.0/include/node-std=c++14-L/node/out/Release-lv8_base_without_compiler-lv8_compiler-lv8_init-lv8_initializers-lv8_libbase-lv8_libplatform-lv8_snapshot-lbrotli-lcares-lgtest-lgtest_main-l直方图-licudata-licui18n-licutools-licuucx-lllhttp-lnghttp2-lnghttp3-lngtcp2-lopenssl-ltorque_base-luv-luvwasi-lv8_zlib-lzlib-lpthread-lnode因为找不到代码缓存和快照功能的符号,这里曲线救国,来自Node.js源码引入这两个文件,具体方案稍后再研究。编译后得到一个a.out文件,执行该文件可以看到输出的helloworld。太棒了,我们已经实现了将Node.js嵌入到我们的项目中。让我们来看看其中涉及的一些逻辑。从LoadEnvironment看。MaybeLocalLoadEnvironment(Environment*env,constchar*main_script_source_utf8){Isolate*isolate=env->isolate();}returnLoadEnvironment(env,[&](constStartExecutionCallbackInfo&info)->MaybeLocal{//一个会分析});}LoadEnvironment进一步调整另一个LoadEnvironment。MaybeLocalLoadEnvironment(Environment*env,StartExecutionCallbackcb){env->InitializeLibuv();env->InitializeDiagnostics();returnStartExecution(env,cb);}LoadEnvironment做一些初始化,然后调用StartExecution。MaybeLocalStartExecution(Environment*env,StartExecutionCallbackcb){InternalCallbackScopecallback_scope(env,Object::New(env->isolate()),{1,0},InternalCallbackScope::kSkipAsyncHooks);}if(cb!=nullptr){EscapableHandleScope作用域(env->isolate());如果(StartExecution(env,"internal/bootstrap/environment").IsEmpty())返回{};StartExecutionCallbackInfo信息={env->process_object(),env->native_module_require(),};返回scope.EscapeMaybe(cb(info));}}StartExecution最后执行了第一个传入回调的LoadEnvironment,传入了进程和原生JS模块加载器。然后看回调函数的逻辑。std::stringname="embedder_main_"+std::to_string(env->thread_id());//插入原生JS模块代码native_module::NativeModuleEnv::Add(name.c_str(),UnionBytes(**main_utf16,main_utf16->length()));env->set_main_utf16(std::move(main_utf16));std::vector>params={env->process_string(),env->require_string()};std::vector>args={env->process_object(),env->native_module_require()};//执行我们的代码returnExecuteBootstrapper(env,name.c_str(),¶ms,&args);回调函数执行我们通过ExecuteBootstrapper传入的代码。MaybeLocalExecuteBootstrapper(Environment*env,constchar*id,std::vector>*parameters,std::vector>*arguments){EscapableHandleScope范围(env->isolate());//从原生JS模块代码中找到我们的代码MaybeLocalmaybe_fn=NativeModuleEnv::LookupAndCompile(env->context(),id,parameters,env);本地<函数>fn;如果(!maybe_fn.ToLocal(&fn)){returnMaybeLocal();}//执行我们的代码MaybeLocalresult=fn->Call(env->context(),Undefined(env->isolate()),arguments->size(),arguments->data());}让我们回头看看我们的代码。constpublicRequire=require('module').createRequire(process.cwd()+'/');globalThis.require=publicRequire;publicRequire('./test');require函数是一个原生的JS模块加载器,可以用来加载Node.js原生的JS模块。可以使用模块模块创建用户JS模块加载器。通过使用JS模块加载器,我们可以将代码串在一起。