Node.js에서 C++ API를 호출하는 방법
Node.js의 코드는 C++과 JavaScript(이하 JS)로 구현되어 있고, JS 코드에서 C++ API를 호출하는 구조로 되어있는데, JS에서 어떻게 C++ API를 호출하는 걸까?
Node.js의 구성요소
V8 JavaScript Engine
- 구글에서 개발된 JIT(Just-in-time) 가상 머신형식의 JS 엔진
- ECMAScript(ECMA – 262) 3rd Edition 규격의 C++로 작성되었으며, 독립적으로 실행이 가능
native/builtin module
- JS 코드를 실행하고 결과를 얻을수 있고, C++ API를 JS에서도 접근 할수 있음
- JS로 구현할 수 없는 기능을 C++로 구현한 모듈을
builtin module이라 함 - JS로 구현한 모듈을
native module이라 함
C++ API를 호출하는 방법을 알아보자!
- src 폴더의 C++ 코드에는 JS에서 C++ API를 사용하게 하기 위한 부분들이 포함되어 있음
빌트인 모듈(builtin module) 등록 과정을 알아보자!
node_os.cc파일에서 볼 수 있듯이 Node.js에서getHostname함수는 C++ API를 사용해서GetHostname를 호출함SetMethod(target, "open", Open)의 경우 ‘open’을 Key, ‘Open’을 Value로 구성하여 target에 할당- 그리고 해당 모듈을
NODE_MODULE_CONTEXT_AWARE_BUILTIN을 사용해서 Node.js에 등록
/* https://github.com/nodejs/node/blob/master/src/node_file.cc */
[...]
void InitFs(Local<Object> target,
Local<Value> unused,
Local<Context> context,
void* priv) {
Environment* env = Environment::GetCurrent(context);
// Function which creates a new Stats object.
target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "FSInitialize"),
env->NewFunctionTemplate(FSInitialize)->GetFunction());
[...]
env->SetMethod(target, "open", Open);
[...]
}
} // end namespace node
NODE_MODULE_CONTEXT_AWARE_BUILTIN(fs, node::InitFs)
SetMethod 내부
taht의 경우 V8의 JS Object이고,name이 Node.js에서 사용할 함수명이고,callback이 C++ API 함수임that->set()함수를 통해서 JS Object(흔히 말하는 Handle Target)에 함수명과 함수를 등록하고 함수에 함수명을 설정(function->SetName()) 함
/* https://github.com/nodejs/node/blob/master/src/env-inl.h */
[...]
inline void Environment::SetMethod(v8::Local<v8::Object> that,
const char* name,
v8::FunctionCallback callback) {
v8::Local<v8::Function> function =
NewFunctionTemplate(callback)->GetFunction();
// kInternalized strings are created in the old space.
const v8::NewStringType type = v8::NewStringType::kInternalized;
v8::Local<v8::String> name_string =
v8::String::NewFromUtf8(isolate(), name, type).ToLocalChecked();
that->Set(name_string, function);
function->SetName(name_string); // NODE_SET_METHOD() compatibility.
}
NODEMODULECONTEXT_AWARE 내부
- C++ 로 작성된 빌트인 코드의 마지막엔 항상
NODE_MODULE_CONTEXT_AWARE를 호출 - 해당 매크로를 통해서 V8 엔진에서 C++ API를 인식할 수 있게 됨
/* https://github.com/nodejs/node/blob/master/src/node.h */
[...]
struct node_module {
int nm_version;
unsigned int nm_flags;
void* nm_dso_handle;
const char* nm_filename;
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;
};
[...]
#define NODE_C_CTOR(fn) \
NODE_CTOR_PREFIX void __cdecl fn(void); \
__declspec(dllexport, allocate(".CRT$XCU")) \
void (__cdecl*fn ## _)(void) = fn; \
NODE_CTOR_PREFIX void __cdecl fn(void)
#else
#define NODE_C_CTOR(fn) \
NODE_CTOR_PREFIX void fn(void) __attribute__((constructor)); \
NODE_CTOR_PREFIX void fn(void)
#endif
#define NODE_MODULE_CONTEXT_AWARE_X(modname, regfunc, priv, flags) \
extern "C" { \
static node::node_module _module = \
{ \
NODE_MODULE_VERSION, \
flags, \
NULL, \
__FILE__, \
NULL, \
(node::addon_context_register_func) (regfunc), \
NODE_STRINGIFY(modname), \
priv, \
NULL \
}; \
NODE_C_CTOR(_register_ ## modname) { \
node_module_register(&_module); \
} \
}
#define NODE_MODULE(modname, regfunc) \
NODE_MODULE_X(modname, regfunc, NULL, 0)
#define NODE_MODULE_CONTEXT_AWARE(modname, regfunc) \
NODE_MODULE_CONTEXT_AWARE_X(modname, regfunc, NULL, 0)
#define NODE_MODULE_CONTEXT_AWARE_BUILTIN(modname, regfunc) \
NODE_MODULE_CONTEXT_AWARE_X(modname, regfunc, NULL, NM_F_BUILTIN) \
NODE_C_CTOR매크로의 역활은node_module_register함수를 호출하는 역할을 담당- nodemoduleregister은 구조체를
modlist_builtin에 추가
// https://github.com/nodejs/node/blob/master/src/node.h
NODE_C_CTOR(_register_ ## modname) { \
node_module_register(&_module); \
} \
// https://github.com/nodejs/node/blob/master/src/node.cc
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 (!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 {
modpending = mp;
}
}
Array
