메뉴 건너뛰기


Developer > Open Source

SpiderMonkey 2장. SpiderMonkey 기본 사용법

2013.11.16 02:27

푸우 조회 수:8467

1장에서 우리는 SpiderMonkey의 개념에 해당되는 부분과 필수적인 요소들을 살펴 보았습니다.
이번 장에서는 SpiderMonkey를 사용하는 아주 기본적인 부분을 알려드리도록 하겠습니다.
 
참고로 아래의 글중 스크립트에 해당하는 영문은 script-x로 모두 바뀌어서 보여집니다.
이 점 참고하셔서 읽어 주세요.
 
 
2장. SpiderMonkey 기본 사용법
 
1. 기본 골격
 
아래의 C코드는 Spider Monkey를 이용하여 자바스크립트 코드 "x=1; y=2; x+y"를 수행하는 코드입니다.
가장 단순한 SpiderMonkey의 사용을 보여주는 것이지만 1+2 정도 하는데 무지하게 코드가 기네요.
하지만 내 프로그램에 무한한 확장성을 만들어 준다는 생각을 하시면서 참고 분석을 해 보도록 합시다.
아래의 코드는 1장에 설명했던 runtime, context, global object를 가지고 있습니다.
뭐 이게 없으면 돌아가질 않을 거니깐... 
01: #include <stdio.h>
02: #include <stdlib.h>
03: #include <string.h>
04: #include "jsapi.h"
05: 
06: #define JSVERSION_LATEST      JSVERSION_1_7
07: 
08: 
09: 
10: 
11: static JSClass global_class = {
12:         "global", JSCLASS_GLOBAL_FLAGS,
13:         JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
14:         JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
15:         JSCLASS_NO_OPTIONAL_MEMBERS
16: };
17: 
18: 
19: 
20: 
21: void reportError(JSContext *cx, const char *message, JSErrorReport *report)
22: {
23:     fprintf(stderr, "%s:%u:%s\n",
24:                 report->filename ? report->filename : "<no filename>",
25:                 (unsigned int) report->lineno,
26:                 message);
27: }
28: 
29: 
30: int main(int argc, const char *argv[])
31: {
32:         JSRuntime *rt;
33:         JSContext *cx;
34:         JSObject  *global;
35: 
36:         
37:         
38:         
39:         {
40:                 
41:                 rt = JS_NewRuntime(8L * 1024L * 1024L);
42:                 if (rt == NULL)
43:                         return 1;
44: 
45:                 
46:                 cx = JS_NewContext(rt, 8192);
47:                 if (cx == NULL)
48:                         return 1;
49:                 JS_SetOptions(cx, JSOPTION_VAROBJFIX);
50:                 JS_SetVersion(cx, JSVERSION_LATEST);
51:                 // 스크립트 수행 중 에러 발생시 호출할 Callback함수 지정
52:                 JS_SetErrorReporter(cx, reportError);
53: 
54:                 
55:                 global = JS_NewObject(cx, &global_class, NULL, NULL);
56:                 if (global == NULL)
57:                         return 1;
58: 
59:                 
60:                 if (!JS_InitStandardClasses(cx, global))
61:                         return 1;
62:         }
63: 
64:         
65:         
66:         
67:         {
68:                 char *filename="Internal script";
69:                 uintN lineno=1;
70:                 jsval rval;
71:                 JSBool ok;
72:                 char *source = "x=1; y=2; x+y";
73: 
74:                 ok = JS_Evaluatescript(cx, global, source, strlen(source),
75:                 filename, lineno, &rval);
76:                 if (ok) {
77:                         jsdouble d;
78:                         ok = JS_ValueToNumber(cx, rval, &d);
79:                         printf("Return: %f\n", d);
80:                 }
81:         }
82: 
83:         
84:         
85:         
86:         {
87:                 JS_DestroyContext(cx);
88:                 JS_DestroyRuntime(rt);
89:                 JS_ShutDown();
90:         }
91: 
92:         return 0;
93: }
 
04라인: SpiderMonkey를 사용하기 위해서는 jsapi.h 파일을 include 시켜야 합니다.
 
