주넥

개인블로그

WebAssembly C,C++ 코드와 JavaScript간에 array parameter 주고 받기...

댓글 0

프로그래밍

2020. 4. 2.


WebAssembly는 WebBrowser상에서 수행가능한 byte code를 정의한 것으로, C나 C++로 만들어진 코드를 컴파일해서 WebAssembly (*.wasm)를 얻고, 이를 컴파일 부산물로 얻어진 Glue logic (*.js)을 이용해 WebBrowser상에서 수행시키게 된다. 


이글에서는 C나 C++쪽 코드와 JavaScript 코드상에 ByteArray와 같은 대량의 data를 주고 받는 것을 설명한다.


구글링을 해보면 Heap 어쩌구 하면서 상당히 복잡한 방법이 있다.

https://becominghuman.ai/passing-and-returning-webassembly-array-parameters-a0f572c65d97


이 글에서는 비교적 단순하게 C나 C++코드쪽 메모리 공간을 JavaScript상에서 접근하는 방법을 소개한다.


아래는 C++ 에서 Test라는 Class가 uint8_t 배열 _data 를 가지고 있고, 이것을 JavaScript 쪽 코드로 전달하는 예제 코드이다.

WebAssemly는 근본적으로 C interface이므로, C++의 경우 아래 예제처럼 s_Instances std::vector 전역 변수를 이용하여 생성되는 Class의  Instance들을 관리한다.  extern "C" {} 를 통해 JavaScript로 export할 C 함수를 정의해야 한다.

#include <stdio.h>

class Test {
public:
  Test() {
        for (int i =0; i < 256; i++) {
             _data[i] = i;
        }
  }
  virtual ~Test() {}
  uint8_t* getDataAddr() {
       return &_data[0];
  }
private:
   uint8_t _data[256];
};

#include <vector>
auto s_Instances = std::vector<Test>();

extern "C" {
    int testNew(); 
    uint8_t* testGetDataAddr(int objInst);
}

int testNew()
{
    s_Instances.push_back(Test());
    return s_Instances.size() - 1;
}

uint8_t* testGetDataAddr(int objInst)
{
   return s_Instances[objInst].getDataAddr();
}



아래 명령어를 이용해서 위의 test.cpp파일의 예제 코드를 emscripten toolchain을 이용해서 컴파일 한다.

참고로 사용된 emscripten toolchain 버전은 1.39.11 이다.

~/WASM_test$ em++ test.cpp -o test.js -O3 -s WASM=1 \
         -s EXPORTED_FUNCTIONS="['_testNew','_testGetDataAddr']

이제 가져다 사용하는 index.html 파일의 javascript 쪽 코드이다.

C++  와 동일한 이름의 javascript Class를 만들고, 생성자에서 C++쪽 array의 주소를 offset으로 얻은 후,

wasmMemory.buffer에서 offset만큼, 동일한 크기로 Uint8Array를 선언해서 JavaScript쪽 byteArray 인 _data를 만들어 주면 된다.

emscripten toolchain의 버전이 올라가면서 온갖 glue logic을 가지고 있는 test.js 파일의 기능이 개선되면서 

wasmMemory를 통해 간단하게 C++ 쪽 memory접근이 가능하게 된 듯 하다.

<html>
<body>
<script src="test.js"></script>
<script>

class Test {
    constructor() {
        this.cppInst = Module._testNew();
        let offsetModule._testGetDataAddr(this.cppInst);
        // create a view on the memory that points to this array
        this._data = new Uint8Array(wasmMemory.bufferoffset256);
    }
}

Module.onRuntimeInitialized = function() {    
    var test = new Test();
    for (let i = 0i < 256i++) {
        console.log(test._data[i]);
    }
    for (let i = 0i < 256i++) {
        test._data[i] = 255 - i;
    }
    for (let i = 0i < 256i++) {
        console.log(test._data[i]);
    }
}
</script>


테스트는 간단하게 아래 명령어로 index.html 을 브라우징 해본다. 

단순히 firefox index.html 등과 같이 하면 필요한 test.js 파일이나 test.wasm 파일을 브라우저가 가져올 수 없어서 에러가 생기므로, 아래와 같이 one shot 웹서버를 기동시켜 브라우징하는것이다.

실행하고 나면 바로 웹서버가 중지되니, 다시 테스트할려면 아래 명령어를 다시 실행해야 한다.

실행후 브라우저 Console Log를 확인해 보면 0~255 다시 255~0 까지 출력되어 있을 것이다.

~/WASM_test$ emrun --port 8080 .