Reputation: 271
chapter-06-interact-with-js/js-without-glue, in Learn WebAssembly by Mike Rourke
It does not work. Error message is Uncaught (in promise) TypeError: m._init is not a function
.
At First, error message is Uncaught (in promise) LinkError: WebAssembly.instantiate(): Import #0 module="env" function="__memory_base" error: global import must be a number or WebAssembly.Global object
.
I replaced memoryBase and tableBase with __memory_base and __table_base in common/load-wasm.js. And it is solved.
But I get another error message Uncaught (in promise) TypeError: m._init is not a function
.
/*
* This file interacts with the canvas through imported functions.
* It moves a circle diagonally across the canvas.
*/
#define BOUNDS 255
#define CIRCLE_RADIUS 50
#define BOUNCE_POINT (BOUNDS - CIRCLE_RADIUS)
bool isRunning = true;
typedef struct Circle {
int x;
int y;
char direction;
} Circle;
struct Circle circle;
/*
* Updates the circle location by 1px in the x and y in a
* direction based on its current position.
*/
void updateCircleLocation() {
// Since we want the circle to "bump" into the edge of the canvas,
// we need to determine when the right edge of the circle
// encounters the bounds of the canvas, which is why we're using
// the canvas width - circle width:
if (circle.x == BOUNCE_POINT) circle.direction = 'L';
// As soon as the circle "bumps" into the left side of the
// canvas, it should change direction again.
if (circle.x == CIRCLE_RADIUS) circle.direction = 'R';
// If the direction has changed based on the x and y
// coordinates, ensure the x and y points update accordingly:
int incrementer = 1;
if (circle.direction == 'L') incrementer = -1;
circle.x = circle.x + incrementer;
circle.y = circle.y - incrementer;
}
// We need to wrap any imported or exported functions in an
// extern block, otherwise the function names will be mangled.
extern "C" {
// These functions are passed in through the importObj.env object
// and update the circle on the <canvas>:
extern int jsClearCircle();
extern int jsFillCircle(int x, int y, int radius);
/*
* Clear the existing circle element from the canvas and draw a
* new one in the updated location.
*/
void moveCircle() {
jsClearCircle();
updateCircleLocation();
jsFillCircle(circle.x, circle.y, CIRCLE_RADIUS);
}
bool getIsRunning() {
return isRunning;
}
void setIsRunning(bool newIsRunning) {
isRunning = newIsRunning;
}
void init() {
circle.x = 0;
circle.y = 255;
circle.direction = 'R';
setIsRunning(true);
}
}
<!doctype html>
<html lang="en-us">
<head>
<title>Interact with JS without Glue Code</title>
<script
type="application/javascript"
src="../common/load-wasm.js">
</script>
<style>
#myCanvas {
border: 2px solid black;
}
#actionButtonWrapper {
margin-top: 16px;
}
#actionButton {
width: 100px;
height: 24px;
}
</style>
</head>
<body>
<h1>Interact with JS without Glue Code</h1>
<canvas id="myCanvas" width="255" height="255"></canvas>
<div id="actionButtonWrapper">
<button id="actionButton">Pause</button>
</div>
<script type="application/javascript">
const canvas = document.querySelector('#myCanvas');
const ctx = canvas.getContext('2d');
const fillCircle = (x, y, radius) => {
ctx.fillStyle = '#fed530';
// Face outline:
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
ctx.closePath();
// Eyes:
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.arc(x - 15, y - 15, 6, 0, 2 * Math.PI);
ctx.arc(x + 15, y - 15, 6, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
// Mouth:
ctx.beginPath();
ctx.moveTo(x - 20, y + 10);
ctx.quadraticCurveTo(x, y + 30, x + 20, y + 10);
ctx.lineWidth = 4;
ctx.stroke();
ctx.closePath();
};
const env = {
table: new WebAssembly.Table({ initial: 8, element: 'anyfunc' }),
_jsFillCircle: fillCircle,
_jsClearCircle: function() {
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, 255, 255);
},
};
loadWasm('js-without-glue.wasm', { env }).then(({ instance }) => {
const m = instance.exports;
m._init();
// Move the circle by 1px in the x and y every 20 milliseconds:
const loopCircleMotion = () => {
setTimeout(() => {
m._moveCircle();
if (m._getIsRunning()) loopCircleMotion();
}, 20)
};
// Enable you to pause and resume the circle movement:
document.querySelector('#actionButton')
.addEventListener('click', event => {
const newIsRunning = !m._getIsRunning();
m._setIsRunning(newIsRunning);
event.target.innerHTML = newIsRunning ? 'Pause' : 'Start';
if (newIsRunning) loopCircleMotion();
});
loopCircleMotion();
});
</script>
</body>
</html>
/**
* Returns a valid importObj.env object with default values to pass
* into the WebAssembly.Instance constructor for Emscripten's
* Wasm module.
*/
const getDefaultEnv = () => ({
__memory_base: 0,
__table_base: 0,
memory: new WebAssembly.Memory({ initial: 256 }),
table: new WebAssembly.Table({ initial: 2, element: 'anyfunc' }),
abort: console.log
});
/**
* Returns a WebAssembly.Instance instance compiled from the specified
* .wasm file.
*/
function loadWasm(fileName, importObj = { env: {} }) {
// Override any default env values with the passed in importObj.env
// values:
const allEnv = Object.assign({}, getDefaultEnv(), importObj.env);
// Ensure the importObj object includes the valid env value:
const allImports = Object.assign({}, importObj, { env: allEnv });
// Return the result of instantiating the module (instance and module):
return fetch(fileName)
.then(response => {
if (response.ok) return response.arrayBuffer();
throw new Error(`Unable to fetch WebAssembly file ${fileName}`);
})
.then(bytes => WebAssembly.instantiate(bytes, allImports));
}
Following examples are the same.
- chapter-05-create-load-module/without-glue
- chapter-06-interact-with-js/js-with-glue
How can I fix it?
Upvotes: 2
Views: 2534
Reputation: 21
Add this line to your args list in the vscode default build task
"-s", "EXPORT_ALL=1"
If you look at the wat representation, you will see there is no _init... after you compile with the above added arg, it will be in there along with every other function. Quick and dirty solution.
The Emscripten FAQ says the following
Emscripten does dead code elimination of functions that are not called from the compiled code. While this does minimize code size, it can remove functions that you plan to call yourself (outside of the compiled code).
To make sure a C function remains available to be called from normal JavaScript, it must be added to the EXPORTED_FUNCTIONS using the emcc command line. For example, to prevent functions my_func() and main() from being removed/renamed, run emcc with:
emcc -s "EXPORTED_FUNCTIONS=['_main', '_my_func']" ...
main is by default included in the list of exported functions, but init is not.
Upvotes: 2
Reputation: 11
When you compile your .c file into the js output, use the following command: emcc js-with-glue.c -O3 -s WASM=1 -s MODULARIZE=1 -o js-with-glue.js -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall', 'cwrap']"
Then in your html file, any place you're referencing the m._() calls that our .c file exposes it replace with result.ccall(, ,,). Where result is the return from the promise.
Here is the source:
<script type="application/javascript">
Module()
.then(result => {
const m = result.asm;
console.log('asm', result.asm);
var initFunc = result.ccall('init', null, null, null );
// Move the rectangle by 1px in the x and y every 20 milliseconds:
const loopRectMotion = () => {
setTimeout(() => {
result.ccall('moveRect',null,null,null);
if (result.ccall('getIsRunning',null,null,null)) loopRectMotion();
}, 20)
};
// Enable you to pause and resume the rectangle movement:
document.querySelector('#actionButton')
.addEventListener('click', event => {
const newIsRunning = !result.ccall('getIsRunning',null,null,null);
result.ccall('setIsRunning',null,bool,newIsRunning);
event.target.innerHTML = newIsRunning ? 'Pause' : 'Start';
if (newIsRunning) loopRectMotion();
});
loopRectMotion();
});
</script>
Upvotes: 1