11라인: Global Object를 생성하기 위한 Class정의 입니다. Class와 Object가 거의 유사하면서 또 좀 다릅니다. 다음에 설명하기로 하고 여기서는 Global Object 생성하기 위해 선언을 했다는 정도로 알고 넘어갑니다.
 
40라인: JS_NewRuntime()함수를 사용해서 runtime환경을 생성하고 있습니다. JS_NewRuntime()함수의 인자로 바이트 크기를 주는데 가비지 콜렉터가 동작하기 위한 최대 메모리를 지정한 것 입니다. 여기서는 8M바이트를 지정하고 있네요. 1장에서도 말한바와 같이 하나의 Application에는 일반적은 하나의 Runtime환경만 있으면 충분합니다.
 
46라인: JS_NewContext()함수를 사용해서 context환경을 생성하고 있습니다. JS_NewContext()함수의 두번째 인자는 context를 위해 할당할 스택의 크기를 바이트로 줍니다. 여기서는 8K를 줬네요. context에서 중요한 것은 script당 혹은 thread당 하나씩 만들어 져야 한다는 것 입니다. 예로 웹브라우져에서 SpiderMonkey를 사용한다면 Web Page 당 하나씩 만들어 지는게 보통입니다.
 
49라인: JS_SetOptions()함수는 적어도 SipderMonkey 1.5에서는 없었는데 새로 생겼더군요. Context에 상세한 옵션을 켜거나 끄는하는 함수입니다. 모호한 구문에 경고를 내라든가 경고를 에러로 취급하라든가 하는 옵션을 동작하게 혹은 동작하지 않게합니다.
On, Off 방법은 아래와 같습니다.
  // Strict 모드를 켤 경우(On시킬 경우)
  JS_SetOptions(cx, JS_GetOptions(cx) | JSOPTION_STRICT);
  // Strict 모드를 끌 경우(Off시킬 경우)
  JS_SetOptions(cx, JS_GetOptions(cx) & ~JSOPTION_STRICT);
각 옵션에 대한 자세한 설명은 "https://developer.mozilla.org/en/SpiderMonkey/JSAPI_Reference/JS_SetOptions"를 참조하세요.
위에서 사용한 옵션 JSOPTION_VAROBJFIX의 의미는 JS_Evaluatescript()가 obj 파라메터의 스코프 체인상의 마지막 오브젝트(즉, 글로벌 오브젝트)를 ECMA "변수 객체"로써 사용하게 하라는 것 입니다.
예로 "x=1"과 "var x=1"이라는 스크립트가 있을 경우 JSOPTION_VAROBJFIX옵션을 주지 않은 경우에는 "x=1"은 무조건 글로벌 오브젝트 상의 프로퍼티로 만들게 되고 "var x=1"은 JS_Evaluatescript()함수의 파라메터로 지정한 object 상의 프로퍼티로 만들게 됩니다. 그런데 JSOPTION_VAROBJFIX옵션을 주게 되면 두 경우 모두 글로벌 오브젝트 상의 프로퍼티로 만들게 된다는데... 제 생각에는 구별하면 더 좋을 것 같은 느낌이지만  JSOPTION_VAROBJFIX옵션을 사용하는 것이 권장된다고 하네요. 아무튼 별 이유 없다면 그냥 지정하세요. ^^ 
 
50라인: 지정한 context가 script를 수행할 때 Javascript의 어떤 버전 환경으로 돌릴지 JS_SetVersion()를 사용하여 지정합니다.  버전별로 JSVERSION_x_x 이런식의 메크로를 지정하는데 어떤 것들이 있는지는 header파일을 참조하시기 바랍니다.
 
52라인: JS_SetErrorReporter()함수는 script에 오류가 있는 경우 이에 대한 오류 메시지를 출력하기 위한 Callback함수를 지정합니다. 여기서는 reportError()라는 Callback함수를 지정하는데 reportError함수는 21라인에 정의되어 있습니다. 에러를 발생한 파일, 라인, 에러를 발생한 지점 그리고 에러메시지를 알 수 있어서 꽤 유용합니다. 여기에 있는 reportError함수는 에러를 발생한 파일, 라인, 에러메시지만을 출력하도록 되어 있습니다.
 
