프로그래밍/Node.js

Node.js - node.js에서 c/c++ 라이브러리 이용하기 (펌자료)

가카리 2015. 1. 17. 19:52
반응형

출처 : http://jacking.tistory.com/1017


윤인성님이 지은 '모던 웹을 위한 Node.js 프로그래밍'책을 보다가

부록C에 있는 바이너리 모듈 부분을 보다가 '이번 장에서 다루는 내용은 윈도에서 작업이 불가능합니다'라는 글이 있었다.

왜 윈도우는 안될까?라는 생각을 하면서 뭔가 방법이 있을 것 같아서 찾아봤다.
이 글 아래에 있는 아르헨티나에 있는 Javier Santo Domingo님이 잘 정리한 내용을 참고해서 다시 정리합니다.


Add-on이란?

Add-on은 동적으로 링크되는 공유 오브젝트라고 보시면 됩니다. 유닉스 계열에서는 .so라이브러리라고 보시면 될 것 같고, 윈도우에서는 .dll 라이브러리라고 생각하시면 되겠습니다.

자세한 것은 송형주 님이 번역하신 Node.js 도움말 번역 – C/C++ Add-on 작성 관련 이나 Rhio.Kim님의 C++ Addon with Node.js를 보시면 좋을 것 같습니다. node.js extensionswith C++ and V8 (Konstantin Käfer)

또한 구글의 V8에 대해 알고 있으면 도움이 됩니다.

또한, node.js Manual &Documentation의 Addons를 참고하시면 됩니다.

윈도우는 DLL지옥이라는 말이 있을 정도로 운영체제가 철저하게 DLL을 기반으로 동작합니다.

일례로 프로세스를 생성할 때도 kernel32.dll안에 있는 함수를 이용해야 합니다.

node.js 확장코드 역시 dll을 기반으로 확장할 수 있습니다.(장점인지 단점인지...)

물론 C코드와 DLL이 어떻게 동작하는지 안다고 가정하고 씁니다. 모를 경우에는 제프리리처의 Windows C/C++를 보시길 권장합니다.

방법

node.js 동작하기 위한 환경은 마련되어 있다고 가정합니다. (nodejs.org애서 Download에서 .msi 패키지를 받아서 설치하면 끝)

윈도우 개발환경도 구축되어 있다고 가정합니다. 상용 VS가 없다면 express 버전을 무료로 사용하시면 됩니다.(물론 윈도우 SDK는 추가로 설치하셔야 겠지요)

node.js 소스코드를 다운받습니다.

소스코드를 압축해제 도구로 풉니다. 알집이던 7zip이던 대부분 압축 유틸 도구로 푸실 수 있습니다.
명령어 도구를 선호하신다면 tar로 푸셔도 됩니다. 전
7zip을 이용했습니다(무료라서)

gz안에는 tar가 있고 tar에야 드디어 원하는 node-v0.6.17 형태의 디렉토리가 있습니다.

저는 c:\node-v0.6.17 에 아래 그림과 같이 풀었습니다.


저희가 소스를 필요로 하는 것은 node.exe를 만들기 위함이 아니라 addon 코드를 빌드시 필요한 헤더파일등이 필요하기 때문입니다. c코드(.cc)는 사실 필요없습니다. 구현코드는 정적라이브러리인 node.lib에서 가져올 것이니깐요.
이후에 다시 언급하겠지만 저희가 필요한 것은 아래와 같습니다
C:\node-v0.6.17\deps\v8\include // v8.h 가 있습니다.
C:\node-v0.6.17\src // node.h 가 있습니다.
C:\node-v0.6.17\deps\uv\include // uv.h 가 있습니다.

node.lib 파일 구하기

사실 소스코드를 빌드하면 만들 수 있지만, 빌드를 위해서는 파이선 2.x를 설치해야 하더군요.
물론 저는 파이썬을 이미 사용하고 있지만 별도로 뭘 설치하는 것이 번거롭잖아요.

