--- title: Blazing Fast Data Processing with V8 sidebar_label: C++ + V8 description: Process structured data in C++ or Rust programs. Seamlessly integrate spreadsheets by paring V8 and SheetJS. Modernize workflows while preserving Excel compatibility. pagination_prev: demos/bigdata/index pagination_next: solutions/input --- import current from '/version.js'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock'; [V8](https://v8.dev/) is an embeddable JavaScript engine written in C++. It powers Chromium and Chrome, NodeJS and Deno, Adobe UXP and other platforms. [SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing data from spreadsheets. This demo uses V8 and SheetJS to read and write spreadsheets. We'll explore how to load SheetJS in a V8 context and process spreadsheets and structured data from C++ and Rust programs. The ["Complete Example"](#complete-example) creates a C++ command-line tool for reading spreadsheet files and generating new workbooks. ["Bindings"](#bindings) covers V8 engine bindings for other programming languages. ## Integration Details The [SheetJS Standalone scripts](/docs/getting-started/installation/standalone) can be parsed and evaluated in a V8 context. _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 *isolate, v8::Local context, char* code, size_t sz = -1) { v8::Local source = v8::String::NewFromUtf8(isolate, code, v8::NewStringType::kNormal, sz).ToLocalChecked(); v8::Local script = v8::Script::Compile(context, source).ToLocalChecked(); return script->Run(context).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, sz); ``` 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.8.82` | `darwin-x64` | macOS 13.5.1 | `clang 14.0.3` | 2023-08-26 | | `11.8.82` | `darwin-arm` | macOS 13.5.1 | `clang 14.0.3` | 2023-08-26 | | `11.8.82` | `win10-x64` | Windows 10 | `CL 19.37.32822` | 2023-08-26 | | `12.0.72` | `linux-x64` | HoloOS 3.4.11 | `gcc 12.2.0` | 2023-10-11 | | `11.8.82` | `linux-arm` | Debian 11 | `gcc 10.2.1` | 2023-09-26 | ::: 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 pass When the demo was last tested, there were errors in the official V8 embed guide. The correct instructions are included below. ::: :::caution pass The build process is long and will test your patience. ::: ### Preparation 0) Prepare `/usr/local/lib`: ```bash mkdir -p /usr/local/lib cd /usr/local/lib ``` :::caution pass If this step throws a permission error, run: ```bash sudo mkdir -p /usr/local/lib sudo chmod 777 /usr/local/lib ``` ::: 0) Follow the official ["Visual Studio"](https://chromium.googlesource.com/chromium/src/+/master/docs/windows_build_instructions.md#visual-studio) installation steps. :::info pass Using the installer tool, the "Desktop development with C++" workload must be installed. In the sidebar, verify the following components are checked: - "C++ ATL for latest ... build tools" (`v143` when last tested) - "C++ MFC for latest ... build tools" (`v143` when last tested) In the "Individual components" tab, search for "Windows 11 SDK" and verify that "Windows 11 SDK (10.0.22621.0)" is checked. Click "Modify" and allow the installer to finish. The SDK debugging tools must be installed after the SDK is installed. 1) Using the Search bar, search "Apps & features". 2) When the setting panel opens, scroll down to "Windows Software Development Kit - Windows 10.0.22621 and click "Modify". 3) In the new window, select "Change" and click "Next" 4) Check "Debugging Tools for Windows" and click "Change" ::: The following `git` settings should be changed: ```bash git config --global core.autocrlf false git config --global core.filemode false git config --global branch.autosetuprebase always ``` 1) Download and install `depot_tools`: ```bash git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git ``` [The bundle](https://storage.googleapis.com/chrome-infra/depot_tools.zip) is a ZIP file that should be downloaded and extracted. The demo was last tested on an exFAT-formatted drive (mounted at `E:\`). After extracting, verify that the `depot_tools` folder is not read-only. 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` :::caution pass These instructions are for `cmd` use. Do not run in PowerShell! It is strongly recommended to use the "Developer Command Prompt" from Visual Studio as it prepares the console to run build tools. ::: ```bash set DEPOT_TOOLS_WIN_TOOLCHAIN=0 set PATH=E:\depot_tools;%PATH% ``` In addition, the `vs2022_install` variable must be set to the Visual Studio folder. For example, using the "Community Edition", the assignment should be ```bash set vs2022_install="C:\Program Files\Microsoft Visual Studio\2022\Community" ``` These environment variables can be persisted in the Control Panel. 3) Run `gclient` once to update `depot_tools`: ```bash gclient ``` ```bash gclient ``` :::caution pass `gclient` may throw errors related to `git` and permissions issues: ``` fatal: detected dubious ownership in repository at 'E:/depot_tools' 'E:/depot_tools' is on a file system that doesnot record ownership To add an exception for this directory, call: git config --global --add safe.directory E:/depot_tools ``` These issues are related to the exFAT file system. They were resolved by running the recommended commands and re-running `gclient`. ::: :::caution pass There were errors pertaining to `gitconfig`: ``` error: could not write config file E:/depot_tools/bootstrap-2@3_8_10_chromium_26_bin/git/etc/gitconfig: File exists ``` This can happen if the `depot_tools` folder is read-only. The workaround is to unset the read-only flag for the `E:\depot_tools` folder. ::: ### 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`. ```bash cd E:\ mkdir v8 cd v8 fetch v8 cd v8 ``` :::caution pass On exFAT, every cloned repo elicited the same `git` permissions error. `fetch` will fail with a clear remedy message such as ``` git config --global --add safe.directory E:/v8/v8 ``` Run the command then run `gclient sync`, repeating each time the command fails. ::: :::caution pass There were occasional `git` conflict errors: ``` v8/tools/clang (ERROR) ---------------------------------------- [0:00:01] Started. ... error: Your local changes to the following files would be overwritten by checkout: plugins/FindBadRawPtrPatterns.cpp ... Please commit your changes or stash them before you switch branches. Aborting error: could not detach HEAD ---------------------------------------- Error: 28> Unrecognized error, please merge or rebase manually. 28> cd E:\v8\v8\tools\clang && git rebase --onto 65ceb79efbc9d1dec9b1a0f4bc0b8d010b9d7a66 refs/remotes/origin/main ``` The recommended fix is to delete the referenced folder and re-run `gclient sync` ::: 5) Checkout the desired version. The following command pulls `12.0.72`: ```bash git checkout refs/tags/12.0.72 -b sample -t ``` ```bash git checkout tags/12.0.72 -b sample ``` :::caution pass The official documentation recommends: ```bash git checkout refs/tags/12.0.72 -b sample -t ``` This command failed in local testing: ``` E:\v8\v8>git checkout refs/tags/12.0.72 -b sample -t fatal: cannot set up tracking information; starting point 'refs/tags/12.0.72' is not a branch ``` ::: ### Build V8 6) Build the static library. ```bash tools/dev/v8gen.py x64.release.sample ninja -C out.gn/x64.release.sample v8_monolith ``` ```bash tools/dev/v8gen.py arm64.release.sample ninja -C out.gn/arm64.release.sample v8_monolith ``` ```bash tools/dev/v8gen.py x64.release.sample ninja -C out.gn/x64.release.sample v8_monolith ``` :::caution pass In the most recent Linux x64 test, there were build errors: ``` ../../src/compiler/turboshaft/wasm-gc-type-reducer.cc:212:18: error: 'back_insert_iterator' may not intend to support class template argument deduction [-Werror,-Wctad-maybe-unsupported] 212 | std::back_insert_iterator(snapshots), [this](Block* pred) { | ^ ../../build/linux/debian_bullseye_amd64-sysroot/usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/bits/stl_iterator.h:596:11: note: add a deduction guide to suppress this warning 596 | class back_insert_iterator | ^ 1 error generated. ``` This was resolved by manually editing `out.gn/x64.release.sample/args.gn`: ```ninja title="out.gn/x64.release.sample/args.gn (add to file)" treat_warnings_as_errors = false ``` ::: ```bash tools/dev/v8gen.py arm64.release.sample ``` Append the following line to `out.gn/arm64.release.sample/args.gn`: ```text title="out.gn/arm64.release.sample/args.gn (add to file)" is_clang = false ``` Run the build: ```bash ninja -C out.gn/arm64.release.sample v8_monolith ``` ```bash python3 tools\dev\v8gen.py -vv x64.release.sample ninja -C out.gn\x64.release.sample v8_monolith ``` :::caution pass In local testing, the build sometimes failed with a `dbghelp.dll` error: ``` Exception: dbghelp.dll not found in "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\dbghelp.dll" ``` This issue was fixed by removing and reinstalling "Debugging Tools for Windows" from the Control Panel as described in step 0. ::: :::caution pass In local testing, the `ninja` build failed with C++ deprecation errors: ```c++ ../..\src/wasm/wasm-code-manager.h(789,28): error: 'atomic_load>' is deprecated: warning STL4029: std::atomic_*() overloads for shared_ptr are deprecated in C++20. The shared_ptr specialization of std::atomic provides superior functionality. You can define _SILENCE_CXX20_OLD_SHARED_PTR_ATOMIC_SUPPORT_DEPRECATION_WARNING or _SILENCE_ALL_CXX20_DEPRECATION_WARNINGS to suppress this warning. [-Werror,-Wdeprecated-declarations] 789 | auto wire_bytes = std::atomic_load(&wire_bytes_); | ^ C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.37.32822\include\memory(3794,1): note: 'atomic_load>' has been explicitly marked deprecated here 3794 | _CXX20_DEPRECATE_OLD_SHARED_PTR_ATOMIC_SUPPORT _NODISCARD shared_ptr<_Ty> atomic_load( | ^ C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.37.32822\include\yvals_core.h(1317,7): note: expanded from macro '_CXX20_DEPRECATE_OLD_SHARED_PTR_ATOMIC_SUPPORT' 1317 | [[deprecated("warning STL4029: " \ | ^ 2 errors generated. [14/1303] CXX obj/torque_generated_definitions/js-atomics-synchronization-tq.obj FAILED: obj/torque_generated_definitions/js-atomics-synchronization-tq.obj ``` The workaround is to append a line to `out.gn\x64.release.sample\args.gn`: ```text title="out.gn\x64.release.sample\args.gn (add to end)" treat_warnings_as_errors = false ``` ::: 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 ``` ```bash g++ -I. -Iinclude samples/hello-world.cc -o hello_world -fno-rtti -lv8_monolith \ -lv8_libbase -lv8_libplatform -ldl -Lout.gn/arm64.release.sample/obj/ -pthread \ -std=c++17 -DV8_COMPRESS_POINTERS=1 -DV8_ENABLE_SANDBOX ./hello_world ``` ```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 ``` ```bash g++ -I. -Iinclude samples/hello-world.cc -o hello_world -fno-rtti -lv8_monolith \ -lv8_libbase -lv8_libplatform -ldl -Lout.gn/arm64.release.sample/obj/ -pthread \ -std=c++17 -DV8_COMPRESS_POINTERS=1 -DV8_ENABLE_SANDBOX ./hello_world ``` ```bash cl /I. /Iinclude samples/hello-world.cc /GR- v8_monolith.lib Advapi32.lib Winmm.lib Dbghelp.lib /std:c++17 /DV8_COMPRESS_POINTERS=1 /DV8_ENABLE_SANDBOX /link /out:hello_world.exe /LIBPATH:out.gn\x64.release.sample\obj\ .\hello_world.exe ``` ### Prepare Project 8) Make a new project folder: ```bash cd ~/dev mkdir -p sheetjs-v8 cd sheetjs-v8 ``` ```bash cd E:\ 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 ``` ```bash ln -s ~/dev/v8/v8/include ln -s ~/dev/v8/v8/out.gn/arm64.release.sample/obj ``` ```bash ln -s ~/dev/v8/v8/include ln -s ~/dev/v8/v8/out.gn/x64.release.sample/obj ``` ```bash ln -s ~/dev/v8/v8/include ln -s ~/dev/v8/v8/out.gn/arm64.release.sample/obj ``` ```bash copy E:\v8\v8\samples\hello-world.cc .\ ``` 10) Observe that exFAT does not support symbolic links and move on to step 11. 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 ``` ```bash cl /MT /I..\v8\v8\ /I..\v8\v8\include hello-world.cc /GR- v8_monolith.lib Advapi32.lib Winmm.lib Dbghelp.lib /std:c++17 /DV8_COMPRESS_POINTERS=1 /DV8_ENABLE_SANDBOX /link /out:hello_world.exe /LIBPATH:..\v8\v8\out.gn\x64.release.sample\obj\ .\hello_world.exe ``` ### Add SheetJS 12) Download the SheetJS Standalone script and test file. Save both files in the project directory: {`\ 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 ``` ```bash cl /MT /I..\v8\v8\ /I..\v8\v8\include sheetjs.v8.cc /GR- v8_monolith.lib Advapi32.lib Winmm.lib Dbghelp.lib /std:c++17 /DV8_COMPRESS_POINTERS=1 /DV8_ENABLE_SANDBOX /link /out:sheetjs.v8.exe /LIBPATH:..\v8\v8\out.gn\x64.release.sample\obj\ ``` 15) Run the demo: ```bash ./sheetjs.v8 pres.numbers ``` ```bash .\sheetjs.v8.exe 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 Bindings exist for many languages. As these bindings require "native" code, they may not work on every platform. ### 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 in the following deployments: | Architecture | V8 Crate | Date | |:-------------|:---------|:-----------| | `darwin-x64` | `0.75.1` | 2023-08-26 | | `darwin-arm` | `0.73.0` | 2023-06-05 | | `win10-x64` | `0.79.2` | 2023-10-09 | | `linux-x64` | `0.79.2` | 2023-10-11 | | `linux-arm` | `0.75.1` | 2023-08-30 | ::: 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 SheetJS Standalone script and move to the project directory: {`\ 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.