8.5 KiB
title | pagination_prev | pagination_next |
---|---|---|
C++ + V8 | demos/bigdata/index | 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 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:
v8::Isolate* isolate = v8::Isolate::New(create_params);
v8::Local<v8::Context> context = v8::Context::New(isolate);
The following helper function evaluates C strings as JS code:
v8::Local<v8::Value> eval_code(v8::Isolate *i, v8::Local<v8::Context> c, char* code) {
v8::Local<v8::String> source = v8::String::NewFromUtf8(i, code).ToLocalChecked();
v8::Local<v8::Script> 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:
/* 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<v8::Value> result = eval_code(isolate, context, file);
To confirm the library is loaded, XLSX.version
can be inspected:
/* get version string */
v8::Local<v8::Value> 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:
/* load C char array and save to an ArrayBuffer */
std::unique_ptr<v8::BackingStore> back = v8::ArrayBuffer::NewBackingStore(isolate, len);
memcpy(back->Data(), buf, len);
v8::Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, std::move(back));
v8::Maybe<bool> res = context->Global()->Set(context, v8::String::NewFromUtf8Literal(isolate, "buf"), ab);
/* parse with SheetJS */
v8::Local<v8::Value> 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:
/* write with SheetJS using type: "array" */
v8::Local<v8::Value> result = eval_code(isolate, context, "XLSX.write(wb, {type:'array', bookType:'xlsb'})");
/* pull result back to C++ */
v8::Local<v8::ArrayBuffer> ab = v8::Local<v8::ArrayBuffer>::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
- Download and install
depot_tools
:
mkdir -p /usr/local/lib
cd /usr/local/lib
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
- Add the path to the
PATH
environment variable:
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
- Run
gclient
once to updatedepot_tools
:
gclient
Clone V8
- Create a base directory:
mkdir -p ~/dev/v8
cd ~/dev/v8
fetch v8
cd v8
Note that the actual repo will be placed in ~/dev/v8/v8
.
- Checkout the desired version. The following command pulls
11.3.244.11
:
git checkout refs/tags/11.3.244.11 -b sample -t
Build V8
- Build the static library.
tools/dev/v8gen.py x64.release.sample
ninja -C out.gn/x64.release.sample v8_monolith
- Ensure the sample
hello-world
compiles and runs:
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
- Make a new project folder:
cd ~/dev
mkdir sheetjs-v8
cd sheetjs-v8
- Copy the sample source:
cp ~/dev/v8/v8/samples/hello-world.cc .
- Create symbolic links to the
include
headers andobj
library folders:
ln -s ~/dev/v8/v8/include
ln -s ~/dev/v8/v8/out.gn/x64.release.sample/obj
- Build and run the
hello-world
example from this folder:
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
- Download the standalone script and test file:
- xlsx.full.min.js
- pres.numbers
{\ curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js curl -LO https://sheetjs.com/pres.numbers
}
- Download
sheetjs.v8.cc
:
curl -LO https://docs.sheetjs.com/v8/sheetjs.v8.cc
- Compile standalone
sheetjs.v8
binary
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
- Run the demo:
./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:
/* assuming JS code returns an ArrayBuffer, copy result to a Vec<u8> */
fn eval_code_ab(scope: &mut v8::HandleScope, code: &str) -> Vec<u8> {
let source = v8::String::new(scope, &code).unwrap();
let script = v8::Script::compile(scope, source, None).unwrap();
let result: v8::Local<v8::ArrayBuffer> = script.run(scope).unwrap().try_into().unwrap();
/* In C++, `Data` returns a pointer. Collecting data into Vec<u8> is unsafe */
unsafe { return std::slice::from_raw_parts_mut(
result.data().unwrap().cast::<u8>().as_ptr(),
result.byte_length()
).to_vec(); }
}
:::note
This demo was last tested on 2023 May 22 against v8
crate version 0.71.2
:::
- Create a new project:
cargo new sheetjs-rustyv8
cd sheetjs-rustyv8
cargo run
- Add the
v8
crate:
cargo add v8
cargo run
- Download the Standalone build:
{\ curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js
}
- Download
main.rs
and replacesrc/main.rs
:
curl -L -o src/main.rs https://docs.sheetjs.com/v8/main.rs
- Download the test file and run:
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.