I'm stuck with how to load a C++ function in a React code, using wasm compiler.
My C++ is composed of two files, which result after compilation in a 160kb wasm file. Here is the command I currently use for compilation (running on macOS).
em++ ../main.cpp ../stringFormat.cpp -s WASM=1 -s EXPORT_ALL=1 -s MODULARIZE=1 -O3 --closure 1 -o lss.js -std=c++11
I then copy the lss and the wasm file together in my React code, in the same folder.
src
- utils
- wasm
lss.js
lss.wasm
But then, whenever I try to import lss.js in another file, my app crashes with a bunch of undefined expressions.
My js file
import * as lss from '../wasm/lss'
./src/utils/wasm/lss.js
Line 10:6: Expected an assignment or function call and instead saw an expression no-unused-expressions
Line 11:69: Expected an assignment or function call and instead saw an expression no-unused-expressions
Line 11:117: 'read' is not defined no-undef
Line 11:197: 'readbuffer' is not defined no-undef
Line 11:214: 'read' is not defined no-undef
Line 11:336: 'quit' is not defined no-undef
Line 11:367: Unexpected use of 'print' no-restricted-globals
Line 11:430: Unexpected use of 'print' no-restricted-globals
Line 11:493: 'printErr' is not defined no-undef
Line 12:1: Unexpected use of 'print' no-restricted-globals
Line 12:22: Expected an assignment or function call and instead saw an expression no-unused-expressions
Line 12:26: Unexpected use of 'self' no-restricted-globals
Line 14:307: Expected an assignment or function call and instead saw an expression no-unused-expressions
Line 23:174: Expected an assignment or function call and instead saw an expression no-unused-expressions
Line 29:10: Expected an assignment or function call and instead saw an expression no-unused-expressions
Line 29:152: 'readline' is not defined no-undef
Line 29:260: Expected an assignment or function call and instead saw an expression no-unused-expressions
Line 29:350: Expected an assignment or function call and instead saw an expression no-unused-expressions
Line 29:433: Expected an assignment or function call and instead saw an expression no-unused-expressions
Line 30:19: Expected an assignment or function call and instead saw an expression no-unused-expressions
...
I also tried to generate standalone wasm file by adding a SIDE_MODULE=1 flag on compilation.
// My util function to load wasm file to js
const loadWebAssembly = (filename, imports = {}) =>
WebAssembly
.instantiateStreaming(fetch(filename), imports)
.then(({instance}) => instance.exports);
// wasm file is now in my static folder, in public/
const lss = loadWebAssembly('wasm/lss.wasm')
// splitIntoCommonSequences is the C++ function I try to call
.then(module => Promise.resolve(module.splitIntoCommonSequences));
export {lss};
But then I got another error.
WebAssembly.instantiate(): Import #0 module="env" error: module is not an object or function
I tried to figure out how to declare a proper import object for my situation without any success. Is their any better solution ? How do I know what to put inside my import object ?
Thanks !
Use file: notation to import the wasm package that we created using wasm-pack in the package. json. After adding the package, you just need to run npm install to install the packages. One additional thing you need to take care of is changing the scripts from react-scripts to craco .
wasm : Create a new XMLHttpRequest() instance, and use its open() method to open a request, setting the request method to GET , and declaring the path to the file we want to fetch. The key part of this is to set the response type to 'arraybuffer' using the responseType property.
The WebAssembly VM provides a sandbox to ensure application safety. However, this sandbox is also a very limited “computer” that has no concept of file system, network, or even a clock or timer. That is very limiting for the Rust programs running inside WebAssembly.
So I figured out their were more steps to do to get WASM binary working with React, especially if you want to import it in React es6 modules (not from public folder).
I don't claim this as a general solution, just the one that worked for me (and seems to work in most cases).
COMPILER FLAGS
Here is the em++ build command I now use :
em++ ../main.cpp ../stringFormat.cpp \
-Os -g1 \
-s WASM=1 \
-s MALLOC=emmalloc \
-s ALLOW_MEMORY_GROWTH=1 \
-s EXPORT_ES6=1 \
-s MODULARIZE=1 \
-s 'EXPORT_NAME="LongerSubSequence"' \
-s 'ENVIRONMENT="web"' \
--bind \
-o lss.mjs \
-std=c++11 || exit 1
Not sure about all the options, they may be some optimization to do here. The important ones are :
MODULARIZE: Export all functions under a common Module (named with EXPORT_NAME flag) so you can just import js as Module and call Module.My_CPP_Function in your js code.
g1: Keep generated glue code readable enough to manage next step.
ADDING FILES TO REACT
The process generates two files : lss.mjs and lss.wasm. They go like this in the project tree of React :
My_React_Project
|_public
| |_/path/to/lss.wasm
|
|_src
|_/path/to/lss.mjs
/path/to/ can be any path inside the folder, even root.
ADAPT GLUE JS
Finally, to fix errors, I edited the generated lss.mjs file :
/* eslint-disable */
at top of the file, to avoid React syntax errors.var _scriptDir = import.meta.url;
with var _scriptDir = '/path/to/lss.wasm';
, relative to public folder. Not sure if it is useful regarding the following steps, but React will just crash with the import.meta syntax.scriptDirectory = self.location.href;
with scriptDirectory = window.self.location.href;
, since React es6 functions aren't binded to window.var dataURIPrefix = "data:application/octet-stream;base64,";
function isDataURI(filename) {
return String.prototype.startsWith ? filename.startsWith(dataURIPrefix) : filename.indexOf(dataURIPrefix) === 0;
}
It is used by the compiler to automatically locate the binary file, but I handled it by myself instead.
var wasmBinaryFile = "lss.wasm";
with const wasmBinaryFile = '/path/to/lss.wasm';
('/' will point to public folder).if (!isDataURI(wasmBinaryFile)) {
wasmBinaryFile = locateFile(wasmBinaryFile);
}
function getBinary() {
try {
if (wasmBinary) {
return new Uint8Array(wasmBinary);
}
if (readBinary) {
return readBinary(wasmBinaryFile);
} else {
throw "both async and sync fetching of the wasm failed";
}
} catch (err) {
abort(err);
}
}
getBinaryPromise()
function with the following one:const getBinaryPromise = () => new Promise((resolve, reject) => {
fetch(wasmBinaryFile, { credentials: 'same-origin' })
.then(
response => {
if (!response['ok']) {
throw "failed to load wasm binary file at '" + wasmBinaryFile + "'";
}
return response['arrayBuffer']();
}
)
.then(resolve)
.catch(reject);
});
if (!wasmBinary && typeof WebAssembly.instantiateStreaming === "function" && !isDataURI(wasmBinaryFile) && typeof fetch === "function")
with
if (!wasmBinary && typeof WebAssembly.instantiateStreaming === "function" && typeof fetch === "function")
This just remove the isDataURI condition, since we 'trust' the path we gave to our binary file.
NOW USE YOUR BINARY CODE
And that's all it took for me to manage it. Now, in any of my react files, I can just use my c++ function like this :
anyfile.js
import LongerSubSequenceGlue from 'path/to/lss.mjs';
// Avoid main function running if you just want to use another function
const lssModule = LongerSubSequenceGlue({
noInitialRun: true,
noExitRuntime: true
});
//...
lssModule.my_cpp_function(...my_params);
Worked like a charm on Chrome, Firefox and Safari, will make more test later.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With