Emscripten의 엠바인드

JS를 wasm에 바인딩합니다.

지난 wasm 도움말에서 웹에서 사용할 수 있도록 C 라이브러리를 wasm으로 컴파일하는 방법을 설명했습니다. 제게 (그리고 많은 독자에게) 눈에 띄었던 점은 사용 중인 wasm 모듈의 함수를 수동으로 선언해야 하는 방식이 투박하고 약간 어색하다는 것입니다. 다시 한번 말씀드리지만 제가 언급하는 코드 스니펫은 다음과 같습니다.

const api = {
    version: Module.cwrap('version', 'number', []),
    create_buffer: Module.cwrap('create_buffer', 'number', ['number', 'number']),
    destroy_buffer: Module.cwrap('destroy_buffer', '', ['number']),
};

여기서는 EMSCRIPTEN_KEEPALIVE로 표시된 함수의 이름, 반환 유형, 인수 유형을 선언합니다. 그런 다음 api 객체의 메서드를 사용하여 이러한 함수를 호출할 수 있습니다. 하지만 이런 방식으로 wasm을 사용하면 문자열이 지원되지 않으며 메모리 청크를 수동으로 이동해야 하므로 많은 라이브러리 API를 사용하기가 매우 번거롭습니다. 더 나은 방법이 없을까요? 물론 있습니다. 그렇지 않으면 이 도움말이 무슨 내용이겠어요?

C++ 이름 변환

개발자 환경은 이러한 바인딩을 지원하는 도구를 빌드하기에 충분한 이유가 되지만 실제로 더 긴급한 이유가 있습니다. C 또는 C++ 코드를 컴파일할 때 각 파일은 별도로 컴파일됩니다. 그런 다음 링커가 이러한 모든 소위 객체 파일을 함께 뭉쳐 wasm 파일로 변환합니다. C의 경우 링커가 사용할 수 있도록 함수 이름이 객체 파일에 계속 제공됩니다. C 함수를 호출하는 데 필요한 것은 이름뿐이며, 이는 cwrap()에 문자열로 제공됩니다.

반면 C++은 함수 오버로드를 지원하므로 서명이 다른 한 (예: 유형이 다른 매개변수) 동일한 함수를 여러 번 구현할 수 있습니다. 컴파일러 수준에서 add와 같은 멋진 이름은 링커의 함수 이름에 서명을 인코딩하는 이름으로 변환됩니다. 따라서 더 이상 이름으로 함수를 조회할 수 없습니다.

embind 입력

embind는 Emscripten 도구 모음의 일부이며 C++ 코드를 주석 처리할 수 있는 다양한 C++ 매크로를 제공합니다. JavaScript에서 사용할 함수, enum, 클래스 또는 값 유형을 선언할 수 있습니다. 일반 함수로 간단하게 시작해 보겠습니다.

#include <emscripten/bind.h>

using namespace emscripten;

double add(double a, double b) {
    return a + b;
}

std::string exclaim(std::string message) {
    return message + "!";
}

EMSCRIPTEN_BINDINGS(my_module) {
    function("add", &add);
    function("exclaim", &exclaim);
}

이전 도움말과 비교하면 더 이상 emscripten.h를 포함하지 않습니다. 더 이상 EMSCRIPTEN_KEEPALIVE로 함수에 주석을 달지 않아도 되기 때문입니다. 대신 JavaScript에 함수를 노출할 이름을 나열하는 EMSCRIPTEN_BINDINGS 섹션이 있습니다.

이 파일을 컴파일하려면 이전 도움말과 동일한 설정 (또는 원하는 경우 동일한 Docker 이미지)을 사용하면 됩니다. embind를 사용하려면 --bind 플래그를 추가합니다.

$ emcc --bind -O3 add.cpp

이제 새로 만든 wasm 모듈을 로드하는 HTML 파일을 작성하기만 하면 됩니다.

<script src="/a.out.js"></script>
<script>
Module.onRuntimeInitialized = _ => {
    console.log(Module.add(1, 2.3));
    console.log(Module.exclaim("hello world"));
};
</script>