자바 스크립트의 성능이 충분하지 않기 때문에 네이티브 Node.js 모듈에 더 의존해야합니다.
네이티브 확장은 처음에는 초보적인 주제는 아니지만 모든 Node.js 개발자가이 기능을 어떻게 활용하는지에 대한 지식을 얻기 위해이 기사를 추천합니다.
Node.js를 Scale로 사용 하여 더 큰 Node.js 설치가있는 회사와 Node의 기본 사항을 이미 배운 개발자의 요구에 초점을 맞춤.
기본 Node.js 모듈의 일반적인 사용 사례
네이티브 모듈에 대한 지식은 네이티브 확장을 의존성으로 추가 할 때 편리합니다.
네이티브 확장을 사용하는 인기있는 모듈 목록을 살펴보십시오. 적어도 하나는 사용하고 있잖아, 그렇지?
- https://github.com/wadey/node-microtime
- https://github.com/node-inspector
- https://github.com/node-inspector/v8-profiler
- http://www.nodegit.org/
네이티브 Node.js 모듈 작성을 고려해야하는 몇 가지 이유가 있습니다. 여기에는 다음이 포함되지만 이에 국한되지는 않습니다.
성능에 중요한 애플리케이션 : 솔직히 말해서, Node.js는 비동기식 I / O 작업을 수행하는 데 적합하지만 실제 업무 처리와 관련하여 선택의 폭이 크지 않습니다.
하위 레벨 (예 : 운영 체제) API로 연결
C 또는 C ++ 라이브러리와 Node.js 사이에 브리지 만들기
네이티브 모듈은 무엇입니까?
Node.js Addons는 C 또는 C ++로 작성된 동적으로 링크 된 공유 객체로서 require () 함수를 사용하여 Node.js에로드 할 수 있으며 일반적인 Node.js 모듈처럼 사용할 수 있습니다. - Node.js 문서에서
즉, (제대로 된 경우) C / C ++의 단점은 모듈의 소비자로부터 숨길 수 있습니다. 그들이 대신 보게되는 것은 자바 스크립트로 작성한 것처럼 모듈이 Node.js 모듈이라는 것입니다.
이전 블로그 게시물에서 배웠 듯이 Node.js는 독자적으로 C 프로그램 인 V8 JavaScript 엔진에서 실행됩니다. 이 C 프로그램과 직접 상호 작용하는 코드를 자체 언어로 작성할 수 있습니다. 비싼 직렬화 및 통신 오버 헤드를 피할 수 있기 때문에 좋습니다.
또한 이전 blogpost에서 Node.js 가비지 수집기 의 비용에 대해 알아 보았습니다 . C / C ++에는 GC 개념이 없기 때문에 메모리를 직접 관리하기로 결정하면 가비지 수집을 완전히 피할 수 있지만 메모리 문제를 훨씬 쉽게 생성 할 수 있습니다.
기본 확장 프로그램을 작성하려면 다음 항목 중 하나 이상에 대한 지식이 필요합니다.
모두 훌륭한 문서를 가지고 있습니다. 이 분야에 관심이 있으시다면 꼭 읽어 보시기 바랍니다.
더 이상 고민하지 않고 시작하자.
선결 요건
Linux :
맥:
- Xcode가 설치되어 있어야합니다 : 설치 만하지 말고 적어도 한 번 시작하고 이용 약관에 동의하십시오. 그렇지 않으면 작동하지 않습니다!
Windows
cmd.exe
관리자 권한 으로 실행 하고 입력npm install --global --production windows-build-tools
- 모든 것을 설치할 것입니다.
또는
- Visual Studio 설치 (모든 C / C ++ 빌드 도구가 미리 구성되어 있음)
또는
- 최신 Windows 빌드에서 제공하는 Linux 서브 시스템을 사용하십시오. 그것으로 위의 리눅스 명령을 따르십시오.
우리 고유의 Node.js 확장 생성하기
네이티브 확장을위한 첫 번째 파일을 만듭니다. 우리는 .cc
클래스가 C 인 확장을 사용 하거나 .cpp
C ++의 기본 확장 인 확장을 사용할 수 있습니다 . 구글 스타일 가이드는 권장 .cc
, 그래서 나는 그것으로 스틱거야.
먼저, 전체 파일을 보자. 그 후에, 나는 그것을 줄 단위로 설명 할 것이다!
#include <node.h>
const int maxValue = 10;
int numberOfCalls = 0;
void WhoAmI(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
auto message = v8::String::NewFromUtf8(isolate, "I'm a Node Hero!");
args.GetReturnValue().Set(message);
}
void Increment(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
if (!args[0]->IsNumber()) {
isolate->ThrowException(v8::Exception::TypeError(
v8::String::NewFromUtf8(isolate, "Argument must be a number")));
return;
}
double argsValue = args[0]->NumberValue();
if (numberOfCalls + argsValue > maxValue) {
isolate->ThrowException(v8::Exception::Error(
v8::String::NewFromUtf8(isolate, "Counter went through the roof!")));
return;
}
numberOfCalls += argsValue;
auto currentNumberOfCalls =
v8::Number::New(isolate, static_cast<double>(numberOfCalls));
args.GetReturnValue().Set(currentNumberOfCalls);
}
void Initialize(v8::Local<v8::Object> exports) {
NODE_SET_METHOD(exports, "whoami", WhoAmI);
NODE_SET_METHOD(exports, "increment", Increment);
}
NODE_MODULE(module_name, Initialize)
이제 파일을 한 줄씩 살펴 보겠습니다.
#include <node.h>
C ++의 include는 require()
JavaScript 와 같습니다 . 그것은 주어진 파일에서 모든 것을 끌어 올 것이지만 C ++에서는 헤더 파일의 개념을 가지고 있습니다.
구현없이 헤더 파일에 정확한 인터페이스를 선언 할 수 있으며 헤더 파일을 사용하여 구현을 포함 할 수 있습니다. C ++ 링커는이 두 함수를 함께 연결합니다. 코드에서 다시 사용할 수있는 내용을 설명하는 문서 파일로 생각하십시오.
void WhoAmI(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
auto message = v8::String::NewFromUtf8(isolate, "I'm a Node Hero!");
args.GetReturnValue().Set(message);
}
이것이 네이티브 확장이기 때문에 v8 네임 스페이스를 사용할 수 있습니다. v8::
v8의 인터페이스에 액세스하는 데 사용되는 표기법에 유의하십시오 . v8::
v8의 제공된 유형을 사용하기 전에 포함시키지 않으려면 using v8;
파일의 맨 위에 추가 할 수 있습니다 . 그런 다음 v8::
유형에서 모든 네임 스페이스 지정자를 생략 할 수 있지만 코드에 이름 충돌이 발생할 수 있으므로 사용시주의하십시오. 100 % 명확히하기 위해 v8::
코드에서 모든 v8 유형에 대해 표기법 을 사용할 것 입니다.
이 예제 코드에서는 args
호출 관련 정보를 모두 제공 하는 객체를 통해 함수가 호출 된 인수 (JavaScript에서)에 액세스 할 수 있습니다 .
로 v8::Isolate*
우리는 우리의 기능에 대한 현재 자바 스크립트 범위에 대한 액세스 권한을 확보하고 있습니다. 스코프는 자바 스크립트처럼 작동합니다. 변수를 할당하고 특정 코드의 수명이 다할 때까지 연결할 수 있습니다. 우리는 자바 스크립트 에서처럼 메모리 할당량을 할당하기 때문에 이러한 메모리 덩어리 할당을 해제 할 필요가 없습니다. 가비지 수집기가 자동으로 처리합니다.
function () {
var a = 1;
} // SCOPE
비아를 통해 args.GetReturnValue()
함수의 반환 값에 액세스 할 수 있습니다. 우리는 v8::
네임 스페이스 에서 원하는만큼 원하는대로 설정할 수 있습니다 .
C ++에는 정수 및 문자열을 저장하는 기본 제공 유형이 있지만 JavaScript는 자체 v8::
유형 객체 만 인식 합니다. 우리가 C ++ 세계의 범위에있는 한 C ++에 내장 된 것들을 자유롭게 사용할 수 있지만 JavaScript 객체와 JavaScript 코드와의 상호 운용성을 처리 할 때는 C ++ 유형을 이해할 수있는 유형으로 변환해야합니다 자바 스크립트 컨텍스트에 의해. 다음은 V8에 노출 :: 네임 스페이스를 같은 종류 v8::String
나 v8::Object
.
void WhoAmI(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
auto message = v8::String::NewFromUtf8(isolate, "I'm a Node Hero!");
args.GetReturnValue().Set(message);
}
우리의 파일에서 두 번째 메소드를 보자. 상위 인자가 10이 될 때까지 제공된 인자로 카운터를 증가시킨다.
이 함수는 JavaScript에서 매개 변수를 받아들입니다. 자바 스크립트에서 매개 변수를 받아들이면 느슨하게 입력 된 객체이기 때문에 조심해야합니다. (자바 스크립트에서 이미 익숙하다.)
arguments 배열에는 v8::Object
s 가 들어 있으므로 모든 JavaScript 객체이므로주의해야합니다.이 컨텍스트에서는 포함 할 수있는 내용을 결코 확인할 수 없기 때문입니다. 이러한 객체의 유형을 명시 적으로 확인해야합니다. 다행히도, 타입 캐스팅 (typecasting) 전에 유형을 결정하기 위해이 클래스에 헬퍼 메소드가 추가되었습니다.
기존 JavaScript 코드와의 호환성을 유지하려면 인수 유형이 잘못되면 약간의 오류가 발생합니다. 형식 오류를 발생 시키려면 v8::Exception::TypeError()
생성자 를 사용하여 Error 개체를 만들어야합니다 . TypeError
첫 번째 인수가 숫자가 아닌 경우 다음 블록은 a를 throw합니다 .
if (!args[0]->IsNumber()) {
isolate->ThrowException(v8::Exception::TypeError(
v8::String::NewFromUtf8(isolate, "Argument must be a number")));
return;
}
자바 스크립트에서 스 니펫은 다음과 같습니다.
If (typeof arguments[0] !== ‘number’) {
throw new TypeError(‘Argument must be a number’)
}
카운터가 범위를 벗어나면 처리해야합니다. 자바 스크립트에서와 마찬가지로 맞춤 예외를 만들 수 있습니다 new Error(error message’)
. C에서 v8 API를 사용하면 다음과 같이 보일 것입니다 : v8::Exception:Error(v8::String::NewFromUtf8(isolate, "Counter went through the roof!")));
여기서 격리는 현재를 통해 참조를 얻어야하는 현재 범위입니다 v8::Isolate* isolate = args.GetIsolate();
.
double argsValue = args[0]->NumberValue();
if (numberOfCalls + argsValue > maxValue) {
isolate->ThrowException(v8::Exception::Error(
v8::String::NewFromUtf8(isolate, "Counter went through the roof!")));
return;
}
우리가 잘못 처리 할 수있는 모든 것을 처리 한 후에는 C ++ 범위에서 사용할 수있는 카운터 변수에 인수를 추가합니다. 마치 자바 스크립트 코드처럼 보입니다. 새 값을 JavaScript 코드로 반환하려면 먼저 integer
C ++ v8::Number
에서 JavaScript로 액세스 할 수 있도록 변환 해야 합니다. 먼저 우리 정수를 double으로 캐스팅 static_cast<double>()
해야하며 그 결과를 v8::Number
생성자에 전달할 수 있습니다 .
auto currentNumberOfCalls =
v8::Number::New(isolate, static_cast<double>(numberOfCalls));
NODE_SET_METHOD
내보내기 개체에 메서드를 할당하는 데 사용 하는 매크로 입니다. 이것은 JavaScript에서 사용 된 것과 매우 유사한 내보내기 객체입니다. 그것은 다음과 같습니다.
exports.whoami = WhoAmI
사실, 모든 Node.js addons는 다음 패턴에 따라 초기화 함수를 내 보내야합니다.
void Initialize(v8::Local<v8::Object> exports);
NODE_MODULE(module_name, Initialize)
모든 C ++ 모듈은 자신을 노드 모듈 시스템에 등록해야합니다. 이 줄이 없으면 JavaScript에서 모듈에 액세스 할 수 없습니다. 실수로 모듈을 등록하는 것을 잊어 버린 경우에도 여전히 컴파일되지만 JavaScript에서 모듈에 액세스하려고하면 다음 예외가 발생합니다.
module.js:597
return process.dlopen(module, path._makeLong(filename));
^
Error: Module did not self-register.
지금부터이 오류가 표시되면 무엇을 해야할지 알게 될 것입니다.
우리 고유의 Node.js 모듈 컴파일하기
이제 C ++ Node.js 모듈의 뼈대가 준비 되었으니 컴파일 해 봅시다! 우리가 사용해야하는 컴파일러가 호출 되며 기본적으로 node-gyp
제공됩니다 npm
. 우리가해야 할 일은 binding.gyp
다음과 같은 파일을 추가하는 것입니다 :
{
"targets": [
{
"target_name": "addon",
"sources": [ "example.cc" ]
}
]
}
npm install
나머지는 돌봐 줄 것입니다. 또한 node-gyp
시스템에 전역 적으로 설치하여 사용할 수도 있습니다 npm install node-gyp -g
.
이제 C ++ 파트를 준비 했으니 남은 것은 Node.js 코드 내에서 작동하도록하는 것입니다. 이러한 addons를 호출하는 것은 node-gyp
컴파일러 덕분에 매끄럽게 이루어 집니다. 그냥 require
떨어져있어.
const myAddon = require('./build/Release/addon')
console.log(myAddon.whoami())
이 접근 방식은 효과적이지만, 매번 경로를 지정하는 것이 약간 지루할 수 있으며, 상대 경로가 작동하기가 어렵다는 것을 모두 알고 있습니다. 이 문제를 해결하는 데 도움이되는 모듈이 있습니다.
이 bindings
모듈은 require
우리 를 위해 더 적은 작업을 할 수 있도록 제작되었습니다 . 먼저 bindings
모듈을 설치 npm install bindings --save
한 다음 코드 바로 앞에 작은 조정을하십시오. 우리는 require
바인딩 모듈을 사용할 수 .node
있으며 binding.gyp
파일에 지정된 모든 기본 확장을 노출 합니다 target_name
.
const myAddon = require('bindings')('addon')
console.log(myAddon.whoami())
바인딩을 사용하는이 두 가지 방법은 동등합니다.
이것은 Node.js에 대한 네이티브 바인딩을 만들고이를 JavaScript 코드에 연결하는 방법입니다. 그러나 작은 문제가 있습니다. Node.js는 끊임없이 진화하고 있으며, 인터페이스가 많이 파괴되는 경향이 있습니다! 이것은 특정 버전을 대상으로하는 것이 좋은 아이디어가 아님을 의미합니다. 왜냐하면 당신의 애드온이 오래 가지 않을 것이기 때문입니다.
Node.js (NaN)에 대해서는 Native Abstractions를 사용하십시오.
NaN이 라이브러리는 독립적 인 개인에 의해 작성된 타사 모듈로 시작하지만 후반 2015에서는 Node.js를 재단의 배양 된 프로젝트가되었다.
NaN은 Node.js API 위에 추상화 레이어를 제공하고 모든 버전 위에 공통 인터페이스를 생성합니다. 네이티브 Node.js 인터페이스 대신 NaN을 사용하는 것이 가장 좋은 방법으로 간주되므로 언제나 앞서 갈 수 있습니다.
NaN을 사용하려면 응용 프로그램의 일부를 다시 작성해야하지만 먼저 응용 프로그램을 설치하십시오 npm install nan --save
. 먼저 우리의 target 필드에 다음 행을 추가해야합니다 bindings.gyp
. 이렇게하면 우리 프로그램에 NaN 헤더 파일을 포함시켜 NaN 함수를 사용할 수 있습니다.
{
"targets": [
{
"include_dirs" : [
"<!(node -e \"require('nan')\")"
],
"target_name": "addon",
"sources": [ "example.cc" ]
}
]
}
우리는 예제 애플리케이션에서 v8의 일부 유형을 NaN의 추상화로 대체 할 수 있습니다. 호출 인수에 대한 도우미 메소드를 제공하고 v8 유형으로 작업하는 것이 훨씬 더 나은 경험을 제공합니다.
가장 먼저 알게 될 것은 JavaScript의 범위를 명시 적으로 액세스 할 필요가 없으며 v8::Isolate* isolate = args.GetIsolate();
NaN이 자동으로이를 처리한다는 것입니다. 그것의 타입은 바인딩을 현재 스코프로 숨길 것이므로, 우리는 그것을 사용하는 것을 귀찮게하지 않아도됩니다.
#include <nan.h>
const int maxValue = 10;
int numberOfCalls = 0;
void WhoAmI(const Nan::FunctionCallbackInfo<v8::Value>& args) {
auto message = Nan::New<v8::String>("I'm a Node Hero!").ToLocalChecked();
args.GetReturnValue().Set(message);
}
void Increment(const Nan::FunctionCallbackInfo<v8::Value>& args) {
if (!args[0]->IsNumber()) {
Nan::ThrowError("Argument must be a number");
return;
}
double argsValue = args[0]->NumberValue();
if (numberOfCalls + argsValue > maxValue) {
Nan::ThrowError("Counter went through the roof!");
return;
}
numberOfCalls += argsValue;
auto currentNumberOfCalls =
Nan::New<v8::Number>(numberOfCalls);
args.GetReturnValue().Set(currentNumberOfCalls);
}
void Initialize(v8::Local<v8::Object> exports) {
exports->Set(Nan::New("whoami").ToLocalChecked(),
Nan::New<v8::FunctionTemplate>(WhoAmI)->GetFunction());
exports->Set(Nan::New("increment").ToLocalChecked(),
Nan::New<v8::FunctionTemplate>(Increment)->GetFunction());
}
NODE_MODULE(addon, Initialize)
이제 우리는 Node.js 네이티브 확장이 어떻게 보일지에 대해 일하는 동시에 관용적 인 예를 보았습니다.
먼저 코드를 구조화하고 컴파일 프로세스에 대해 배웠고 모든 작은 부분을 이해하기 위해 줄 단위로 코드 자체를 살펴 보았습니다. 마지막으로 v8 API에 대한 NaN의 추상화를 살펴 보았습니다.
우리가 만들 수있는 작은 조정이 한 가지 더 있습니다. NaN의 제공된 매크로를 사용하는 것입니다.
매크로는 컴파일러가 코드를 컴파일 할 때 확장 할 코드 스 니펫입니다. 우리는 이미이 매크로 중 하나를 사용 NODE_MODULE
했지만, NaN에는 우리가 포함시킬 수있는 몇 가지 다른 것들이 있습니다. 이 매크로는 네이티브 확장을 만들 때 시간을 절약 해줍니다.
#include <nan.h>
const int maxValue = 10;
int numberOfCalls = 0;
NAN_METHOD(WhoAmI) {
auto message = Nan::New<v8::String>("I'm a Node Hero!").ToLocalChecked();
info.GetReturnValue().Set(message);
}
NAN_METHOD(Increment) {
if (!info[0]->IsNumber()) {
Nan::ThrowError("Argument must be a number");
return;
}
double infoValue = info[0]->NumberValue();
if (numberOfCalls + infoValue > maxValue) {
Nan::ThrowError("Counter went through the roof!");
return;
}
numberOfCalls += infoValue;
auto currentNumberOfCalls =
Nan::New<v8::Number>(numberOfCalls);
info.GetReturnValue().Set(currentNumberOfCalls);
}
NAN_MODULE_INIT(Initialize) {
NAN_EXPORT(target, WhoAmI);
NAN_EXPORT(target, Increment);
}
NODE_MODULE(addon, Initialize)
첫 번째 NAN_METHOD
방법은 긴 메서드 서명을 입력하는 부담을 덜어 주며 컴파일러가이 매크로를 확장 할 때 포함합니다. 매크로를 사용하는 경우 매크로 자체에서 제공하는 이름을 사용해야합니다. 이제는 args
arguments 객체 대신에 호출 될 info
것이므로 모든 것을 변경해야합니다.
우리가 사용한 다음 매크로는 NAN_MODULE_INIT
초기화 함수를 제공하는 것입니다. 그리고 내보내기 대신에 인수를 명명 했으므로이 매크로도 target
변경해야합니다.
마지막 매크로는 NAN_EXPORT
모듈 인터페이스를 설정합니다. 이 매크로에서 객체 키를 지정할 수 없다는 것을 알 수 있습니다. 객체 이름은 각각의 이름으로 지정됩니다.
현대 자바 스크립트에서는 이렇게 보일 것입니다.
module.exports = {
Increment,
WhoAmI
}
이전 예제에서 이것을 사용하려면 다음과 같이 함수 이름을 대문자로 변경하십시오.
'use strict'
const addon = require('./build/Release/addon.node')
console.log(`native addon whoami: ${addon.WhoAmI()}`)
for (let i = 0; i < 6; i++) {
console.log(`native addon increment: ${addon.Increment(i)}`)
}
'개발 > Node.JS' 카테고리의 다른 글
JavaScript 가비지 콜렉션 개선 (0) | 2018.03.11 |
---|---|
Node.js에서 메모리 누수 찾기 (0) | 2018.03.11 |
Node.js Internals Deep Dive - Node.js 가비지 컬렉션 설명 (0) | 2018.03.11 |
npm - CommonJS & require가 작동하는 방식 (0) | 2018.03.11 |
npm - npm Publishing Tutorial (0) | 2018.03.11 |