--- title: C++ + Hermes pagination_prev: demos/bigdata/index pagination_next: solutions/input --- import current from '/version.js'; import CodeBlock from '@theme/CodeBlock'; Hermes is an embeddable JS engine written in C++. With some light shims, it can run the standalone browser scripts. The [Standalone scripts](/docs/getting-started/installation/standalone) can be parsed and evaluated in a Hermes context. :::caution Here be Dragons The main target for Hermes is React Native. At the time of writing, there was no official documentation for embedding the Hermes engine in C++ programs. ::: ## Integration Details _Initialize Hermes_ The runtime can be initialized in one line: ```cpp std::unique_ptr rt(facebook::hermes::makeHermesRuntime()); ``` Hermes does not expose a `console` or `global` variable, but those can be synthesized from JS code in the runtime. :::note pass The JavaScript code is shown below: ```js /* create global object */ var global = (function(){ return this; }).call(null); /* create a fake `console` from the hermes `print` builtin */ var console = { log: function(x) { print(x); } }; ``` ::: The code can be stored in a C string and evaluated after creating a runtime: ```cpp auto src = std::make_shared( /* create global object */ "var global = (function(){ return this; }).call(null);" /* create a fake `console` from the hermes `print` builtin */ "var console = { log: function(x) { print(x); } };" ); auto js = rt->prepareJavaScript(src, std::string("")); rt->evaluatePreparedJavaScript(js); ``` _Load SheetJS Scripts_ The main library can be loaded by reading the script from the file system and evaluating in the Hermes context: ```cpp static char *read_file(const char *filename, size_t *sz) { FILE *f = fopen(filename, "rb"); if(!f) return NULL; long fsize; { fseek(f, 0, SEEK_END); fsize = ftell(f); fseek(f, 0, SEEK_SET); } char *buf = (char *)malloc(fsize * sizeof(char)); *sz = fread((void *) buf, 1, fsize, f); fclose(f); return buf; } /* Unfortunately the library provides no C-friendly Buffer classes */ class CBuffer : public facebook::jsi::Buffer { public: CBuffer(const uint8_t *data, size_t size) : buf(data), sz(size) {} size_t size() const override { return sz; } const uint8_t *data() const override { return buf; } private: const uint8_t *buf; size_t sz; }; // ... /* load SheetJS library */ size_t sz; char *xlsx_full_min_js = read_file("xlsx.full.min.js", &sz); auto src = std::make_shared(CBuffer((uint8_t *)xlsx_full_min_js, sz)); auto js = rt->prepareJavaScript(src, std::string("xlsx.full.min.js")); rt->evaluatePreparedJavaScript(js); ``` To confirm the library is loaded, `XLSX.version` can be printed to the console: ```cpp auto src = std::make_shared( "console.log('SheetJS Library Version: ' + XLSX.version)" ); auto js = rt->prepareJavaScript(src, std::string("")); rt->evaluatePreparedJavaScript(js); ``` ### Reading Files Hermes supports `ArrayBuffer` but has no simple helper to read raw memory. Libraries are expected to implement `MutableBuffer`: ```cpp /* ArrayBuffer constructor expects MutableBuffer */ class CMutableBuffer : public facebook::jsi::MutableBuffer { public: CMutableBuffer(uint8_t *data, size_t size) : buf(data), sz(size) {} size_t size() const override { return sz; } uint8_t *data() override { return buf; } private: uint8_t *buf; size_t sz; }; // ... /* load payload as ArrayBuffer */ size_t sz; char *data = read_file(argv[1], &sz); auto payload = std::make_shared(CMutableBuffer((uint8_t *)data, sz)); auto ab = facebook::jsi::ArrayBuffer(*rt, payload); ``` It is strongly recommended to create a stub function to perform the entire workflow in JS code and pass the final result back to C++. :::note pass The stub function will be passed an `ArrayBuffer` object: ```js function(buf) { /* `buf` will be an ArrayBuffer */ var wb = XLSX.read(buf); return XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]]); } ``` ::: _C++ integration code_ ```cpp /* define stub function to read and convert first sheet to CSV */ auto src = std::make_shared( "(function(buf) {" "var wb = XLSX.read(buf);" "return XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]]);" "})" ); auto js = rt->prepareJavaScript(src, std::string("")); auto func = rt->evaluatePreparedJavaScript(js); /* call stub function and capture result */ auto csv = func.asObject(*rt).asFunction(*rt).call(*rt, ab); /* interpret as utf8 and print to stdout */ std::string str = csv.getString(*rt).utf8(*rt); ``` ## Complete Example The "Integration Example" covers a traditional integration in a C++ application, while the "CLI Test" demonstrates other concepts using the `hermes` CLI tool. ### Integration Example :::note This demo was tested in the following deployments: | Architecture | Git Commit | Date | |:-------------|:-----------|:-----------| | `darwin-x64` | `9f8603b` | 2023-07-24 | | `darwin-arm` | `869312f` | 2023-06-05 | ::: 0) Install build dependencies: ```bash brew install icu4c cmake ninja ``` 1) Make a project directory: ```bash mkdir sheetjs-hermes cd sheetjs-hermes ``` 2) Download the [`Makefile`](pathname:///hermes/Makefile): ```bash curl -LO https://docs.sheetjs.com/hermes/Makefile ``` 3) Download [`sheetjs-hermes.cpp`](pathname:///hermes/sheetjs-hermes.cpp): ```bash curl -LO https://docs.sheetjs.com/hermes/sheetjs-hermes.cpp ``` 4) Build the library (this is the `init` target): ```bash make init ``` 5) Build the application: ```bash make sheetjs-hermes ``` 6) Download the standalone script and test file: {`\ curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js curl -LO https://sheetjs.com/pres.numbers`} 7) Run the application: ```bash ./sheetjs-hermes pres.numbers ``` If successful, the program will print the library version number and the contents of the first sheet as CSV rows. ### CLI Test :::note This demo was last tested on 2023 May 30 against Hermes version `0.11.0`. ::: Due to limitations of the standalone binary, this demo will encode a test file as a Base64 string and directly add it to an amalgamated script. 0) Install the `hermes` command line tool 1) Download the standalone script and test file: {`\ curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js curl -LO https://sheetjs.com/pres.numbers`} 2) Bundle the test file and create `payload.js`: ```bash node -e "fs.writeFileSync('payload.js', 'var payload = \"' + fs.readFileSync('pres.numbers').toString('base64') + '\";')" ``` 3) Create support scripts: - `global.js` creates a `global` variable and defines a fake `console`: ```js title="global.js" var global = (function(){ return this; }).call(null); var console = { log: function(x) { print(x); } }; ``` - `hermes.js` will call `XLSX.read` and `XLSX.utils.sheet_to_csv`: ```js title="hermes.js" var wb = XLSX.read(payload, {type:'base64'}); console.log(XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]])); ``` 4) Create the amalgamation `xlsx.hermes.js`: ```bash cat global.js xlsx.full.min.js payload.js hermes.js > xlsx.hermes.js ``` The final script defines `global` before loading the standalone library. Once ready, it will read the bundled test data and print the contents as CSV. 5) Run the script using the Hermes standalone binary: ```bash hermes xlsx.hermes.js ``` If successful, the script will print CSV data from the test file