forked from sheetjs/docs.sheetjs.com
		
	
		
			
	
	
		
			283 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
		
		
			
		
	
	
			283 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| 
								 | 
							
								---
							 | 
						||
| 
								 | 
							
								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<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:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```cpp
							 | 
						||
| 
								 | 
							
								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:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```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>(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<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`:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```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>(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_
							 | 
						||
| 
								 | 
							
								>
							 | 
						||
| 
								 | 
							
								```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<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 last tested on 2023 May 30 against Hermes commit `869312f` on
							 | 
						||
| 
								 | 
							
								a Intel Mac. `llvm-g++ -v` printed:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								Apple clang version 14.0.0 (clang-1400.0.29.202)
							 | 
						||
| 
								 | 
							
								Target: x86_64-apple-darwin21.6.0
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								0) Make a project directory:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```bash
							 | 
						||
| 
								 | 
							
								mkdir sheetjs-hermes
							 | 
						||
| 
								 | 
							
								cd sheetjs-hermes
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								1) Download the [`Makefile`](pathname:///hermes/Makefile):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```bash
							 | 
						||
| 
								 | 
							
								curl -LO https://docs.sheetjs.com/hermes/Makefile
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								2) Download [`sheetjs-hermes.cpp`](pathname:///hermes/sheetjs-hermes.cpp):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```bash
							 | 
						||
| 
								 | 
							
								curl -LO https://docs.sheetjs.com/hermes/sheetjs-hermes.cpp
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								3) Build the library (this is the `init` target):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```bash
							 | 
						||
| 
								 | 
							
								make init
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								4) Build the application:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```bash
							 | 
						||
| 
								 | 
							
								make sheetjs-hermes
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								5) Download the standalone script and test file:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<ul>
							 | 
						||
| 
								 | 
							
								<li><a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`}>xlsx.full.min.js</a></li>
							 | 
						||
| 
								 | 
							
								<li><a href="https://sheetjs.com/pres.numbers">pres.numbers</a></li>
							 | 
						||
| 
								 | 
							
								</ul>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<CodeBlock language="bash">{`\
							 | 
						||
| 
								 | 
							
								curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js
							 | 
						||
| 
								 | 
							
								curl -LO https://sheetjs.com/pres.numbers`}
							 | 
						||
| 
								 | 
							
								</CodeBlock>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								6) 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:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<ul>
							 | 
						||
| 
								 | 
							
								<li><a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`}>xlsx.full.min.js</a></li>
							 | 
						||
| 
								 | 
							
								<li><a href="https://sheetjs.com/pres.numbers">pres.numbers</a></li>
							 | 
						||
| 
								 | 
							
								</ul>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<CodeBlock language="bash">{`\
							 | 
						||
| 
								 | 
							
								curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js
							 | 
						||
| 
								 | 
							
								curl -LO https://sheetjs.com/pres.numbers`}
							 | 
						||
| 
								 | 
							
								</CodeBlock>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								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
							 |