.NET everywhere apparently also means Windows 3.11 and DOS
I often talk about how .NET Core is open source and runs "everywhere." MonoGame, Unity, Apple Watches, Raspberry Pi, and Microcontrollers (as well as a dozen Linuxes, Windows, etc) is a lot of places.
Michal Strehovský wants C# to run EVERYWHERE and I love him for it.
He recently got some C# code running in two "impossible" places that are now added to our definition of everywhere. While these are fun experiments (don't do this in production) it does underscore the flexibility of both Michals' technical abilities and the underlying platform.
Running C# on Windows 3.11
In this 7 tweet thread Michael talks about how he got C# running in Windows 3.11. The app is very simple, just calling MessageBoxA which has been in Windows since Day 1. He's using DllImport/PInvoke to call MessageBox and receive its result.
I'm showing this Windows 3.11 app first because it's cool, but he started where his DOS experiment left off. He's compiling C# native code, and once that's done you can break all kinds of rules.
In this example he's running Win16...not Win32. However (I was alive and coding and used this on a project!) in 1992 there was a bridge technology called Win32s that was a subset of APIs that were in Windows NT and were backported to Windows 3.11 in the form of Win32s. Given some limitations, you could write 32 bit code and thunk from Win16 to Win32.
Michal learned that the object files that CoreTR's AOT (ahead of time) compiler in 2020 can be linked with the 1994 linker from Visual C++ 2.0. The result is native code that links up with Win32s that runs in 16-bit (ish) Windows 3.11. Magical. Kudos Michal.
Running C# in 8kb on DOS
I've blogged about self-contained .NET Core 3.x executables before and I'm a huge fan. I got my app down to 28 megs. It's small by some measurements, given that it includes the .NET runtime and a lot of accoutrements. Certainly one shouldn't judge a VM/runtime by its hello world size, but Michal wanted to see how small he could go - with 8000 bytes as the goal!
He's using text-mode which I think is great. He also removes the need for the garbage collector by using a common technique - no allocations allowed. That means you can't use new anywhere. No reference types.
He uses things like "fixed char[]" fields to declare fixed arrays, remembering they must live on the stack and the stack is small.
Of course, when you dotnet publish something self-contained, you'll initially get a 65 meg ish EXE that includes the app, the runtime, and the standard libraries.
dotnet publish -r win-x64 -c Release
He can use ILLinker and PublishedTrimmed to use .NET Core 3.x's Tree Trimming, but that gets it down to 25 megs.
He tries using Mono and mkbundle and that gets him down to 18.2 megs but then he hits a bug. And he's still got a runtime.
So the only runtime that isn't a runtime is CoreRT which includes no virtual machine, just functions to support you.
dotnet publish -r win-x64 -c Release /p:Mode=CoreRT
And this gets him to 4.7 megs, but still too big. Some tweaks go to about 3 megs. He can pull out reflection entirely and get to 1.2 megs! It'll fit on a floppy now!
dotnet publish -r win-x64 -c Release /p:Mode=CoreRT-ReflectionFree
This one megabyte size seems to be a hardish limit with just the .NET SDK.
Here's where Michal goes off the rails. He makes a stub reimplementation of the System base types! Then recompiles with some magic switches to get an IL only version of the EXE
csc.exe /debug /O /noconfig /nostdlib /runtimemetadataversion:v4.0.30319 MiniBCL.cs Game\FrameBuffer.cs Game\Random.cs Game\Game.cs Game\Snake.cs Pal\Thread.Windows.cs Pal\Environment.Windows.cs Pal\Console.Windows.cs /out:zerosnake.ilexe /langversion:latest /unsafe
Then he feeds that to CoreIT to get the native code
ilc.exe zerosnake.ilexe -o zerosnake.obj --systemmodule zerosnake --Os -g
yada yada yada and he's now here
"Now we have zerosnake.obj — a standard object file that is no different from object files produced by other native compilers such as C or C++. The last step is linking it."
A few more tweaks at he's at 27kb! He then pulls off a few linker switches to disable and strip various things - using the same techniques that native developers use and the result is 8176 bytes. Epic.
link.exe /debug:full /subsystem:console zerosnake.obj /entry:__managed__Main kernel32.lib ucrt.lib /merge:.modules=.rdata /merge:.pdata=.rdata /incremental:no /DYNAMICBASE:NO /filealign:16 /align:16
a
What's the coolest and craziest place you've ever run .NET code? Go follow Michal on Twitter and give him some applause.
Sponsor: Like C#? We do too! That’s why we've developed a fast, smart, cross-platform .NET IDE which gives you even more coding power. Clever code analysis, rich code completion, instant search and navigation, an advanced debugger... With JetBrains Rider, everything you need is at your fingertips. Code C# at the speed of thought on Linux, Mac, or Windows. Try JetBrains Rider today!
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
in Windows 3.11 and DOS only rudimentary program code could be run (for now) as the .NET runtime had been stripped down to almost zero.
Nevertheless Michal did a great job to show how much overhead is included in a .NET application out of the box and what could be achieved if knowledge and some time is investigated to tune the working set of the application.
Hopefully we'll see adding support to the .NET tool chain to perform those kind of .NET application tuning.
Merging Mono repo into .NET 5 runtime this weekend is also a good starting point as Mono does include several improved tools compared against existing .NET Framework / Core 3.1 - eg. linker etc and therefore will sooner or later be available for all .NET 5 platforms.
I still like it. I mean, he could have written a tiny CLR that just maps the assembly into memory, looks for the CLR header, finds the entry point from metadata, and then starts interpreting the IL opcodes. I don't know how hard it is to implement P/Invoke this way (might be easier than a managed method call) and forget about the heap, but still. This would even run .NET on MIPS or Alpha Windows NT. :P
http://matsujirushi.hatenablog.jp/entry/2018/12/29/173521
I haven't done anything at this level since "Inter segment self-relative fixup error" back in the 80s
I bow down before him.
RELATED: I've been working on a project called CS2X that will be able to run at native C performance apps written in a C# subset on Win 3.11, DOS, etc, etc and support an agnostic rendering layer for new, old and embedded platforms. Native CPU/GPU performance on each target in short.
https://github.com/reignstudios/CS2X
https://github.com/reignstudios/Orbital-Framework
VS 2017 & 2019 no longer support the Phone SDK.
So it actually .Net not quite everywhere...
https://www.hanselman.com/blog/EverythingsBrokenAndNobodysUpset.aspx
I'm concerned about Visual Studio...been using it since 6.0. There have always been bugs, but VS 2017 was absolutely horrible; I've never seen a Microsoft program crash so often. 2019 has been better, however every point release update (16.4, 16.4.1, etc.) bring fixes, but introduce more bugs. It's 1 step forward and 2 steps back. It's hard to believe something could be worse than that, but there is: the way bug reports are handled on the developercommunity site; answers like: "we don't have time to look into this now, so we're closing it". I don't see why any genuine/serious report should be closed because of "low priority", either say "we're never doing this", or keep it open. Many of the canned responses by MS come off as rude. It seems like there are too many new features put into a minor point release, and they clearly are not tested enough.
You were ahead of your time on your blog post about this. You should share it with whoever is running VS. Most people I know miss how candid Brian Harry was sometimes when he blogged about the state of VS, and we wish someone at MS would just come out and state what's going on.
Comments are closed.