아까 다운로드 받은 소스에 보면 'Other release files'라는 링크가 있습니다.


클릭을 해보시면 http://nodejs.org/dist/v0.6.17/ 로 연결이 됩니다. 목록중에 node.lib 파일을 다운받습니다.
64비트 환경이라면 x64/ 하위에서 64비트로 컴파일된 파일들을 받을 수 있습니다.
여담이지만, 64비트용 node 설치패키지(node-v0.6.17.msi)도 여기서 찾을 수 있습니다.

addon을 위한 프로젝트생성

파일 >새로 만들기 >프로젝트 를 선택하여 "새 프로젝트"에서

Visual C++ >Win32 >Win32 프로젝트 선택후 이름하고 위치를 입력해줍니다. 저는 편의상 아래와 같이 입력했습니다.
이름: hellonodejs (마음대로..)
위치: C:\node-addon

Win32 응용 프로그램 마법사 에서는 그냥 "다음" 버튼(마침을 누르지 마셈)

다음 나오는 창에서 아래를 선택하고 "마침"을 누릅니다.

응용 프로그램 종류: DLL(D)
추가 옵션: 빈 프로젝트 체크

소스코드 추가 (hellonodejs.cpp)


#include<node.h>

#include<v8.h>

usingnamespace v8;

#pragmacomment(lib, "node.lib")

extern"C"void NODE_EXTERN init (Handle<Object> target)

{

 HandleScope scope;

 target->Set(String::New("hello"), String::New("world"));

}


라이브러리 복사 및 참고 지시어 추가

hellonodejs.cpp 와 동일한 디렉토리에 node.lib 파일을 복사해줍니다. (이동하던...)


include 디렉토리 설정 추가

솔루션 탐색기에서 hellonodejs 오른쪽 클릭 [속성]에서

구성(C): 모든 구성 을 선택 후(Debug 뿐 아니라 Release도 바꿔야 하는데, 각각 바꾸려면 귀찮찮아요..)


[구성 속성]-[C/C++]-[일반]의 추가 포함 디렉터리에 아래를 입력해줍니다.
C:\node-v0.6.17\deps\v8\include;C:\node-v0.6.17\src;C:\node-v0.6.17\deps\uv\include

빌드를 하다보면 stdint.h 파일이 없다고 투덜투덜대는데,
Visual C++ 10.0(2010) 미만의 버전에서는 이 파일이 없을 겁니다. (C99에서 새롭게 도입되었기 때문에...ㅡㅜ)

이럴 경우 http://msinttypes.googlecode.com/svn/trunk/stdint.h 를 사용하시거나
귀찮으시면 그냥 C:\node-v0.6.17\deps\v8\include\v8stdint.h 파일을 복사해서 stdint.h 이름으로 만듭니다.


빌드

빌드를 하면 경고가 하나 뜨는데 어쨌든 .dll 파일은 만들어집니다.

c:\node-v0.6.17\src\node_object_wrap.h(57) : warning C4251: 'node::ObjectWrap::handle_' : class 'v8::Persistent<T>'에서는 class 'node::ObjectWrap'의 클라이언트에서 DLL 인터페이스를 사용하도록 지정해야 합니다.

(class 'v8::Persistent<T>' needs to have dll-interface to be used by clients of class 'node::ObjectWrap')

1> with
1> [
1> T=v8::Object
1> ]


node_object_wrap.h에서 정의한 handle_이라는 애가 문제인데요

 v8::Persistent<v8::Object> handle_; // ro

node네임스페이스의 ObjectWrap 라는 클래스안의 public 맴버 handle_가 DLL 인터페이스를 사용하도록 지정이 안되었나보다.


실행하기

생성된 .dll을 .node로 바꾸어야 한다.
> copy hellonodejs.dll hellonodejs.node

node 명령 인터프리터(REPL; Read Eval Print Loop)로 들어가서
> node

require("./hellonodejs").hello 를 수행
> var hello = require("./hellonodejs")
> hello.hello


