docs.sheetjs.com/docz/docs/03-demos/42-engines/20-chakra.md

12 KiB

title sidebar_label description pagination_prev pagination_next
Sheets in ChakraCore C++ + ChakraCore Process structured data in C++ programs. Seamlessly integrate spreadsheets into your program by pairing ChakraCore and SheetJS. Handle the most complex files without breaking a sweat. demos/bigdata/index solutions/input

import current from '/version.js'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock';

ChakraCore is an embeddable JS engine written in C++.

SheetJS is a JavaScript library for reading and writing data from spreadsheets.

This demo uses ChakraCore and SheetJS to pull data from a spreadsheet and print CSV rows. We'll explore how to load SheetJS in a ChakraCore context and process spreadsheets from a C++ program.

The "Integration Example" section includes a complete command-line tool for reading data from files.

Integration Details

Initialize ChakraCore

ChakraCore provides a global object through JsGetGlobalObject:

/* initialize */
JsRuntimeHandle runtime;
JsContextRef context;
size_t cookie = 0;
JsCreateRuntime(JsRuntimeAttributeNone, nullptr, &runtime);
JsCreateContext(runtime, &context);
JsSetCurrentContext(context);

/* obtain reference to global object */
JsValueRef global;
JsGetGlobalObject(&global);

/* DO WORK HERE */

/* cleanup */
JsSetCurrentContext(JS_INVALID_REFERENCE);
JsDisposeRuntime(runtime);

:::note pass

Cleanup and validation code is omitted from the discussion. The integration example shows structured validation and controlled memory usage.

:::

Load SheetJS Scripts

SheetJS Standalone scripts can be parsed and evaluated in a ChakraCore context.

The main library can be loaded by reading the script from the file system and evaluating in the ChakraCore context:

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;
}

#define EVAL_FILE(path) {\
  JsValueRef filename; \
  JsValueRef result; \
  JsCreateString(path, strlen(path), &filename); \
  size_t len; const char* script = read_file(path, &len);\
  JsValueRef src;\
  JsCreateExternalArrayBuffer((void*)script, len, nullptr, nullptr, &src);\
  JsRun(src, cookie++, filename, JsParseScriptAttributeNone, &result); \
}

// ...
  /* load library */
  EVAL_FILE("shim.min.js")
  EVAL_FILE("xlsx.full.min.js")

Reading Files

JsCreateExternalArrayBuffer can generate an ArrayBuffer from a C byte array:

/* read file */
size_t len; char *buf = read_file(argv[1], &len);

/* load data into array buffer */
JsValueRef ab;
JsCreateExternalArrayBuffer((void*)buf, len, nullptr, nullptr, &ab);

After pushing the data, it is easiest to store properties on globalThis:

/* assign to the `buf` global variable */
JsValueRef buf_str; JsCreateString("buf", strlen("buf"), &buf_str);
JsObjectSetProperty(global, buf_str, ab, true);

/* call globalThis.wb = XLSX.read(ab) */
const char* script_str ="globalThis.wb = XLSX.read(buf);"

JsValueRef script;
JsCreateExternalArrayBuffer((void*)script_str, (size_t)strlen(script_str), nullptr, nullptr, &script);
JsRun(script, cookie++, fname, JsParseScriptAttributeNone, &result);

Complete Example

The "Integration Example" covers a traditional integration in a C application, while the "CLI Test" demonstrates other concepts using the ch CLI tool.

Integration Example

:::note Tested Deployments

This demo was tested in the following deployments:

Architecture Git Commit Date
darwin-x64 c3ead3f 2024-03-15
darwin-arm c3ead3f 2023-10-19
win10-x64 c3ead3f 2024-03-04
linux-x64 1f6e17c 2024-04-25

:::

  1. Install dependencies:
brew install icu4c cmake
brew install icu4c cmake

On Arch Linux / HoloOS:

sudo pacman -S cmake clang

Install Visual Studio 2022 with the "Desktop Development with C++" workflow and the "Git for Windows" individual component.

All commands in this demo should be run in a "Native Tools Command Prompt".

  1. Download ChakraCore:
git clone https://github.com/chakra-core/ChakraCore.git
cd ChakraCore
git checkout 1f6e17c
cd ..
  1. Build ChakraCore:
cd ChakraCore
./build.sh --static --icu=/usr/local/opt/icu4c/include --test-build -j=8
cd ..

:::note pass

In some test runs, the build failed with the message:

!!! couldn't find ICU ...

This was fixed with a local symlink to the icu4c folder before the build step:

cd ChakraCore
mkdir -p usr/local/opt
ln -s /opt/homebrew/opt/icu4c usr/local/opt/icu4c
cd ..

:::

:::info pass

When the demo was last tested, ChakraCore JIT was not supported.

:::

cd ChakraCore
./build.sh --static --icu=/usr/local/opt/icu4c/include --test-build -j=8 --no-jit
cd ..

