Playing with WebAssembly and C#
I will always remember the day I was shown asm.js running a demo of a game using the Unreal engine... in a web browser, no less!!! - My jaw was on the ground. That was 2014. Little did I know at the time but asm.js would seed the path towards WebAssembly.
WebAssembly (also known as WASM) looks very exciting. The homepage bills it as "a new portable, size- and load-time-efficient format suitable for compilation to the web.". It's being designed "as an open standard by a W3C Community Group that includes representatives from all major browsers.". Basically, it allows statically typed languages to be cross-compiled into a format that browsers can understand and execute. Wow!
Like I say, it looks exciting. For your standard line-of-business web apps, it doesn't sound like it will displace the use of JavaScript when building Web Apps but it will be useful for high performance apps like games or canvas related apps. Even the WebAssembly homepage's demo is a Tank game. In fact, Unity and Unreal have already ported their gaming engines to WebAssembly!
I am just wondering what the future holds for the technology. In five to ten years, could there be off-the-shelf libraries/frameworks (like the .NET Framework) written/compiled to WebAssembly? These libraries would then be consumed by normal JavaScript web apps to execute more performant operations. There are loads of useful C++ libraries out there that the web could benefit from.
By the way, if you want to dive into the nitty-gritty details of WebAssembly, this 5 part series from Mozilla's Lin Clarke is a fantastic diversion of your time.
Browser Support
As you can see from the caniuse.com website, WebAssembly is now supported in all major browsers and here's the thing: WebAssembly is on by default in these browsers, which means you don't have to flip a flag or anything like that to start using it. This means, as of today, there is no friction for end-users.
Language Support
asm.js was originally designed as a way to compile C++ to the web, but one of the advantages of WASM is that you can write it in any language that can cross-compile to WASM. All the major languages look like they will support it in the future including:
- C and C++
- Rust
- C#
- Java / Kotlin
- Go
- ...and even TypeScript which is a bit meta!
As an example of languages that are looking to target WASM, this guy here is maintaining a list of languages that are looking to cross-compile to WebAssembly.
Playing with WebAssembly
As I was reading up on all of this, I thought I would follow the main WebAssembly websites "Getting Started" pages to see just how easy it is to get up and running with a test project.
...But it turns out you can experiment and play around with wasm today using the online WasmExplorer or WasmFiddle websites, however these currently only accept C++ code. This is great because it saves you from having to dust off that C++ compiler.
So I decided to try it out and play with WasmExplorer. I went off and found some random C++ code on the old interwebs that checked if a year was a leap year or not.
This is the C++ code I decided to test out:
#include
using namespace std;
int main()
{
int year;
cout << "Enter a year: ";
cin >> year;
if (year % 4 == 0)
{
if (year % 100 == 0)
{
if (year % 400 == 0)
cout << year << " is a leap year.";
else
cout << year << " is not a leap year.";
}
else
cout << year << " is a leap year.";
}
else
cout << year << " is not a leap year.";
return 0;
}
But if you paste the above code wholesale into WasmExplorer, you get the following error:
Compiling C/C++ to Wast
Loading: //npmcdn.com/wasdk-capstone-x86/lib/capstone-x86.min.js
Compiling .wast to x86
wasm text error: parsing wasm text at 230:21
It turns out, the wasm compiler doesn't like the cout/cin
lines from the standard namespace. That's expected as the std::cin
doesn't make any sense in the code above so I did a little bit of code refactoring.
The modified code looked like this:
bool IsLeapYear( int year )
{
bool isLeapYear = false;
if (year % 4 == 0)
{
if (year % 100 == 0)
{
if (year % 400 == 0)
isLeapYear = true;
}
else
isLeapYear = true;
}
return isLeapYear;
}
Now, when I compile the above code I now get a successful compile and the ability to download a .wasm file! Nice!
But how do you instantiate this in the browser? Thankfully Mozilla have a great tutorial here. Essentially, you need to load the wasm module, then instantiate the module like this:
var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule, wasmImports);
// Now call C++ IsLeapYear function!
log(wasmInstance.exports.IsLeapYear(2001));
However, when I attempted to run this code locally from a standard directory on disk, I got the following error message regarding the fetch API:
Basically, I needed to serve this file from a simple web server. As I didn't want to mess with Windows IIS I decided to use Python as a quick way to host static content. I'm on Windows, so I fired up the WSL to make use of the Linux Ubuntu command prompt and issued this command:
python -m SimpleHTTPServer 8000
Then, I just needed to navigate to the directory where you want the web server to start from, and was up and running:
For some reason, when doing this my function names were mangled. Instead of IsLeapYear()
they were _Z10IsLeapYeari()
. I don't know if this is a side-effect of using WasmExplorer or if I had done something wrong but calling the function yields the correct results:
var importObject = {};
function fetchAndInstantiate(url, importObject) {
return fetch(url).then(response =>
response.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, importObject)
).then(results =>
results.instance
);
}
fetchAndInstantiate('test.wasm', importObject).then(function (instance) {
// Now call C++ IsLeapYear function! ( 2000 was a leap year... 2001 was not! )
console.log(instance.exports._Z10IsLeapYeari(2000));
console.log(instance.exports._Z10IsLeapYeari(2001));
})
Now when we run the above code we see the results from the calls. The 1 represents the call for the year 2000 and the result of 1 represents the call for the year 2001
C# & Blazor
So, that's all cool but what's the story for .NET and C#?
I am very interested in using C# to target WebAssembly. I use a lot of C# libraries and having the capability to cross-compile them to the browser would be amazing. Using WebAssembly in addition to PWA's could, in the future become the new norm. Imagine pulling down your C# business library in WebAssembly form to run in the browser. You wrote it in C# but its all executable from JavaScript.
While reading up on this topic, I found out about Blazor. This is an experimental C# UI Framework which is apparently highly experimental but a very compelling example of what could be possible in the world of WebAssembly. It makes use of a project named DotNetAnywhere which is a separate port of the .NET runtime
The great thing about Blazor is that you can actually play around with it today!! - The github page for the project can be found here and there is a Visual Studio extension you can install which gives you access to a Blazor application:
When you create the project you are presented with the following solution:
When you run the project you will see this screen, telling you you can navigate to http://localhost:5000 to view the web app.
With the web app looking like this:
The amazing thing about this demo is the fact that C# code is running on the client, in the browser! Here is a snippet of code from this project.
@functions {
WeatherForecast[] forecasts;
override protected async Task InitAsync()
{
using (var client = new HttpClient())
{
var json = await client.GetStringAsync("/sample-data/weather.json");
forecasts = JsonUtil.Deserialize(json);
}
}
class WeatherForecast
{
public string DateFormatted { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF { get; set; }
public string Summary { get; set; }
}
}
Under the covers, Blazor uses DotNetAnywhere which is a project I have never heard of before. It's another alternative .NET runtime. However, it doesn't sound like DNA is going to be the future platform for building .NET WebAssembly components as Steve Sanderson (The guy who created Blazor) mentions in this blog post which is worth reading.
However, while this project is just an experiment, it demonstrates the future potential of WebAssembly and C#.
Official C# to WebAssembly Tools
The good news is that the mono team are actively working on a wasm compiler for C#. The name of the tool is called mono-wasm and the project page can be found on github here. It's still early days but something I intend to keep an ear out for. I also found this comment here on example usage of the "mono-wasm" tool.
Wrapping Up
WebAssembly looks great and I am sure it will be another major milestone in the history of web development. Yes, it's still very new and there is a long way to go before it's used for mainstream development. There are also some limitations with it in its current form. For example, at the moment you cannot easily access the DOM from WebAssembly code but improvements in this area are apparently on the road-map!
The idea of writing library code once, using it on the server-side, then compiling and porting for use on the web really resonates with me. I think, if we fast-forward 5, 10 years, once WebAssembly is well established, it is going to have a huge part to play in the future of WebApps.
Comments
Post a Comment