8.0 KiB
title | pagination_prev | pagination_next |
---|---|---|
C++ + Hermes | demos/bigdata/index | 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 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:
std::unique_ptr<facebook::jsi::Runtime> 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:
/* 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:
auto src = std::make_shared<facebook::jsi::StringBuffer>(
/* 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("<eval>"));
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:
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>(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:
auto src = std::make_shared<facebook::jsi::StringBuffer>(
"console.log('SheetJS Library Version: ' + XLSX.version)"
);
auto js = rt->prepareJavaScript(src, std::string("<eval>"));
rt->evaluatePreparedJavaScript(js);
Reading Files
Hermes supports ArrayBuffer
but has no simple helper to read raw memory.
Libraries are expected to implement MutableBuffer
:
/* 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>(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:
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
/* define stub function to read and convert first sheet to CSV */
auto src = std::make_shared<facebook::jsi::StringBuffer>(
"(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("<eval>"));
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 |
:::
- Install build dependencies:
brew install icu4c cmake ninja
- Make a project directory:
mkdir sheetjs-hermes
cd sheetjs-hermes
- Download the
Makefile
:
curl -LO https://docs.sheetjs.com/hermes/Makefile
- Download
sheetjs-hermes.cpp
:
curl -LO https://docs.sheetjs.com/hermes/sheetjs-hermes.cpp
- Build the library (this is the
init
target):
make init
- Build the application:
make sheetjs-hermes
- Download the standalone script and test file:
- xlsx.full.min.js
- pres.numbers
{\ curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js curl -LO https://sheetjs.com/pres.numbers
}
- Run the application:
./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.
-
Install the
hermes
command line tool -
Download the standalone script and test file:
- xlsx.full.min.js
- pres.numbers
{\ curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js curl -LO https://sheetjs.com/pres.numbers
}
- Bundle the test file and create
payload.js
:
node -e "fs.writeFileSync('payload.js', 'var payload = \"' + fs.readFileSync('pres.numbers').toString('base64') + '\";')"
- Create support scripts:
global.js
creates aglobal
variable and defines a fakeconsole
:
var global = (function(){ return this; }).call(null);
var console = { log: function(x) { print(x); } };
hermes.js
will callXLSX.read
andXLSX.utils.sheet_to_csv
:
var wb = XLSX.read(payload, {type:'base64'});
console.log(XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]]));
- Create the amalgamation
xlsx.hermes.js
:
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.
- Run the script using the Hermes standalone binary:
hermes xlsx.hermes.js
If successful, the script will print CSV data from the test file