GObject Introspection(2): Gjs와 SpiderMonkey

불로장생 아몬드·2025년 3월 16일

GNOME

목록 보기
2/4

GObject Introspection 시리즈
GObject Introspection (1)


지난 글에서는 GObject Introspection란 무엇인지에 대해 살펴보았습니다. 해당 글에서 GObject Introspection을 사용하기 위해서는 C와 개별 언어 간의 차이를 해소하는 언어 바인딩이 필요하다고 말씀드렸습니다. 앞으로 이어지는 글에서는 GNOME Shell 에서 사용하는 자바스크립트 바인딩은 Gjs의 코드를 통해 언어 바인딩에 대해 알아보겠습니다.

Gjs 라고 줄여 부르는 GNOME JavaScript 는 SpiderMonkey 자바스크립트 엔진과 GNOME 플랫폼 라이브러리를 기반으로 빌드된 자바스크립트의 런타임입니다. GNOME 플랫폼 라이브러리에 대해서는 이전 시리즈 에서 소개한 바 있습니다.

자바스크립트는 대표적인 인터프리터 언어 중 하나입니다. 코드를 한 줄씩 읽어가며 실행할 수 있고 컴파일을 할 필요가 없어 디버깅이 편리합니다. 현대 웹 브라우저는 동적 컨텐츠를 구성하기 위해 자바스크립트를 실행할 수 있는 기능을 내장하고 있습니다. 자바스크립트 코드를 실행하기 위해서는 선언된 변수, 함수 호출 스택 등 실행에 필요한 정보를 관리하는 기능이 필요합니다. 이러한 기능을 제공하는 것이 바로 자바스크립트 엔진입니다.

Gjs의 언어 바인딩 구현을 이해하기 위해서는 자바스크립트 엔진을 이해할 필요가 있습니다. 언어 바인딩의 핵심이 하나의 언어를 다른 언어와 호환하는 것이고, GNOME 플랫폼 라이브러리의 C 코드를 자바스크립트로 호환합니다. 그리고 여기에 실제 실행 환경을 담당하는 자바스크립트 엔진이 빠질 수 없기 때문입니다. 따라서 이번 글에서는 Gjs가 사용하는 자바스크립트 엔진인 스파이더몽키에 대해 다루어 보겠습니다.

SpiderMonkey

SpiderMonkey는 C++와 Rust로 구현된 자바스크립트 엔진입니다. 공식 사이트에서는 스파이더몽키를 "JavaScript와 WebAssembly 의 구현 라이브러리"라고 정의하고 있습니다. 라이브러리로 제작되었기 때문에, 다른 프로그램에 손쉽게 내장할 수 있다는 장점이 있습니다.
스파이더몽키가 제공하는 라이브러리의 API는 JSAPI라는 이름을 가지고 있습니다. 이 JSAPI를 통해, 우리는 프로그램 내에서 자바스크립트의 실행 환경 생성, 변수 조작, 함수 정의 등 C++와 자바스크립트 코드 간의 커뮤니케이션을 구현할 수 있습니다.
다음은 spidermonkey-embedding-example 깃헙에서 가져온 JSAPI 예제 코드입니다.

static bool HelloExample(JSContext* cx) {
  JS::RootedObject global(cx, boilerplate::CreateGlobal(cx));
  if (!global) {
    return false;
  }

  JSAutoRealm ar(cx, global);

  // The 'js' delimiter is meaningless, but it's useful for marking C++ raw
  // strings semantically.
  return ExecuteCodePrintResult(cx, R"js(
    `hello world, it is ${new Date()}`
  )js");
}
  • HelloExample : 예제 함수의 이름입니다. JsContext* 인자는 자바스크립트의 실행 컨텍스트 객체입니다.
  • JS::RootedObject global: 자바스크립트의 최상위 객체인 globalThis 를 생성합니다.
  • ExecuteCodePrintResult : 주어진 자바스크립트 코드를 실행하는 함수를 호출합니다. 이 함수는 다음과 같이 정의되어 있습니다.
static bool ExecuteCodePrintResult(JSContext* cx, const char* code) {
  JS::CompileOptions options(cx);
  options.setFileAndLine("noname", 1);

  JS::SourceText<mozilla::Utf8Unit> source;
  if (!source.init(cx, code, strlen(code), JS::SourceOwnership::Borrowed)) {
    return false;
  }

  JS::RootedValue rval(cx);
  if (!JS::Evaluate(cx, options, source, &rval)) return false;

  // There are many ways to display an arbitrary value as a result. In this
  // case, we know that the value is an ASCII string because of the expression
  // that we executed, so we can just print the string directly.
  printf("%s\n", JS_EncodeStringToASCII(cx, rval.toString()).get());
  return true;
}
  • if (!JS::Evaluate(cx, options, source, &rval)) return false; 이 함수에서는 바로 이 한 줄에만 주목하면 됩니다. 바로 주어진 옵션과 소스를 바탕으로 자바스크립트 코드를 실제로 실행하는 부분입니다. 네 가지 인자를 받고 있는 것을 알 수 있는데요, cx는 자바스크립트 실행환경으로, 이 실행환경에서 코드를 실행하라~ 는 명령을 전달합니다. source는 실행할 코드를 담고 있으며, rval 은 실행 결과를 반환받아 저장합니다. 코드 마지막 부분에서 이를 printf로 화면에 출력하는 것을 볼 수 있네요.

JSAPI 종류

이렇게 스파이더몽키의 JSAPI를 사용해 자바스크립트 코드를 실행하는 예제를 살펴보았습니다. 위 예제에서는 JS::Evaluate, JS::RootedObject등 실행과 객체 생성 관련 API를 다루어보았데요, JSAPI에는 그보다 더 다양한 API가 있습니다. 이들 API를 기능에 따라 나누어보면 다음과 같이 분류할 수 있습니다.

  • 실행 환경 생성 : 자바스크립트의 실행 환경을 생성합니다. JS_NewContext 와 같은 함수가 있습니다.

  • 함수 정의 및 호출 : 자바스크립트에서 호출할 수 있는 함수를 정의합니다.

      static JSFunctionSpec myjs_global_functions[] = {
        JS_FN("rand",   myjs_rand,   0, 0),
        JS_FN("srand",  myjs_srand,  0, 0),
        JS_FN("system", myjs_system, 1, 0),
        JS_FS_END
      };
    
      ...
      if (!JS_DefineFunctions(cx, global, myjs_global_functions))
        return false;
    • JS_FN("rand", myjs_rand, 0, 0) : myjs_rand라는 C++ 함수를 "rand"라는 자바스크립트 함수와 연결하는 코드입니다.
  • 변수 생성 : 자바스크립트에서 사용되는 객체, 또는 원시형 변수를 생성할 수 있습니다.

  • 자바스크립트 코드 실행 : JS::Evaluate 를 호출하여 자바스크립트 코드를 실행합니다.

  • 기타 환경 설정 : 자바스크립트 실행 환경의 설정(JIT 컴파일러 옵션 등)을 관리할 수 있습니다.

이번 장에서는 Gjs의 자바스크립트 엔진, 스파이더몽키와 JSAPI에 대해 알아보았습니다. 다음 글에서는 언어 바인딩이 어떻게 이루어지는지 실제 코드를 예시로 확인해보겠습니다.

읽어주셔서 감사합니다.
즐거운 하루 보내세요.

profile
안녕하세요!

0개의 댓글