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
|