WebAssembly: Calling Into JavaScript From Bare-Bones C Code
So you're working with WebAssembly and just have a few bits and pieces running in C, but want to call into JavaScript... read on to learn how!
Join the DZone community and get the full member experience.
Join For FreeIn my previous article, we explored the idea of creating a bare-bones WebAssembly module with no Emscripten plumbing. In that article, we were able to call into the module from JavaScript but we didn't get into the reverse:
How does one call into JavaScript from a module?
Calling Into Javascript From a WebAssembly Module
In this article, we're going to try and get a WebAssembly module to call a method that's defined in our JavaScript.
In order to compile C code, when the method being used isn't in the source code, you need to define the method signature using the extern keyword as shown in the following example:
// Define the JavaScript method's signature that we're going to be calling.
extern void CallJS(int iVal);
// A method that the JavaScript will call into to trigger our code
// that will in turn call a JavaScript method passing along the value
// received.
void Test(int iVal){ CallJS(iVal); }
You can run the following command line to build the wasm file:
emcc test.c -s WASM=1 -s SIDE_MODULE=1 -O1 -o test.wasm
To define the method in our JavaScript, we just need to add it to the env object that is part of the main object that we pass as the 2nd parameter to the WebAssembly.instantiate
method:
var importObject = {
'env': {
// ... (other properties/methods of this object)
'_CallJS': function(iVal){ alert("value received: " + iVal.toString()); }
}
};
Just like how we needed to add an underscore character before the method name when calling into the module from JavaScript, we also need to include an underscore before the method name here.
The following is the full HTML/JavaScript for our example module:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
</head>
<body>
<input type="text" id="txtValue" />
<input type="button" value="Pass Value" onclick="PassValue();" />
<script type="text/javascript">
var gModule = null;
var importObject = {
'env': {
'memoryBase': 0,
'tableBase': 0,
'memory': new WebAssembly.Memory({initial: 256}),
'table': new WebAssembly.Table({initial: 0, element: 'anyfunc'}),
'_CallJS': function(iVal){ alert("value received: " + iVal.toString()); }
}
};
fetch('test.wasm').then(response =>
response.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, importObject)
).then(results => {
gModule = results.instance; // Hold onto the module's instance so that we can reuse it
});
function PassValue(){
// Get the value from the textbox (convert the value from a string to an int)
var iVal = parseInt(document.getElementById("txtValue").value,10);
// Call the method in the module
gModule.exports._Test(iVal);
}
</script>
</body>
</html>
Published at DZone with permission of Gerard Gallant, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments