@@ -10,7 +10,7 @@ date: '2025-03-16T12:37:30.218Z'
1010
1111> 이미지 출처: https://medium.com/ai-innovation/a-guide-for-javascript-developers-to-build-c-add-ons-with-node-addon-api-28c84a0c0cb1
1212
13- 최근 Rust를 공부 하면서 Rust 컴파일 파일이 Node.js Addon에 어떻게 연결되는지 궁금했다. 따라서 이번 기회에 N-API에 대해 "얕게" 알아보려고 한다.
13+ 최근 Rust를 공부하면서 Rust 컴파일 파일이 Node.js Addon에 어떻게 연결되는지 궁금했다. 따라서 이번 기회에 N-API에 대해 "얕게" 알아보려고 한다.
1414
1515이 글은 다음과 같은 내용을 다룬다.
1616
@@ -26,59 +26,96 @@ date: '2025-03-16T12:37:30.218Z'
2626
2727Node-API는 [ Node.js v8에서 Stable로 소개] ( https://nodejs.org/docs/latest-v8.x/api/n-api.html#n_api_n_api ) 되었다.
2828
29- Node-API는 Node.js 네이티브 애드온 (C/C++)을 개발하기 위한 API이다. 기본 JavaScript 런타임(예: V8)과 독립적이며 Node.js 자체의 일부로 유지 관리되고 있다.
29+ Node-API는 Node.js 네이티브 Addon (C/C++)을 개발하기 위한 API이다. 기본 JavaScript 런타임(예: V8)과 독립적이며 Node.js 자체의 일부로 유지 관리되고 있다.
3030
3131이 API는 Node.js의 모든 버전에서 [ ABI 안정적] ( https://nodejs.org/en/learn/modules/abi-stability ) 이다.
3232
3333> ABI(Application Binary Interface): 응용 프로그램이 컴파일된 이후에도 호환성을 유지하기 위한 인터페이스.
3434
35- 기본 JavaScript 엔진의 변경 사항으로부터 애드온을 보호하고 특정 버전에 대해 컴파일된 모듈을 재컴파일 없이 이후 버전의 Node.js에서 실행할 수 있도록 하는 것이 목적이다.
35+ 기본 JavaScript 엔진의 변경 사항으로부터 Addon을 보호하고 특정 버전에 대해 컴파일된 모듈을 재컴파일 없이 이후 버전의 Node.js에서 실행할 수 있도록 하는 것이 목적이다.
3636
3737## Node-API의 장점
3838
3939![ addon chart] ( addon.webp ' l ')
4040
4141Node-API가 탄생한 배경을 이해하면 Node-API의 장점을 이해할 수 있다.
4242
43- Node.js는 JavaScript를 브라우저 외부에서 실행시킬 수 있는 앱이다. 따라서 다양한 기능을 충족시키기 위해 C/C++ 네이티브 애드온을 통해 Node.js를 확장시키고자 하는 노력을 기울여 왔다.
43+ Node.js는 JavaScript를 브라우저 외부에서 실행시킬 수 있는 앱이다. 따라서 다양한 기능을 충족시키기 위해 C/C++ 네이티브 Addon을 통해 Node.js를 확장시키고자 하는 노력을 기울여 왔다.
4444
45- 이러한 노력은 유지보수의 어려움이 있었다. 내부적으로 의존하는 V8 엔진 및 Libuv 등은 지속적으로 업데이트되며 새로운 기능이 추가되거나 변경된다. 이러한 변화는 C/C++로 개발된 애드온의 ABI 안정성을 보장하기 어렵게 만들었다.
45+ 이러한 노력은 유지보수의 어려움이 있었다. 내부적으로 의존하는 V8 엔진 및 Libuv 등은 지속적으로 업데이트되며 새로운 기능이 추가되거나 변경된다. 이러한 변화는 C/C++로 개발된 Addon의 ABI 안정성을 보장하기 어렵게 만들었다.
4646
47- > 애드온 C++에서 V8에 직접 접근한 경우 Node.js는 안정성을 보장할 방법이 없기 때문.
47+ > Addon C++에서 V8에 직접 접근한 경우 Node.js는 안정성을 보장할 방법이 없기 때문.
4848
49- 또한 C/C++ 애드온을 개발하고자 하는 개발자는 인터페이스 가이드를 받지 못하기 때문에 숙련된 개발자가 아니면 애드온을 적용하기가 어려웠다.
49+ 또한 C/C++ Addon을 개발하고자 하는 개발자는 인터페이스 가이드를 받지 못하기 때문에 숙련된 개발자가 아니면 Addon을 적용하기가 어려웠다.
5050
5151이러한 문제를 해결하기 위해 Node-API가 등장하게 되었다.
5252
5353### 1. ABI 안정성
5454
55- Node.js의 업데이트로 인해 애드온이 깨지는 문제를 해결한다.
55+ Node.js의 업데이트(내부 바인딩 디펜던시 라이브러리; V8 버전업 등)로 인해 Addon이 깨지는 문제를 해결한다.
5656
57- ### 2. 개발자 친화적
57+ ### 2. 인터페이스 제공
5858
5959Node-API는 C/C++ 개발자가 아니더라도 사용할 수 있도록 설계되었다.
6060
61- 주요 인터페이스를 제공하고 이를 통해 Node.js와 상호작용할 수 있도록 한다.
61+ 주요 인터페이스를 제공하고 이를 통해 Node.js와 상호작용 할 수 있도록 한다.
62+
63+ > 인터페이스는 아래의 [ Rust와 Node-API를 사용한 Node.js Addon 개발] ( #rust와-node-api를-사용한-nodejs-addon-개발 ) 에서 간단하게 소개한다.
6264
6365### 3. 최적화
6466
65- 기존 C/C++ 애드온은 GC 메모리 관리를 직접해야 했으며 멀티 스레딩도 직접 구현해야 했다. 이는 애드온 개발자에게 추가적인 부담을 준다.
67+ C/C++ Addon을 특별한 바인딩 과정 없이 적용할 경우 직접 메모리 관리를 직접 해야 했으며 멀티 스레딩도 스스로 구현해야 했다.
68+
69+ 이는 Node.js와 V8, Libuv 등 다양한 바인딩 라이브러리들의 관계를 정확하게 알아야 하는 부담으로 다가왔다.
70+
71+ Node-API는 인터페이스를 통해 최적화해 주기 때문에 Addon 개발자에게 추가적인 부담을 주지 않는다.
6672
6773## Rust와 Node-API를 사용한 Node.js Addon 개발
6874
69- 바로 실전으로 이해해보자.
75+ 이제 Addon 개발 과정을 따라가면서 실체를 조금 더 자세히 이해해 보자.
76+
77+ 개발 과정은 다음과 같다.
7078
71- Rust로 컴파일된 목적 파일을 Node.js Addon에 연결하여 JavaScript 파일에서 사용하는 과정을 다루고자 한다.
79+ 1 . Addon 개발 환경 설정
80+ 2 . Rust 파일을 목적 파일로 컴파일
81+ 3 . 목적 파일을 Node.js Addon에 연결
82+ 4 . JavaScript 파일에서 사용
7283
7384### 1. Node-gyp 설치
7485
7586``` sh
7687$ npm i -g node-gyp
7788```
7889
79- Node-API 에서 제공하는 헤더, C 코드 등이 포함되어 있는 [ node-gyp] ( https://github.com/nodejs/node-gyp ) 를 설치한다.
90+ Node-API에서 제공하는 헤더, C 코드 등이 포함된 [ node-gyp] ( https://github.com/nodejs/node-gyp ) 를 설치한다.
8091
81- ### 2. binding.gyp 파일 생성
92+ 사전 파일이 포함된 ` node-gyp ` 로 Addon을 빌드할 예정이기 때문에 [ gcc] ( https://namu.wiki/w/GCC ) 등의 컴파일러를 쓰지 않는다는 것을 확인할 수 있다.
93+
94+ ### 2. Rust 파일을 목적 파일로 컴파일
95+
96+ ``` rs
97+ // mycalc.rs
98+ // rust_mul이라는 곱셈 함수를 export 할 예정.
99+ #[no_mangle]
100+ pub extern " C" fn rust_mul (a : isize , b : isize ) -> isize {
101+ a * b
102+ }
103+ ```
104+
105+ ``` sh
106+ # compile
107+ $ rustc --crate-type=" dylib" mycalc.rs -o libmycalc.so
108+ ```
109+
110+ Rust 코드로 작성된 ` rust_mul ` 함수를 ` libmycalc.so ` 파일로 컴파일한다.
111+
112+ 해당 함수를 Node-API를 통해 C Addon으로 가져와 JavaScript 모듈에서 사용할 수 있도록 할 예정이다.
113+
114+ ![ so file] ( so-file.webp )
115+
116+ ` .so ` 파일은 컴파일 단계에서 코드 영역에 추가되지 않고 런타임에 동적으로 로드되는 라이브러리이다.
117+
118+ ### 3. binding.gyp 파일 생성
82119
83120``` json
84121// binding.gyp
@@ -88,98 +125,79 @@ Node-API 에서 제공하는 헤더, C 코드 등이 포함되어 있는 [node-g
88125 "target_name" : " mycalc_addon" ,
89126 "sources" : [" mycalc_addon.c" ],
90127 "libraries" : [
91- " /PATH/libmycalc.so" // 동적 로딩을 사용하기 위해 추가
128+ " /PATH/libmycalc.so" // 동적 로딩을 사용하기 위해 추가(Rust 파일을 컴파일한 파일)
92129 ],
93130 }
94131 ]
95132}
96133```
97134
98- ### 3. Rust 파일 목적 파일로 컴파일
135+ ` node-gyp ` 를 사용해 빌드할 때 필요한 설정 파일인 ` binding.gyp ` 를 작성한다.
99136
100- ``` rs
101- // mycalc.rs
102- // rust_mul 이라는 곱셈 함수를 export 할 예정.
103- #[no_mangle]
104- pub extern " C" fn rust_mul (a : isize , b : isize ) -> isize {
105- a * b
106- }
107- ```
108-
109- ``` sh
110- # compile
111- $ rustc --crate-type=" dylib" mycalc.rs -o libmycalc.so
112- ```
137+ ` mycalc_addon.c ` 파일은 Node-API를 사용해 C Addon을 작성할 파일이다.
113138
114139### 4. Addon C 파일 작성
115140
116141``` c
117- // mycalc_addon.c// node_api를 가져옴(node-gyp에 있을 것이므로 바로 사용한다)
142+ // mycalc_addon.c
143+ // node_api를 가져오는 모습(node-gyp에 있을 것이므로 사용할 수 있다)
118144#include < node_api.h>
119145#include < stdio.h>
120- // Rust 파일의 함수를 C로 가져온다(so 파일에 있을 예정).
146+ // Rust 파일의 함수를 C로 가져온다(so 파일에 있을 예정)
121147int rust_mul (int a, int b);
122148// 두 숫자를 더하는 C 함수
123- napi_value Mul(napi_env env, napi_callback_info info)
124- {
149+ napi_value Mul(napi_env env, napi_callback_info info) {
125150 napi_status status ;
126- // 인자 개수와 인자 배열
151+ // 인자 개수와 인자 배열
127152 size_t argc = 2 ;
128153 napi_value argv [2 ];
129154 status = napi_get_cb_info (env , info , & argc , argv , NULL , NULL );
130- if (status != napi_ok )
131- {
155+ if (status != napi_ok ) {
132156 napi_throw_error(env , NULL , " Failed to parse arguments" );
133157 return NULL;
134158 }
135- // 인자가 2개인지 확인
136- if (argc < 2 )
137- {
159+ // 인자가 2개인지 확인
160+ if (argc < 2 ) {
138161 napi_throw_error(env , NULL , " Wrong number of arguments" );
139162 return NULL;
140163 }
141- // 인자를 double로 변환
164+ // 인자를 double로 변환. JavaScript와 C++의 다른 타입을 일치시킨다
142165 double value1 , value2 ;
143166 status = napi_get_value_double (env , argv [0 ], & value1 );
144- if (status != napi_ok )
145- {
167+ if (status != napi_ok ) {
146168 napi_throw_error(env , NULL , " Invalid argument 1" );
147169 return NULL;
148170 }
149171 status = napi_get_value_double (env , argv [1 ], & value2 );
150- if (status != napi_ok )
151- {
172+ if (status != napi_ok ) {
152173 napi_throw_error(env , NULL , " Invalid argument 2" );
153174 return NULL;
154175 }
155- // rust 모듈을 활용해 결과 계산 //////////////////////////////////////////////
176+ // rust 모듈을 활용해 결과 계산 //////////////////////////////////////////////
156177 double result = rust_mul (value1 , value2 );
157- // 결과를 napi_value로 변환
178+ // rust 모듈의 결과를 napi_value로 변환
158179 napi_value result_value ;
159180 status = napi_create_double (env , result , & result_value );
160- if (status != napi_ok )
161- {
181+ if (status != napi_ok ) {
162182 napi_throw_error(env , NULL , " Failed to create result value" );
163183 return NULL;
164184 }
185+ // 결과 반환
165186 return result_value ;
166187}
167188// 모듈 초기화 함수
168- napi_value Init(napi_env env, napi_value exports)
169- {
189+ napi_value Init(napi_env env, napi_value exports) {
170190 napi_status status ;
171- // 함수 등록
191+ // 함수 등록
172192 napi_value fn ;
173193 status = napi_create_function (env , NULL , 0 , Mul , NULL , & fn );
174- if (status != napi_ok )
175- {
194+ if (status != napi_ok ) {
176195 napi_throw_error(env , NULL , " Failed to create function" );
177196 return NULL;
178197 }
179- // exports 객체에 함수 추가
198+ // exports 객체에 함수 추가. 이로써 JavaScript에서 사용 가능해진다! ////////
180199 status = napi_set_named_property (env , exports , " mul" , fn );
181- if (status != napi_ok )
182- {
200+ if (status != napi_ok ) {
183201 napi_throw_error(env , NULL , " Failed to set named property" );
184202 return NULL;
185203 }
@@ -189,12 +207,26 @@ napi_value Init(napi_env env, napi_value exports)
189207NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
190208```
191209
210+ `napi`로 시작하는 함수들은 Node-API에서 제공하는 함수이다.
211+
212+ 위의 코드를 모두 이해할 필요는 없다. Node-API를 사용해 C Addon을 작성하는 방법을 보여주기 위한 코드이다.
213+
214+ 중요한 점은 Rust의 `rust_mul` 함수를 C로 가져와 `Mul` 함수에서 사용하고 있다는 점이다.
215+
192216### 5. Addon 빌드
193217
194218```sh
195219$ node-gyp configure && node-gyp build
196220```
197221
222+ ` node-gyp ` 를 사용해 Addon을 빌드한다.
223+
224+ ![ build addon] ( build-addon.webp ' s ')
225+
226+ ` build ` 명령어를 실행하면 ` build/Release ` 디렉터리에 ` mycalc_addon.node ` 파일이 생성된다.
227+
228+ > ` .node ` 파일은 Node.js Addon을 의미한다.
229+
198230### 6. JavaScript 파일에서 사용하기
199231
200232``` js
@@ -203,11 +235,23 @@ const addon = require("./build/Release/mycalc_addon");
203235console .log (addon .mul (3 , 5 ));// 15 출력
204236```
205237
238+ TA-DA! JavaScript 파일에서 C Addon을 사용하고 있다.
239+
240+ 이로써 Rust \< -> C \< -> Node.js 간의 연결이 완료되었다.
241+
206242## 마치며
207243
208- Node-API를 이해하는 과정에서 애드온에 대한 이해도가 높아졌다.
244+ Rust와 Node-API를 사용해 Node.js Addon을 개발하면서 Addon을 개발하는 과정을 이해할 수 있었다.
245+
246+ 글을 요약하면 다음과 같다.
247+
248+ 1 . N-API는 Node-API 를 의미한다.
249+ 2 . Node-API를 활용해 JS 레이어를 거치지 않는, 고속 라이브러리를 만들 수 있다.
250+ 3 . 바이너리 코드로 통합되므로 FFI([ 외부 함수 인터페이스] ( https://ko.wikipedia.org/wiki/%EC%99%B8%EB%B6%80_%ED%95%A8%EC%88%98_%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4 ) ; 다른 언어 호출)가 가능하다.
251+
252+ Node.js의 내부에 한층 더 깊게 파고들어 간 것 같아 뿌듯하다.
209253
210- Node.js의 내부에 한층 더 깊게 파고들어간 것 같아 뿌듯하다 .
254+ 이 글을 통해 Node.js Addon 개발에 흥미가 생겼길 바라며 글을 마무리 하겠다 .
211255
212256## 참고
213257
0 commit comments