녹색의 'world'라는 output이 나왔다!

대조군...

혹시나 사기치는 거 아냐? 라고 의심할 수 있으니.. hellonodejs.node 가 없을 때 벌어지는 일을 수행해 보았다.


hellonodejs.node를 지우고
> del hellonodejs.node

#10에서 수행한 것을 동일하게 수행


모듈을 찾을 수 없다고 에러가 난다.

결론: hellonodejs.node 가 있기에 'world'라는 글씨가 찍힌 것이다!


소스코드를좀바꾸어보자


첫 번째 코드는 단순히 모듈의 hello라는 세터에 대해 "world"라는 문자열을 반환하게 했다.

이번에는 hello라는 함수로 바꾸어 봅시다.

hellonodejs.cpp (이름을 helloaddon 등으로 했어야 하는데.. 바꾸자니 또 뭐하고..)


#include<node.h>

usingnamespace v8;

#pragmacomment(lib, "node.lib")

Handle<Value> Method(const Arguments& args) {

 HandleScope scope;

 return scope.Close(String::New("world"));

}

void init(Handle<Object> target) {

 NODE_SET_METHOD(target, "hello", Method);

}

NODE_MODULE(hellonodejs, init)

이번에 실행을 해보면 .hello라고 치면 문자열이 나오는 것이 아니라 함수라고 [Function]이라는 반환이 나온다.

함수는 괄호로 이용해서 실행을 해야하지..



윈도우에서 AddOn을쓴다는것은?


자바스크립트의 한계를 넘어서 윈도우 API를 사용할 수 있다는 것입니다.

샘플로 현재 시간을 출력하는 함수를 만들어보겠습니다. 물론 자바스크립트에는 Date라는 객체가 있어서 날짜를 가져올 수 있습니다만,

윈도우 API중에 GetLocalTime이라는 함수를 모듈화 해보겠습니다.

hellonodejs.cpp (아래 굵은 부분이 추가)

윈도우 API 함수의 선언이 포함되어 있는 windows.h를 우선 include하고, Now라는 핸들러 함수를 만들었습니다.


#include<node.h>

usingnamespace v8;

#pragmacomment(lib, "node.lib")

#include<Windows.h>

Handle<Value> Method(const Arguments& args) {

 HandleScope scope;

 return scope.Close(String::New("world"));

}

Handle<Value> Now(const Arguments& args) {

 constint SizeBuf = 25;

HandleScope scope;

SYSTEMTIME st;

GetLocalTime(&st);

char szTime[SizeBuf];
sprintf_s(szTime, SizeBuf,
"%d-%02d-%02d %02d:%02d:%02d.%03d",
st.wYear, st.wMonth, st.wDay,
st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);


return
scope.Close(String::New(szTime));

}

void init(Handle<Object> target) {
NODE_SET_METHOD(target,
"hello", Method);
NODE_SET_METHOD(target,
"now", Now);

}


NODE_MODULE(hellonodejs, init)

실행해보면,,,

현재 시간을 가지고 있는 문자열을 찍어줍니다. JSON 형태로 반환하게 하는 것이 좋을지도 모르겠습니다.



이상으로 제가 쓰는 글은 마치고, 윈도우에서 C/C++ 애드온을 방법을 알려준 하비에르씨의 글을 아래 적습니다.


Building a Node.js Windows C/C++ Addon


BNWCA.pdf

(번역 by 나모)

소개

윈도우 운영체제에서 node.js를 위한 애드온(addon)작업을 방금 마쳤습니다. 물론 단순한 "hello world" node.js 애드온이었지만, 4시간에 걸쳐서 끝냈다. 다른 누군가도 저와 동일한 작업을 수행하기 원할 것이고, 맨땅에 헤딩을 하지 않기 위해서 간단하게 수행법을 기록하였습니다.

방법

Node.JS 소스코드를 다운로드

node.lig 파일을 얻기위해 빌드