:::note pass

In some test runs, the build failed with the message:

!!! couldn't find ICU ...

This was fixed with a local symlink to the icu4c folder before the build step:

cd ChakraCore
mkdir -p usr/local/opt
ln -s /opt/homebrew/opt/icu4c usr/local/opt/icu4c
cd ..

:::

:::caution pass

When the demo was last tested, ChakraCore JIT was not supported.

:::

cd ChakraCore
./build.sh --static --embed-icu --test-build -j=8 --no-jit
cd ..

:::info pass

As explained in the ChakraCore project wiki1, the build accepts a few flags:

  • /p:Platform=x64 controls the architecture
  • /p:Configuration=Debug enables runtime checks
  • /p:RuntimeLib=static_library ensures MSVC libraries are statically linked

:::

cd ChakraCore
msbuild /m /p:Platform=x64 /p:Configuration=Debug /p:RuntimeLib=static_library Build\Chakra.Core.sln
cd ..

:::caution pass

During some test runs, the build failed with a message referencing cfguard.h:

    44>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.39.33519\include\cfguard.h(44,1): error C2220: the following warning is treated as an error
         (compiling source file 'ThreadContextInfo.cpp')

    44>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.39.33519\include\cfguard.h(44,1): warning C4005: '_GUARD_CHECK_ICALL': macro redefinition

The source file lib\Runtime\Base\ThreadContextInfo.cpp must be patched. The highlighted lines must be commented:

#if defined(_UCRT) && _CONTROL_FLOW_GUARD

// highlight-start
//# if _MSC_VER >= 1913
//#  include <cfguard.h>
//# else
// highlight-end
   extern "C" void __fastcall _guard_check_icall(_In_ uintptr_t _Target);
// highlight-next-line
//# endif
#endif

:::

After building, the generated DLL should be copied into the project folder:

copy .\ChakraCore\Build\VcBuild\bin\x64_debug\ChakraCore.dll .
  1. Download the source file and Makefile:
curl -L -O https://docs.sheetjs.com/chakra/sheetjs.ch.cpp
curl -L -O https://docs.sheetjs.com/chakra/Makefile
  1. Build the sample application:
make

:::caution pass

In some macOS test runs, the build failed with the message:

clang: error: no such file or directory: '/usr/local/opt/icu4c/lib/libicudata.a'

This was fixed by creating a symbolic link:

sudo mkdir -p /usr/local/opt
sudo ln -s /opt/homebrew/opt/icu4c /usr/local/opt
make

:::

cl sheetjs.ch.cpp ChakraCore.lib /I ChakraCore\lib\Jsrt /link /LIBPATH:ChakraCore\Build\VcBuild\bin\x64_debug
  1. Download the SheetJS Standalone script, shim script and test file. Move all three files to the project directory:

{\ curl -L -O https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js curl -L -O https://cdn.sheetjs.com/xlsx-${current}/package/dist/shim.min.js curl -L -O https://docs.sheetjs.com/pres.numbers}

  1. Run the test program:
./sheetjs.ch pres.numbers
.\sheetjs.ch.exe pres.numbers

If successful, the program will print the contents of the first sheet as CSV.

CLI Test

:::note Tested Deployments

This demo was last tested on 2024-04-25 against ch commit 1f6e17c.

:::

Due to limitations of the ch standalone binary, this demo will encode a test file as a Base64 string and directly add it to an amalgamated script.

  1. Download and extract the ChakraCore release ZIP. Copy the binary (bin/ch) to your project folder.

:::tip pass

The "Integration Example" also builds the ch binary! It will typically be placed in the ChakraCore/out/Test/ folder on Linux/macOS or ChakraCore\Build\VcBuild\bin\x64_debug\ on x64 Windows.

:::

  1. Download the SheetJS Standalone script, shim script and test file. Move all three files to the project directory:

{\ curl -L -O https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js curl -L -O https://cdn.sheetjs.com/xlsx-${current}/package/dist/shim.min.js curl -L -O https://docs.sheetjs.com/pres.numbers}

  1. Bundle the test file and create payload.js:
node -e "fs.writeFileSync('payload.js', 'var payload = \"' + fs.readFileSync('pres.numbers').toString('base64') + '\";')"
  1. Create support scripts:
  • global.js creates a global variable:
var global = (function(){ return this; }).call(null);
  • chakra.js will call XLSX.read and XLSX.utils.sheet_to_csv:
var wb = XLSX.read(payload, {type:'base64'});
console.log(XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]]));
  1. Create the amalgamation xlsx.chakra.js:
cat global.js xlsx.full.min.js payload.js chakra.js > xlsx.chakra.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.

  1. Run the script using the ChakraCore standalone binary:
./ch xlsx.chakra.js

  1. See "Building ChakraCore" in the ChakraCore project wiki ↩︎