--- title: C++ + V8 pagination_prev: demos/bigdata/index pagination_next: solutions/input --- import current from '/version.js'; import CodeBlock from '@theme/CodeBlock'; V8 is an embeddable JS engine written in C++. It powers Chromium and Chrome, NodeJS and Deno, Adobe UXP and other platforms. The [Standalone scripts](/docs/getting-started/installation/standalone) can be parsed and evaluated in a V8 context. ## Integration Details _Initialize V8_ The official V8 `hello-world` example covers initialization and cleanup. For the purposes of this demo, the key variables are noted below: ```cpp v8::Isolate* isolate = v8::Isolate::New(create_params); v8::Local context = v8::Context::New(isolate); ``` The following helper function evaluates C strings as JS code: ```cpp v8::Local eval_code(v8::Isolate *i, v8::Local c, char* code) { v8::Local source = v8::String::NewFromUtf8(i, code).ToLocalChecked(); v8::Local script = v8::Script::Compile(i, source).ToLocalChecked(); return script->Run(c).ToLocalChecked(); } ``` _Load SheetJS Scripts_ The main library can be loaded by reading the scripts from the file system and evaluating in the V8 context: ```cpp /* simple wrapper to read the entire script file */ 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; } // ... size_t sz; char *file = read_file("xlsx.full.min.js", &sz); v8::Local result = eval_code(isolate, context, file); ``` To confirm the library is loaded, `XLSX.version` can be inspected: ```cpp /* get version string */ v8::Local result = eval_code(isolate, context, "XLSX.version"); v8::String::Utf8Value vers(isolate, result); printf("SheetJS library version %s\n", *vers); ``` ### Reading Files V8 supports `ArrayBuffer` natively. Assuming `buf` is a C byte array, with length `len`, this snippet stores the data as an `ArrayBuffer` in global scope: ```cpp /* load C char array and save to an ArrayBuffer */ std::unique_ptr back = v8::ArrayBuffer::NewBackingStore(isolate, len); memcpy(back->Data(), buf, len); v8::Local ab = v8::ArrayBuffer::New(isolate, std::move(back)); v8::Maybe res = context->Global()->Set(context, v8::String::NewFromUtf8Literal(isolate, "buf"), ab); /* parse with SheetJS */ v8::Local result = eval_code(isolate, context, "globalThis.wb = XLSX.read(buf)"); ``` `wb` will be a variable in the JS environment that can be inspected using the various SheetJS API functions. ### Writing Files The underlying memory from an `ArrayBuffer` can be recovered: ```c /* write with SheetJS using type: "array" */ v8::Local result = eval_code(isolate, context, "XLSX.write(wb, {type:'array', bookType:'xlsb'})"); /* pull result back to C++ */ v8::Local ab = v8::Local::Cast(result); size_t sz = ab->ByteLength(); char *buf = ab->Data(); ``` The resulting `buf` can be written to file with `fwrite`. ## Complete Example :::note This demo was tested in the following deployments: | V8 Version | Platform | OS Version | Compiler | Date | |:--------------|:-------------|:-------------|:---------------|:-----------| | `11.3.244.11` | `darwin-x64` | macOS 13.2 | `clang 14.0.3` | 2023-05-20 | | `11.3.244.11` | `linux-x64` | HoloOS 3.4.6 | `gcc 12.2.0` | 2023-05-20 | ::: This program parses a file and prints CSV data from the first worksheet. It also generates an XLSB file and writes to the filesystem. :::caution At the time of writing, there were errors in the official V8 embed guide for the macOS platform. The correct instructions are included below. ::: :::caution The build process is long and will test your patience. ::: ### Preparation 1) Download and install `depot_tools`: ```bash mkdir -p /usr/local/lib cd /usr/local/lib git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git ``` 2) Add the path to the `PATH` environment variable: ```bash export PATH="/usr/local/lib/depot_tools:$PATH" ``` At this point, it is strongly recommended to add the line to a shell startup script such as `.bashrc` or `.zshrc` 3) Run `gclient` once to update `depot_tools`: ```bash gclient ``` ### Clone V8 4) Create a base directory: ```bash mkdir -p ~/dev/v8 cd ~/dev/v8 fetch v8 cd v8 ``` Note that the actual repo will be placed in `~/dev/v8/v8`. 5) Checkout the desired version. The following command pulls `11.3.244.11`: ```bash git checkout refs/tags/11.3.244.11 -b sample -t ``` ### Build V8 6) Build the static library. ```bash tools/dev/v8gen.py x64.release.sample ninja -C out.gn/x64.release.sample v8_monolith ``` 7) Ensure the sample `hello-world` compiles and runs: ```bash g++ -I. -Iinclude samples/hello-world.cc -o hello_world -fno-rtti -lv8_monolith \ -lv8_libbase -lv8_libplatform -ldl -Lout.gn/x64.release.sample/obj/ -pthread \ -std=c++17 -DV8_COMPRESS_POINTERS=1 -DV8_ENABLE_SANDBOX ./hello_world ``` ### Prepare Project 8) Make a new project folder: ```bash cd ~/dev mkdir sheetjs-v8 cd sheetjs-v8 ``` 9) Copy the sample source: ```bash cp ~/dev/v8/v8/samples/hello-world.cc . ``` 10) Create symbolic links to the `include` headers and `obj` library folders: ```bash ln -s ~/dev/v8/v8/include ln -s ~/dev/v8/v8/out.gn/x64.release.sample/obj ``` 11) Build and run the `hello-world` example from this folder: ```bash g++ -I. -Iinclude hello-world.cc -o hello_world -fno-rtti -lv8_monolith \ -lv8_libbase -lv8_libplatform -ldl -Lobj/ -pthread -std=c++17 \ -DV8_COMPRESS_POINTERS=1 -DV8_ENABLE_SANDBOX ./hello_world ``` ### Add SheetJS 12) Download the standalone script and test file: {`\ curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js curl -LO https://sheetjs.com/pres.numbers`} 13) Download [`sheetjs.v8.cc`](pathname:///v8/sheetjs.v8.cc): ```bash curl -LO https://docs.sheetjs.com/v8/sheetjs.v8.cc ``` 14) Compile standalone `sheetjs.v8` binary ```bash g++ -I. -Iinclude sheetjs.v8.cc -o sheetjs.v8 -fno-rtti -lv8_monolith \ -lv8_libbase -lv8_libplatform -ldl -Lobj/ -pthread -std=c++17 \ -DV8_COMPRESS_POINTERS=1 -DV8_ENABLE_SANDBOX ``` 15) Run the demo: ```bash ./sheetjs.v8 pres.numbers ``` If the program succeeded, the CSV contents will be printed to console and the file `sheetjsw.xlsb` will be created. That file can be opened with Excel. ## Bindings V8 is easily embeddable. Bindings exist for many languages. ### Rust The `v8` crate provides binary builds and straightforward bindings. The Rust code is similar to the C++ code. Pulling data from an `ArrayBuffer` back into Rust involves an unsafe operation: ```rust /* assuming JS code returns an ArrayBuffer, copy result to a Vec */ fn eval_code_ab(scope: &mut v8::HandleScope, code: &str) -> Vec { let source = v8::String::new(scope, &code).unwrap(); let script = v8::Script::compile(scope, source, None).unwrap(); let result: v8::Local = script.run(scope).unwrap().try_into().unwrap(); /* In C++, `Data` returns a pointer. Collecting data into Vec is unsafe */ unsafe { return std::slice::from_raw_parts_mut( result.data().unwrap().cast::().as_ptr(), result.byte_length() ).to_vec(); } } ``` :::note This demo was last tested on 2023 May 22 against `v8` crate version `0.71.2` ::: 1) Create a new project: ```bash cargo new sheetjs-rustyv8 cd sheetjs-rustyv8 cargo run ``` 2) Add the `v8` crate: ```bash cargo add v8 cargo run ``` 3) Download the [Standalone build](/docs/getting-started/installation/standalone): {`\ curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`} 4) Download [`main.rs`](pathname:///v8/main.rs) and replace `src/main.rs`: ```bash curl -L -o src/main.rs https://docs.sheetjs.com/v8/main.rs ``` 5) Download [the test file](https://sheetjs.com/pres.numbers) and run: ```bash curl -LO https://sheetjs.com/pres.numbers cargo run pres.numbers ``` If the program succeeded, the CSV contents will be printed to console and the file `sheetjsw.xlsb` will be created. That file can be opened with Excel.