Visual C++에서 DLL 프로젝트를 생성

File >New Project 선택 (참고로 원저자는 myaddon이라는 이름을 선택)

Visual C++ >Win32 선택

Win32 프로젝트 선택후

Win32 애플리케이션 마법사 >애플리케이션 설정 : 타입을 DLL

당신의 addon 코드를 작성

코드를 추가. 원저자는 아래의 코드 추가


extern "C" void NODE_EXTERN init (Handle<Object> target)
{
HandleScope scope;
target->Set(String::New("hello"), String::New("world"));
}

당신의 코드에 node.lib에 대한 참조를 추가. 전처리구문을 추가

#pragma comment(lib, "c:\\node.js\\src\\Debug\\node")

include가 필요한 디렉토리들을 컴파일러가 알 수 있도록 설정

"c:\node.js\src\deps\uv\include\;

c:\node.js\src\deps\v8\include\;

C:\node.js\src\src\"


addon을 빌드

당신의 addon을 컴파일해서 .dll 형태의 파일로 얻기위해 빌드 (F7을 누르기)

이름을 .node로 바꾸기(바꾸는 것이 귀찮다면 프로젝트 설정에서 프로퍼티(Properties)|일반(General)에서 .node로 설정)

.node 파일을 node.exe 디렉토리와 동일한 곳에 복사한다.

addon을 실행한다

C:\node.js>node

> require("./myaddon").hello

'world'


아래는애드온을찾다가기록


tistory : C++ Addon with Node.js [PDF]

=> Writing Native Extension for Node

=> waf (The meta build system) : http://code.google.com/p/waf/

slideshare : Writing native bindings to node.js in C++

Olivier Lalonde's blog : How to write your own native Node.js extension

> git clone git://github.com/olalonde/node-notify.git

> npm install notify


node.js 비동기 addon작성


윈도우의 콜백을 node.js에서 사용하기 위해서 addon으로 구현하고 있었다.

문제는 아래와 같이 작성하면, 아에 블록이 되어 버린다는 것이다.


Handle<Value> CallbackHandler(const Arguments& args) {

 HandleScope scope;

if(!args[0]->IsFunction()) {

return scope.Close(Undefined());

 }

 Local<Function> cb = Local<Function>::Cast(args[0]);

constunsigned argc = 1;

 Local<Value> argv[1] = { Local<Value>::New(String::New("hello world")) };

Sleep(10000); //뭔가오래걸리는작업

 cb->Call(Context::GetCurrent()->Global(), argc, argv);

return scope.Close(Undefined());

}


여러가지 방법을 찾아보았는데 비동기 함수 콜백을 위해서는 libuv의 uv_queue_work를 이용하면 가능했다.

바톤이라는 구조체를 이용해서 오래걸리는 작업(블록이 되는)을 시작, 끝 함수 포인터를 지정해서 큐에 넣는 식이었다.

아래 소스코드에서 AsyncWork, AsyncAfter 가 오래걸리는 작업, 작업 후에 정리하는 함수의 이름이다.

또한 Baton 이라는 구조체가 중간에 매개가 되는 변수이다.

Handle<Value> Async(const Arguments& args) {

 HandleScope scope;

if (!args[0]->IsFunction()) {

return ThrowException(Exception::TypeError(

 String::New("Callback function required")));

 }

 Local<Function> callback = Local<Function>::Cast(args[0]);

Baton* baton = new Baton();

 baton->request.data = baton;

 baton->callback = Persistent<Function>::New(callback);

 uv_queue_work(uv_default_loop(), &baton->request, AsyncWork, AsyncAfter);

return scope.Close(Undefined());

}


include하는 라이브러리중에 #include <uv.h> 가 바로 그것이다!

Writing native bindings to node.js in C++ 슬라이드의 28쪽이나

node.js extensionswith C++ and V8 슬라이드의 여기를 보면된다. (참고로 여기는 좌우 화살표로 슬라이드 조절이 된다)

반응형