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 |