v8
This commit is contained in:
parent
b6095aff3f
commit
8000cfcbd7
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,4 +2,4 @@
|
||||
*.bak
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
docs
|
||||
/docs
|
||||
|
2
Makefile
2
Makefile
@ -13,7 +13,7 @@ init:
|
||||
|
||||
.PHONY: dev
|
||||
dev:
|
||||
cd docz; npm run start; cd ..
|
||||
cd docz; npm run start -- --host=0.0.0.0; cd ..
|
||||
|
||||
.PHONY: serve
|
||||
serve:
|
||||
|
261
docz/docs/03-demos/12-engines/02_v8.md
Normal file
261
docz/docs/03-demos/12-engines/02_v8.md
Normal file
@ -0,0 +1,261 @@
|
||||
---
|
||||
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<v8::Context> context = v8::Context::New(isolate);
|
||||
```
|
||||
|
||||
The following helper function evaluates C strings as JS code:
|
||||
|
||||
```cpp
|
||||
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:
|
||||
|
||||
```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<v8::Value> result = eval_code(isolate, context, file);
|
||||
```
|
||||
|
||||
To confirm the library is loaded, `XLSX.version` can be inspected:
|
||||
|
||||
```cpp
|
||||
/* 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:
|
||||
|
||||
```cpp
|
||||
/* 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:
|
||||
|
||||
```c
|
||||
/* 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 |
|
||||
|
||||
:::
|
||||
|
||||
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:
|
||||
|
||||
<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>
|
||||
|
||||
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.
|
106
docz/static/v8/sheetjs.v8.cc
Normal file
106
docz/static/v8/sheetjs.v8.cc
Normal file
@ -0,0 +1,106 @@
|
||||
/* sheetjs.v8 (C) SheetJS -- https://sheetjs.com */
|
||||
/* based on the official V8 samples ("BSD-3-Clause") */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "include/libplatform/libplatform.h"
|
||||
#include "include/v8.h"
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> eval_code(v8::Isolate *isolate, v8::Local<v8::Context> context, char* code) {
|
||||
v8::Local<v8::String> source = v8::String::NewFromUtf8(isolate, code).ToLocalChecked();
|
||||
v8::Local<v8::Script> script = v8::Script::Compile(context, source).ToLocalChecked();
|
||||
return script->Run(context).ToLocalChecked();
|
||||
}
|
||||
#define EVAL_CODE(x) eval_code(isolate, context, (char *)x)
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
/* initialize -- this part is from the hello world example */
|
||||
v8::V8::InitializeICUDefaultLocation(argv[0]);
|
||||
v8::V8::InitializeExternalStartupData(argv[0]);
|
||||
std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
|
||||
v8::V8::InitializePlatform(platform.get());
|
||||
v8::V8::Initialize();
|
||||
|
||||
v8::Isolate::CreateParams create_params;
|
||||
create_params.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
|
||||
v8::Isolate* isolate = v8::Isolate::New(create_params);
|
||||
{
|
||||
v8::Isolate::Scope isolate_scope(isolate);
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
||||
v8::Context::Scope context_scope(context);
|
||||
|
||||
/* load library */
|
||||
{
|
||||
/* read file */
|
||||
size_t sz; char *file = read_file("xlsx.full.min.js", &sz);
|
||||
if(!file) { perror("Error reading xlsx.full.min.js"); return 1; }
|
||||
|
||||
/* evaluate */
|
||||
v8::Local<v8::Value> result = EVAL_CODE(file);
|
||||
|
||||
/* free */
|
||||
free(file);
|
||||
}
|
||||
|
||||
/* get version string */
|
||||
{
|
||||
v8::Local<v8::Value> result = EVAL_CODE("XLSX.version");
|
||||
v8::String::Utf8Value vers(isolate, result);
|
||||
printf("SheetJS library version %s\n", *vers);
|
||||
}
|
||||
|
||||
/* read file */
|
||||
{
|
||||
/* read bytes */
|
||||
size_t sz; char *file = read_file(argv[1], &sz);
|
||||
if(!file) { perror("Error reading file"); return 1; }
|
||||
|
||||
/* copy into array buffer and assign to `buf` in the global scope */
|
||||
{
|
||||
std::unique_ptr<v8::BackingStore> back = v8::ArrayBuffer::NewBackingStore(isolate, sz);
|
||||
memcpy(back->Data(), file, sz);
|
||||
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);
|
||||
}
|
||||
|
||||
printf("Loaded file %s\n", argv[1]);
|
||||
}
|
||||
|
||||
/* parse workbook and assign to global `wb` property */
|
||||
{
|
||||
v8::Local<v8::Value> result = EVAL_CODE("globalThis.wb = XLSX.read(buf)");
|
||||
}
|
||||
|
||||
/* print CSV of first worksheet */
|
||||
{
|
||||
v8::Local<v8::Value> result = EVAL_CODE("XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]])");
|
||||
v8::String::Utf8Value csv(isolate, result);
|
||||
printf("%s\n", *csv);
|
||||
}
|
||||
|
||||
// write sheetjsw.xlsb
|
||||
{
|
||||
v8::Local<v8::Value> result = EVAL_CODE("XLSX.write(wb, {type:'array', bookType:'xlsb'})");
|
||||
v8::Local<v8::ArrayBuffer> ab = v8::Local<v8::ArrayBuffer>::Cast(result);
|
||||
FILE *f = fopen("sheetjsw.xlsb", "wb"); fwrite((char *)ab->Data(), 1, ab->ByteLength(), f); fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
/* cleanup -- this part is from the hello world example */
|
||||
isolate->Dispose();
|
||||
v8::V8::Dispose();
|
||||
v8::V8::DisposePlatform();
|
||||
delete create_params.array_buffer_allocator;
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user