C / Emscripten small example
I'm a C# developer and also wanted to write a small WASM library. I tried using C# with NativeAOT LLVM but the WASM file was very large even with all of the trimming options set.
And so I decided to try writing a C program and use Emscripten to create a WASM file and a javascript file that loads it and runs it. Below is a minimal working example of code that does some basic things that others may want to do (when starting from zero knowledge like myself).
I have assumed that you have followed the install instructions for Emscipten and Ruby etc. I used Visual Studio for coding and if you also do this, then just be aware that you will get errors showing in VS if you compile the code in VS, but when you compile it via Emsctipten, then all is well. You can set some VS configuration to point to the additional c files of Emscripten which helps with some errors, but dont get too hung up on them.
What the example does
an html web page will load the wasm via some js and then call a wasm function to get a string. This string is then displayed in the web page.
The string is made up of
some fixed text
the domain name (comes from wasm calling out to js to get the domain name)
the current time (epoch time). (This comes from using a standard emscripten js library to get the js Date.Now and return it to wasm)
So it covers both getting strings from js to wasm and returning strings from wasm to js, plus shows the use of built-in emscripten functions.
C Code
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <emscripten/html5.h>
#include <emscripten.h>
//------------------------------------------------------------------------
// Function to return a pointer to the string that has the domain name from a js call
//------------------------------------------------------------------------
char* GetDomainName() {
return (char*)EM_ASM_PTR({
var jsString = window.location.hostname;
//var jsString = Module['currentURL'];
var lengthBytes = lengthBytesUTF8(jsString) + 1;
var stringOnHeap = _malloc(lengthBytes);
stringToUTF8(jsString, stringOnHeap, lengthBytes);
return stringOnHeap;
});
}
//------------------------------------------------------------------------
// Function to return a pointer to the string
//------------------------------------------------------------------------
char* get_string() {
char* url = GetDomainName();
// built-in function to get the current time in milliseconds (epoch time)
double now = emscripten_date_now();
char output[100];
snprintf(output, 100, "Hello, its a great day! %s %f",url, now);
// Free the memory allocated by GetDomainName
free(url);
// strdup is a standard C function that allocates memory for the string and copies it to the heap
char* duplicate = strdup(output);
return duplicate;
}
Compiling the C code with Emscripten (from windows)
NB. this command is the windows version. To use from linux needs ./emcc syntax
emcc -oz -sEXPORTED_FUNCTIONS=_get_string,_free -sENVIRONMENT=web -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE='$stringToNewUTF8' generator.c -o generator.js
The EXPORTED_FUNCTIONS make the C function(s) available to call in the wasm file. They are the C function name prefixed with an underscore. _free is a built-in function from emscripten so you dont need to write one yourself to free memory (no garage collection here :-) )
This will generate 2 files
Generator.wasm (25KB)
Generator.js (54KB)
NB. The Generator.js file will have js code that is both standard load and run code and also has additional js functions that come from our C code example above
The web page
<!DOCTYPE html>
<html>
<head>
<title>Wasm String Example</title>
</head>
<body>
<div id="output"></div>
<script>
var Module = {};
Module.onRuntimeInitialized = () => {
let ptr = Module._get_string(); // call wasm function to load the string into memory and retrun a pointer to it
var str = UTF8ToString(ptr);
Module._free(ptr); // Free the allocated memory
// Display the string in the HTML
document.getElementById('output').textContent = str;
}
</script>
<script async="" type="text/javascript" src="Generator.js"></script>
</body>
</html>
How to run it
CORS will block the wasm file in the browser unless run via a web server. the emscripten install requires a python install and it comes with a simple web server. so you can run it with
(from a command prompt)
>python -m http.server
Then from the browser to to the web page. e.g.
http://localhost:8000/WasmTestPage.html
and you should see something like
Hello, its a great day! localhost 1745985924336.000000
and the time will change every time you refresh the page.
Summary
remember that strings are not natively available in wasm so we have to pass arrays of chars to and from wasm memory and then return a pointer the memory location, for the caller to then go and access the bytes from the memory location. Once finished with the memory, then you must free the memory to avoid memory leaks.
For C to put string data into wasm memory, then they need to be on the heap as anything on the stack will not be there as the function ends. This is why any string constants (arrays and const *) must be copied to heap memory for use.
You can use your own C free function or use the built-in emscripten _free function as per the example. All of the js interop is added to the Module variable in js. You can write your own C code to convert the pointer based string into a local variable, but its easier to use the emscipten built-in functions such as stringToUTF8 and UTF8ToString as they handle a few complexities.
Don't forget to free your heap memory appropriately and I hope this helps someone else. Remember this is my first C code as I am a C# developer.