55라인: 이곳에서 Global Object를 생성하고 있습니다. Context는 여러 Object를 가질 수 있습니다만 Global Object는 Context당 하나입니다. Global Object에 등록한 Class, Object, Function들은 각각의 이름앞에 특별한 Object명을 사용하지 않고 사용할 수 있다는게 다른 Object와 다릅니다. 즉, Root Object이므로 여기에 포함된 요소들은 Object명 없이 바로 사용할 수 있다는 거죠. 예로 웹브라우져에서 documet.write() 라는 구문을 사용했을 때 앞에 다른 Object이름 없이 document를 사용할 수 있는 것은 document객체는 Global Object에 등록되 Object입니다.  Object생성방법은 후에 자세히 설명할 거구요. 아주 중요합니다.
 
60라인: Javascript에서 제공하는 기본 클래스들을 Global Object에 등록합니다. ECMA표준에 Array, String, Math, Date 같은 Class들을 기본 제공하라고 되어 있고 SpiderMonkey는 내부적으로 이러한 Class들을 준비하고 있는데 60라인을 빠뜨린다면 이런 클래스들을 사용할 수 없습니다.
 
68 ~ 80라인: 이곳에서 실제 Javascript를 수행하고 있는데요. 이 곳을 뺀 나머지들은 SpiderMonkey를 사용하기 위한 일반적인 골격이라고 생각하시면 될 것 같습니다.
여기서는 어떤 파일이 아니라 그냥 문자열로 만든 자바스크립트 코드를 수행하고 그 결과 값을 리턴값을 받아서 printf()함수로 화면에 출력하고 있습니다.
JS_Evaluatescript() 함수는 스크립트 문자열을 지정된 컨텍스트와 객체내에서 수행하여 줍니다.
위에서 filename과 lineno는 실제 무슨 파일이름과 라인번호가 아니라 스크립트 문자열에 오류가 있을 경우 에러를 출력할때 알아보기 쉽게 같이 출력할 내용을 지정하는 것 입니다. 만약 한 프로그램에서 JS_Evaluatescript()함수를 여러번 사용하고 있다고 할 경우 오류가 생기면 어떤 곳에서 오류가 생겼는지 알기 힘들겠죠? 그래서 지정하는 것이니깐 맘에 드는 글자와 숫자를 지정하시면 됩니다.
제일 마지막 변수는 스크립트의 수행 결과를 얻어오는 것 인데요.
정확히 말하면 스크립트 내용 중 제일 마지막 문장이 실행되고 리턴되는 값을 담아 옵니다.
즉, 위의 경우는 "x+y"가 제일 마지막 문장이니 x와 y를 더한 값을 리턴한다는 거죠.
주의할 점은 리턴되는 값이 우리가 생각하는 숫자가 아니라 SpiderMonkey가 사용하는 jsval형의 값이 리턴되어 옵니다. 이를 바로 읽어 낼 수는 없으며 위의 경우에는 JS_ValueToNumber()라는 함수를 이용하여 C에서 사용하는 일반적인 값으로 변경하여 출력하고 있습니다.
jsval에서 대해서는 중요하므로 다음데 자세히 설명하도록 하겠습니다.
일단 대충 봐두세요.
그리고 앞으로는 실행 코드를 제시할 때 68~80라인을 제외한 나머지 부분은 골격에 해당하므로 생략하고 이 곳에 해당하는 부분만 실제 코드를 보이고 설명하도록 하겠습니다.
 
86 ~ 89라인: SpiderMonkey를 모두 사용한 경우 메모리들을 해제하는 부분입니다.
 
위의 소스를 컴파일하여 실행하면 다음과 같은 결과를 볼 수 있습니다.
 
Return: 3.000000
 
 
2. 스크립트 파일에 의한 실행
 
위에서 예제에서는 간단하게 설명하기 위해 프로그램 내부에서 스크립트 코드를 문자열로 만들어 실행하고 있습니다. 하지만 이런식의 실행 보다는 외부 파일에 스크립트 코드를 적어서 프로그램이 돌면서 실행하도록 만드는 경우가 대부분이겠지요.
외부 스크립트 파일을 실행시키기 위해서는 컴파일이라는 과정과 실행이라는 과정을 거치게 됩니다.
 
그럼 실제 코드를 보도록 하겠습니다.
위와 같은 실행결과를 얻기 위해서 우선 스크립트 파일을 다음과 같이 만듭니다.
파일이름은 아무거나 상관 없습니다.
x=1;
y=2;
x+y;
 
그리고 위의 기본 골격의 68~80 라인에 해당되는 부분을 다음과 같이 바꿉니다. 
 

070:         
071:         
072:         
073:         {
074:                 JSscript *script=NULL;
075: 
076:                 script = JS_CompileFile(cx, global, argv[1]);
077:                 if (script) {
078:                         jsval rval;
079:                         JSBool ok;
080:                         ok=JS_Executescript(cx, global, script, &rval);
081:                         if(ok==JS_TRUE) {
082:                                 jsdouble d;
083:                                 JS_ValueToNumber(cx, rval, &d);
084:                                 printf("Return: %f\n", d);
085:                         }
086:                         JS_Destroyscript(cx, script);
087:                 }
088:         }
 
76라인: JS_CompileFile()함수에서 지정한 파일을 컴파일하고 스크립트 객체를 생성합니다. 이때 argv[1]이라고 되어 있는 부분은 눈치채셨겠지만 스크립트 파일명을 경로와 함께 지정하여 주는 부분입니다.
 
80라인: 컴파일된 스크립트 객체를 JS_Executescript()함수를 사용하여 실행을 합니다.
 
86라인: JS_CompileFile()함수에서 할당한 스크립트 객체를 해제합니다. 
 
실행 결과요? 뭐 위와 똑 같습니다.
 
Return: 3.000000
 
자바스크립트 코드별로 컴파일을 미리 해 놓고 필요한 스크립트 오브젝트만을 실행할 수 있습니다.
컴파일은 꼭 외부 파일만 가능한 것은 아닙니다.
컴파일 관련 함수들은 다음과 같은 것들이 있습니다.
 - JS_CompileFile();
 - JS_CompileFunction();
 - JS_CompileFunctionForPrincipals();
 - JS_Compilescript();
 - JS_CompilescriptForPrincipals();
 - JS_CompileUCFunction();
 - JS_CompileUCFunctionForPrincipals();
 - JS_CompileUCscript();
 - JS_CompileUCscriptForPrincipals();
함수 중에 UC가 붙은 것들은 자바스크립트 자체가 유니코드로 되어 있을 경우 사용하는 함수입니다.
부연 설명하자면 첫번째 예제에서 봤던 JS_Evaluatescript()함수는 컴파일과 실행과정을 한번에 하는 함수입니다. 즉, JS_Compilescript()함수와 JS_Executescript()함수를 한번에 수행하는 것과 같습니다.
실행과 관련된 함수로는 다음과 같은 것들이 있습니다.
 - JS_Evaluatescript();
 - JS_EvaluatescriptForPrincipals();
 - JS_EvaluateUCscript();
 - JS_EvaluateUCscriptForPrincipals();
 - JS_Executescript();
역시 유니코드 자바스크립트 실행을 위해 UC가 붙은 함수가 있네요.
 
참고로 여러 컴파일된 스크립트 변수만을 가지고 있는 경우 원래 자바스크립트 어떻게 생겼었는지 알고 싶은 경우가 있을 수 있습니다.
그럴 때는 다음과 같은 함수를 사용합니다.
 - JS_DecompileFunction()
 - JS_DecompileFunctionBody()
 - JS_Decompilescript()
일반적으로 잘 사용하지 않으니 예제는 생략하겠습니다.
 
 
이번 강좌에서는 SipiderMonkey를 이용하여 자바스크립트 코드를 실행하는 전체적인 골격을 살펴보 았습니다.
이제 다음 장부터 이를 토대로 프로그램을 확장해 나가 보도록 하겠습니다. 
 
 
 
 
Creative Commons License
Creative Commons License이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
Copyright 조희창(Nicholas Jo). Some rights reserved. http://bbs.nicklib.com