7.6 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:
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++.
JS Stub function
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 | 869312f | 2023-05-30 | 
| 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 inittarget):
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 hermescommand 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.jscreates a- globalvariable and defines a fake- console:
var global = (function(){ return this; }).call(null);
var console = { log: function(x) { print(x); } };
- hermes.jswill call- XLSX.readand- XLSX.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