[HN Gopher] How small is the smallest .NET Hello World binary?
       ___________________________________________________________________
        
       How small is the smallest .NET Hello World binary?
        
       Author : susam
       Score  : 327 points
       Date   : 2023-07-09 08:50 UTC (14 hours ago)
        
 (HTM) web link (blog.washi.dev)
 (TXT) w3m dump (blog.washi.dev)
        
       | mrlonglong wrote:
       | As it happens, I was playing around with martypc and msdos 6.22,
       | and installed TurboPascal v1 on it. I built a hello world com
       | that was just under 1k.
        
       | msephton wrote:
       | Very cool. I love stuff like this. I remember a Snake game being
       | made in 8KB of .NET
        
       | userbinator wrote:
       | _As every section needs to be aligned to the smallest possible
       | section alignment of 0x200 bytes (1KB), we inflate our file by at
       | least that amount of bytes just because of that._
       | 
       | 0x200 is 512, or 0.5K. It's been a long time since I've done PE
       | size optimisation at this level but I remember 512 was the
       | minimum accepted by Windows 9x but NT could accept alignments as
       | small as 1.
       | 
       | Also, I didn't see any mention of the old trick of overlapping
       | the DOS MZ and PE headers, which was state-of-the-art when I was
       | still doing this stuff: http://www.phreedom.org/research/tinype/
       | 
       | ...and then you realise that the demoscene has managed to do this
       | in a 1k binary:
       | 
       | https://www.pouet.net/prodlist.php?type%5B%5D=1k&platform%5B...
        
       | numlock86 wrote:
       | Hear me out: Create a runtime that prints Hello World without any
       | input given and you could go as low as 0 bytes for your "binary"!
       | (And yes, I am aware this already exists.)
        
         | Karellen wrote:
         | You'd still need a file header (or at least a magic number[0])
         | that the OS will recognise in order to launch the runtime with
         | your binary. e.g. you'll need an initial "#!" or "\x7fELF" or
         | "MZ" to have your "binary" even start to be run as an
         | interpreted program, ELF, or PE binary respectively.
         | 
         | [0] https://en.wikipedia.org/wiki/File_format#Magic_number
        
       | Rochus wrote:
       | Just tried with my Oberon+ IDE and get a Hello.dll of 2048 bytes
       | for this module:                 module Hello       begin
       | println("hello world")       end Hello
       | 
       | It uses the https://github.com/rochus-keller/Pelib assembly
       | generator.
        
         | analognoise wrote:
         | Nicklaus Wirth was right. I felt that the first time I used
         | Lazarus/FreePascal, and felt it to be true the more of his
         | works I read.
         | 
         | Recently though, I think maybe the way to get off the "cycle of
         | reincarnation" for this type of thinking is to do WASM - but I
         | cannot abandon hardware like that. I am mentally incapable of
         | accepting a spiritual machine that I cannot break with a hammer
         | upon my desk; [I am] too primitive; an indestructible global
         | computer that runs on other people's hardware for thousands of
         | dollars of compute time and is slower than an RPi (like
         | Ethereum) is anathema to me in some fundamental way.
         | 
         | I still want to get that (Wirth was right) on a shirt with his
         | face on it.
        
           | Rochus wrote:
           | > _Nicklaus Wirth was right_
           | 
           | In what respect in this context? I should have explicitly
           | stated that "Hello.dll" is a .NET assembly, i.e. Oberon+ uses
           | the Mono CLR to run and debug the Oberon+ code (but not the
           | .NET framework); it can also generate C99 (as a substitute
           | for AOT compilation), but here it is about the minimal size
           | of a Hello World .NET binary.
           | 
           | WASM and especially the WAMR runtime might be a good
           | alternative to the Mono CLR in future, but today it's too
           | slow and only a few architectures are supported (much less
           | than Mono).
        
         | cyptus wrote:
         | is the file filled with 0 bytes at the end? I think there are
         | options to disable this
        
           | Rochus wrote:
           | Yes, it has about 700 zero bytes at the end; might be just
           | some overhead due to the PE format; didn't have a close look,
           | was just curious how big it is out of the box after skimming
           | the article. Anyway the size of the DLL itself is irrelevant
           | compared to the binaries required to run it (Mono +
           | mscorlib.dll ~ 10 MB in case of Oberon+).
        
       | valevk wrote:
       | How about .net core vs framework?
        
         | MarkSweep wrote:
         | I have some comparisons here:
         | 
         | https://github.com/AustinWise/SmallestDotnetHelloWorlds
        
         | ComputerGuru wrote:
         | Here's a story about producing the smallest possible with .NET
         | Core [0].
         | 
         | [0]: https://medium.com/@MStrehovsky/building-a-self-contained-
         | ga...
        
       | mihaaly wrote:
       | > Even though this is probably quite a useless project
       | 
       | No. It taught me quite some.
        
       | password4321 wrote:
       | This "minimum viable program" stuff is great!
       | 
       | Michal Strehovsky used the framework currently known as .NET (as
       | opposed to the Framework _formerly_ known as .NET per OP) to
       | create a snake game in under 8KB with no .NET [nor .NET
       | Framework] runtime dependency. (Medium sucks and Twitter isn 't
       | useful right now, but I believe ~"hello world" supported Windows
       | 3.11.)
       | 
       | https://medium.com/@MStrehovsky/building-a-self-contained-ga...
       | [https://web.archive.org/web/20200103110836/https://medium.co...
       | | https://archive.ph/b6qXE]
       | 
       | https://news.ycombinator.com/item?id=22010159 (2020)
       | 
       | https://news.ycombinator.com/item?id=22104734
       | 
       | You can see the latest work (and sponsorship!) at
       | https://flattened.net: "bflat - C# as you know it but with Go-
       | inspired tooling".
        
         | WhereIsTheTruth wrote:
         | Michal and the whole CoreRT team saved C# to me, without
         | NativeAOT today, I'd never ever consider using C#, specially
         | when Go exist
        
         | dgellow wrote:
         | To continue on the "Go-inspired" C#, .Net has
         | System.Threading.Channels!
         | 
         | https://learn.microsoft.com/en-us/dotnet/core/extensions/cha...
        
           | FroshKiller wrote:
           | I love channels in .NET. I use them in our .NET web services
           | to pass work items from controllers to background services
           | without having to directly couple the controller & background
           | service classes.
        
             | KRAKRISMOTT wrote:
             | Why didn't Microsoft implement the arrow syntax for
             | channels? It makes it much less confusing than passing it
             | as parameters.
        
             | peheje wrote:
             | How do you do this? Does the controller then not know what
             | the receiver is or wants in any way? Surely the controller
             | needs to put something on the channel in the correct
             | format?
        
               | pjc50 wrote:
               | The channels are typed: https://learn.microsoft.com/en-
               | us/dotnet/api/system.threadin...
        
             | hypeatei wrote:
             | The widely used MediatR library[0] could be used to do that
             | as well, just FYI.
             | 
             | [0]: https://github.com/jbogard/MediatR
        
               | neonsunset wrote:
               | For the love of God, do not use MediatR. Especially in
               | scenarios Channels are designed for. It is mentally
               | confusing and bloated abstraction, in most places simply
               | resulting in more code achieving the same result at
               | 0.25x(best case) speed.
        
               | gokhan wrote:
               | It's not for the speed but separating parts of the same
               | monolithic program into a microservice like structure.
        
               | neonsunset wrote:
               | This one, that is the problem. It makes it impossible to
               | logically follow (without reviewing all type references)
               | the flow of execution - just call a service directly
               | instead of inventing 3 contexts and 2 intermediate
               | message handlers that end up just passing the data to one
               | consumer at the end.
               | 
               | The fact that this does not raise eyebrows is one of the
               | (avoidable) reasons, among many, why C# gets bad rep. Go
               | solves this by the virtue of its design that makes it
               | painful to invent big brain solutions that should have
               | stayed simple. This is where C# has much longer learning
               | curve and requires judgement when applying the tools at
               | hand.
        
         | andai wrote:
         | >Can C# apps hit the sizes where users would consider the
         | download times instant? Would it enable C# to be used in places
         | where it isn't used right now?
         | 
         | I wonder if this could be used to make C# webassembly more
         | viable. If I remember, a significant part of the download size
         | is the .NET libraries (themselves written in C#?)
         | 
         | On that note, is there any 2D game framework/engine that
         | targets the browser that uses C# and produces small binaries?
        
           | ComputerGuru wrote:
           | > I wonder if this could be used to make C# webassembly more
           | viable.
           | 
           | You might enjoy reading this GitHub thread [0] where the
           | community contributed a WASM library wrapping the C# regex
           | code so that regex101.com could have a "C# mode". Lots of
           | nerd sniping about reducing the payload size.
           | 
           | (There's also another thread [1] discussing the minification
           | of a rust version of that same regex101 wasm library to
           | provide a "rust mode" using @burntsushi's regex crate.)
           | 
           | [0]: https://github.com/firasdib/Regex101/issues/156
           | 
           | [1]: https://github.com/firasdib/Regex101/issues/1208
        
           | ramesh31 wrote:
           | >I wonder if this could be used to make C# webassembly more
           | viable. If I remember, a significant part of the download
           | size is the .NET libraries (themselves written in C#?)
           | 
           | Until WASM ships GC, you're stuck with having to download an
           | entire runtime regardless.
           | 
           | >On that note, is there any 2D game framework/engine that
           | targets the browser that uses C# and produces small binaries?
           | 
           | Godot supports C# scripting and WASM compilation, probably
           | your best bet.
        
             | andai wrote:
             | Yeah, I was wondering if the techniques described in these
             | articles could reduce the size of the .NET libs in the wasm
             | bundle. (I'm not sure what language the runtime itself is
             | implemented in, I think also mostly C# for (formerly known
             | as) .NET Core?)
             | 
             | Thanks for the Godot tip. I haven't checked it out in a
             | while. I found it very counterintuitive (and a 3D engine is
             | definitely overkill for small 2D games... I basically just
             | want Flash... I guess Phaser or Haxe are my best bets).
        
               | sedatesteak wrote:
               | Godot was originally 2d (only?). At any rate the 2d
               | portion is much more mature than the 3d.
        
       | kazinator wrote:
       | Yes, it was a dumb way to spend a Saturday. You have to count the
       | size of that ".NET Framework 4.x.x", against which the reductions
       | in the hello program are insignificant.
        
       | neonsunset wrote:
       | This article will confuse people since the executable in question
       | requires a runtime installed on the system (in fact, it uses .NET
       | Framework 4.7.2, in 2023!). Most likely fair comparison for such
       | solution would be .jar or even .py files.
       | 
       | A good source of truth for an actual executable would be Native
       | AOT binary, which can run on Windows, Linux or macOS. Naturally,
       | it will be much larger, having to incorporate GC, possibly
       | ThreadPool, Console and other auxiliary code.
        
         | ramesh31 wrote:
         | >This article will confuse people since the executable in
         | question requires a runtime installed on the system (in fact,
         | it uses .NET Framework 4.7.2, in 2023!).
         | 
         | That's fair though. The post is titled "the smallest .NET hello
         | world binary", not "the smallest C# hello world binary"
        
         | brookst wrote:
         | Do we care about "fairness" or "reality"? Since it's impossible
         | to have a modern Windows install without .net, and Linux and
         | MacOS don't have it by default, it seems an odd way to be
         | "fair"
        
           | deely3 wrote:
           | > Windows install without .net
           | 
           | correction: without some specific version of .net.
        
             | ptspts wrote:
             | Is there backwards compatibility between .NET Framework
             | versions? Which version number should I request in my
             | ultraportable executable? (Java seems to work fine most of
             | the time eith.some javac flags.)
        
               | password4321 wrote:
               | > _Is there backwards compatibility between .NET
               | Framework versions?_
               | 
               | In theory: yes. In practice: mostly. There can only be
               | one .NET Framework 4 installed at a time; the recent
               | changes are found at https://learn.microsoft.com/en-
               | us/dotnet/framework/whats-new.
               | 
               | > _Which version number should I request in my
               | ultraportable executable?_
               | 
               | This gets tricky if you want to support more than
               | Microsoft does. Here is the details on recent .NET
               | Framework versions per OS:
               | https://learn.microsoft.com/en-
               | us/dotnet/framework/install/g... and ancient of days:
               | https://learn.microsoft.com/en-
               | us/archive/blogs/astebner/mai....
               | 
               | For example: Microsoft currently supports Windows 10+,
               | which first included .NET Framework 4.6. However,
               | Microsoft only currently supports v4.6.2+, and v4.8 has
               | been "Windows Update"'d since May 2019. I personally
               | bumped an old open source project from v3.5 to v4.8
               | recently because of how hard it was for myself as a
               | returning contributor to build these days.
        
         | weinzierl wrote:
         | A comparison between a fully statically linked native binary
         | and one that loads some or all libraries dynamically would
         | equally be vastly unfair.
         | 
         | I think comparing container images would be a good start. Use
         | the most minimal base image you can get away with, or even _"
         | FROM scratch"_ if possible, but compare only images with the
         | same architecture. I'd prefer 32-bit, to take things like
         | running 32-bit on 64-bit or 64-bit with pointer compression out
         | of the picture.
         | 
         | Then compare the size of the uncompressed exported tar.
         | Probably also not completely fair, depending on what question
         | you want to answer but it takes the obvious variances out of
         | the equation.
         | 
         | EDIT: Thinking about it some more, it might even be more fair
         | to compare maximally compressed image size to account for
         | compression within the container. Of course you'd have to
         | compress with the same algorithm and parameters or just add the
         | decompressors size to the final result like they do in
         | compression benchmarks.
        
           | whartung wrote:
           | It's funny because back in the day these were these kind of
           | discussion points thrown around in the Lisp forums when folks
           | would balk at "executable sizes".
           | 
           | Specifically about how C leverages the "free" runtime of the
           | OS while Lisp has to bundle is own.
           | 
           | This idea of container runtime sizes would be an amusing
           | thread. A good test of something like Nix or Guix to a very
           | fine grain. "We don't need bash or ls so we removed it. "
           | "The VM only simulate a single network card, so we'll scrap
           | all the other driver. "
        
             | kazinator wrote:
             | C doesn't leverage the free run time of the OS, when that
             | OS is Windows. Your hello.exe size has to include the run-
             | time DLL's required for it from the C implementation that
             | was used to build it.
             | 
             | Windows has only recently been moving toward providing a
             | public run-time for C; the "Universal C Run-Time" (UCRT)
             | that has been introduced in Windows 10.
        
               | ziml77 wrote:
               | For some reason, even with the UCRT, binaries seem overly
               | bloated. I've been toying with Zig over the past couple
               | weeks and I really like that it doesn't rely on a C
               | runtime on Windows. Plus it makes it easy to operate
               | entirely with UTF-16 strings, which is a nice to have
               | when I'm writing a utility that is specific to Windows.
        
               | mananaysiempre wrote:
               | > C doesn't leverage the free run time of the OS, when
               | that OS is Windows.
               | 
               | Stdio, no. Printf, no (unless you're an adherent of
               | LIBCTINY[1] or one of its descendants[2] and need only a
               | minimal set of features). Malloc, yes--MSVC's malloc() on
               | Win32 has always been a thin wrapper around
               | kernel32!HeapAlloc. Strlen or memcpy or whatnot--also
               | possibly yes, but it's not like minimal implementations
               | of those are large anyway.
               | 
               | [1] http://bytepointer.com/resources/pietrek_libctiny_199
               | 6.htm
               | 
               | [2] https://www.benshoof.org/blog/minicrt
        
               | ChrisSD wrote:
               | The ucrt is included as far back as Windows 7 (if you
               | install updates).
        
               | kazinator wrote:
               | The increment in disk space usage caused by the update
               | has to count toward the effective size of the hello
               | program, experienced by the user of that installation.
        
         | foepys wrote:
         | .NET Framework is still officially supported and targeting
         | anything higher than 4.7.2 is unnecessary since there are no
         | new APIs in 4.8. 4.8 is just a drop-in replacement for 4.7.2
         | with things like better high DPI support.
        
           | Deukhoofd wrote:
           | How about any of the modern .NET versions? They're on .NET
           | 7.0.8 nowadays.
        
             | TrueSlacker0 wrote:
             | .net core is at 7. This article is about. Net framework
             | which stopped at 4.8
        
               | Deukhoofd wrote:
               | .NET Core stopped being called .NET Core at version 3,
               | after which it was renamed .NET, and Microsoft announced
               | it was meant to supersede the old legacy .NET Framework.
               | The article opens with asking itself how to get the
               | smallest .NET executable, and then for some reason limits
               | itself to this legacy version.
        
               | Kwpolska wrote:
               | .NET Framework 4.x is built into Windows, and .NET
               | Framework 4.x binaries are understood by the Windows
               | executable loader. The modern .NET must be manually
               | installed and the executables must take care of launching
               | the runtime on their own.
        
               | marcosdumay wrote:
               | > Microsoft announced it was meant to supersede the old
               | legacy .NET Framework
               | 
               | What actually never happened, to nobody's surprise.
               | 
               | So now we have .Net that was renamed into .Net Framework
               | that is legacy, .Net Core that is legacy but compatible
               | with the modern version, and .Net that is current.
               | Anyway, the platform never stopped being called .Net,
               | because it's larger than just the runtime.
               | 
               | We also have 2 different number sequences starting from
               | 1, and one starting from... some times 4, other times 6,
               | depends on your point of view.
               | 
               | We also have a bunch of confused people without any
               | reason, because all of this is as clear as water. But
               | anyway, it's not the author fault that he didn't
               | communicate the version in an adequate way.
        
               | orphea wrote:
               | > .Net that was renamed into .Net Framework
               | 
               | This is not correct. .NET Framework was named Framework
               | from 1.0. The only time something was renamed is .NET 5
               | which came after .NET Core 3.1.
               | 
               | > We also have a bunch of confused people without any
               | reason, because all of this is as clear as water.
               | 
               | It's funny you say that. Do you consider yourself one of
               | those confused people? :)
        
               | borissk wrote:
               | Haha, the burn.
               | 
               | I remember back in the day - around 2001 - Microsoft
               | thought it will center all their products around Web
               | Services and call them .NET. Windows .NET Server was the
               | supposed name for Server 2003. In the end a few things
               | came out of it: Visual Studio .NET, .NET (the framework),
               | VB.NET, ASP.NET.
               | 
               | https://en.wikipedia.org/wiki/Microsoft_.NET_strategy
        
               | marcosdumay wrote:
               | > Do you consider yourself one of those confused people?
               | 
               | Yes. I have never took place in a conversation about
               | versioning problems in .Net where each person wasn't
               | talking about completely different things.
               | 
               | Anyway, I clearly remember nobody ever naming anything
               | "framework" until the second or third stable version. And
               | if there was such a thing, I would probably have heard,
               | because MS was incredibly loud at the time.
               | 
               | It was retroactively renamed after it.
        
               | belinder wrote:
               | It didn't start from 4 but 5. they skipped 4 because
               | people would confuse it for .net framework
        
               | foepys wrote:
               | > What actually never happened, to nobody's surprise.
               | 
               | Unless they provide feature parity, it will never happen.
               | 
               | A working WinForms designer for third-party controls
               | (read: any control not provided by the framework itself
               | or NDA-ed vendors) in Visual Studio would be nice, for
               | example.
        
         | ComputerGuru wrote:
         | Not necessarily - the actual output exe _doesn 't actually use_
         | the .NET framework for anything at all, besides invoking the
         | main. The actual logic of outputting the bytes to the console
         | is done via a pinvoke (c# ffi) call to the underlying unmanaged
         | (non-.net) code exposed by ucrt.dll
         | 
         | But anyway, this other article [0] (shared in another comment
         | here) about creating the smallest version of Snake (the game)
         | was done on top of .NET Core (the new version of .NET that _isn
         | 't_ bundled with Windows) and effectively does the same thing,
         | but actually produces a non-.NET binary, if that's more to your
         | taste.
         | 
         | [0]: https://medium.com/@MStrehovsky/building-a-self-contained-
         | ga...
        
         | sbjs wrote:
         | Closer analogy would be .pyc files. Precompiled bytecode.
        
         | bombcar wrote:
         | My hello world requires the "bash" framework and is zero bytes.
         | You call it via:                   echo "hello world"
        
           | [deleted]
        
           | stickfigure wrote:
           | > echo "hello world"
           | 
           | I count 18 bytes in that program. Too long!
           | 
           | I left a definitive answer on Quora some years ago:
           | 
           | https://www.quora.com/What-programming-language-has-the-
           | shor...
        
             | [deleted]
        
             | sacrosancty wrote:
             | Those 18 bytes aren't the binary, they're just the command
             | to call the program. Don't count it just as you wouldn't
             | count the length of the filename and its arguments for a
             | regular executable.
        
               | cvoss wrote:
               | If you insist on categorizing `echo "hello world"` as the
               | invocation, rather than the program, then what is the
               | program under your categorization? I'll submit that it is
               | the `echo` binary, which is a whopping 35kB on my
               | machine.
               | 
               | I'll just observe that there is no way to compress "hello
               | world" below a certain size (definitely not to size 0).
               | If you think you have, you've just "moved" it into, say,
               | the framework/os/input/algorithm/etc.
               | 
               | But this fun little debate we're having here is actually
               | connected to some deep theoretical questions, like
               | Kolmogorov complexity, its invariance theorem, and
               | applications to the concept of data compression [0].
               | 
               | [0] https://en.wikipedia.org/wiki/Kolmogorov_complexity
        
           | bdhcuidbebe wrote:
           | I'm intrigued, please share your 100% text compression rate
           | algorithm!
        
             | anoncow wrote:
             | Infinity compression.
        
               | osigurdson wrote:
               | I once ran gzip and infinite number of times on a text
               | file. I was surprised at the result.
        
             | bombcar wrote:
             | Just make sure you have a filesystem that can take
             | arbitrary-length filenames! Then you can compress to zero
             | byte file with a very long filename ;)
        
               | bdhcuidbebe wrote:
               | I guess if you store the filetable in unlimited S3
               | storage, maybe ;-)
        
               | reilly3000 wrote:
               | TXT records in Route 53 for 100% SLA
        
           | AdieuToLogic wrote:
           | How about this, assuming there must be a stand-alone
           | "binary":                 cd /tmp       echo 'echo $0' >
           | hello\ world       chmod +x hello\ world       PATH=. hello\
           | world
        
           | osigurdson wrote:
           | Are you sure that is zero bytes?
        
           | mkleczek wrote:
           | I am afraid it requires "echo" runtime environment, doesn't
           | it?
        
             | tehsauce wrote:
             | yes but it's very easy to install if you have npm
        
               | AdieuToLogic wrote:
               | That almost launched a sip of morning coffee... Well
               | played!
        
             | hnfong wrote:
             | echo is actually a bash builtin, so, not really, unless
             | you're using another shell that doesn't have builtin echo.
        
               | virtue3 wrote:
               | whoosh...
        
           | nighmi wrote:
           | You can skip "echo" and let the error message output "hello
           | world" along with extra text!
        
         | why_only_15 wrote:
         | On MacOS at least _all_ binaries are dynamic (except dyld
         | itself) so I think this is fair. This is because everything is
         | supposed to link I believe some kind of runtime dylib instead
         | of doing e.g. syscalls. This includes anything written in C,
         | for example. Size of binary with dynamic links is the most fair
         | comparison between anything running not on Linux.
        
           | grishka wrote:
           | According to Apple you are supposed to link to
           | libSystem.dylib for syscalls, but there's obviously nothing
           | stopping you from calling into the kernel directly.
        
             | msla wrote:
             | > According to Apple you are supposed to link to
             | libSystem.dylib for syscalls, but there's obviously nothing
             | stopping you from calling into the kernel directly.
             | 
             | As a matter of OS design, this is no longer obvious:
             | 
             | https://lwn.net/Articles/806776/
             | 
             | > A new mechanism to help thwart return-oriented
             | programming (ROP) and similar attacks has recently been
             | added to the OpenBSD kernel. It will block system calls
             | that are not made via the C library (libc) system-call
             | wrappers.
             | 
             | MacOS doesn't implement that, sure, but it could.
        
         | pjmlp wrote:
         | Microsoft themselves just released a new product based on .NET
         | Framework 4.7.2, so don't be that hard on the authors.
         | 
         | https://www.infoq.com/news/2023/06/logic-apps-custom-code/
        
           | aardvarkr wrote:
           | That's a logic app plugging that allows for compatibility
           | with framework code. M$ is a behemoth that supports old
           | legacy code from businesses that don't want to upgrade so I'm
           | not surprised that they're making things like logic apps
           | compatible but this isn't really a "new product based on
           | framework".
        
         | Kwpolska wrote:
         | The .NET Framework is built into Windows, and a mandatory
         | component of it since at least Vista. .NET Framework 4.7.2
         | specifically should be built-in on supported Windows versions.
        
           | TillE wrote:
           | I'm surprised Microsoft still isn't pushing .NET 6 (and the
           | MSVC runtime, for that matter) to everyone with Windows
           | Update. They're not very large, most consumers will want
           | them, and picky enterprises could opt out.
           | 
           | It's an odd annoyance that Windows developers have to deal
           | with.
        
             | electroly wrote:
             | Getting your thing adopted as a Windows component and
             | distributed by Windows Update is a common trap for
             | Microsoft developers. It's always a mistake if you're in
             | some DevDiv or random app team instead of the Windows team.
             | Windows is the slowest-moving product that Microsoft has,
             | and users don't install updates. Hitching your wagon to
             | Windows means no two users will have the same version
             | installed, and getting people to update .NET via Windows
             | Update is a nightmare. God forbid the Windows team decides
             | to stop supporting a version of Windows that is still
             | commonly use in industry; they'll never get a .NET update
             | again.
             | 
             | As a user, think about how annoying it is to get a message
             | saying you need to _run Windows Update_ before you can
             | start an application. Totally unnecessary own-goal for the
             | team that decided to ship their independent component in
             | Windows Update.
             | 
             | It's way easier to either 1.) go self-contained, or 2.) use
             | the on-the-fly .NET download for a framework-dependent
             | build. I absolutely think they made the right call removing
             | current .NET as a Windows component. The annoyance was far
             | greater when .NET Framework was part of Windows.
        
             | pjmlp wrote:
             | Because the modern way is to ship it with the application
             | and preferably use trimming.
        
             | Uvix wrote:
             | They push updates to .NET if it's installed. I can
             | understand them not installing if it's absent, because
             | there's no good story for what to do when that version goes
             | out of support. If they leave it, then customers have
             | insecure unsupported software on their systems; if they
             | remove it, they'll break apps that depend on it.
        
             | formerly_proven wrote:
             | Congruent with runtimes (both .NET and native) for
             | Microsoft languages always being kinda weird. "Uh yeah
             | don't use that CRT in %WINDIR%, that's not supported!
             | Everyone need to bring their own... but not as loose DLLs,
             | that's not allowed! Use the MSI package and install it
             | whenever!" (I think most of those restrictions have been
             | removed in the last couple years, and MS also settled down
             | on VC++ ABI and VCRT changes and introduced UCRT in Windows
             | 10, so)
        
           | ad404b8a372f2b9 wrote:
           | You never have the right version installed though. Every time
           | I've tried to install a .NET program it's always asked me to
           | install a different version than the one I had.
        
             | CrendKing wrote:
             | Sounds like confirmation bias, because when the right
             | version of .NET is ever used, you probably never knew it
             | was a .NET program to begin with.
        
               | ntdll wrote:
               | Not true in my experience. The "look and feel" of the
               | program usually gives it away more or less immediately.
        
               | grujicd wrote:
               | Probably, if that app uses WPF, which is "self-drawn" GUI
               | library. However, if .Net app uses WinForms, that API is
               | just a wrapper over standard Win32 controls and it looks
               | like any other old school Win app.
        
               | ntdll wrote:
               | You're right, but there are a few subtle differences here
               | and there that often make Windows Forms recognizable.
               | 
               | The best example I got off the top of my head is KeePass
               | v1 [0] and KeePass v2 [1]. v1 is written in C++ with
               | native controls, and v2 uses Windows Forms.
               | 
               | If you look at the menu bar and the toolbar, you'll see a
               | difference. Most notably the drag handle on the left, and
               | the search box on the right, in v2. The difference is
               | often a bit easier to spot on Windows 7.
               | 
               | [0]: https://keepass.info/screenshots/main_big.png [1]:
               | https://keepass.info/screenshots/keepass_2x/main_big.png
        
               | userbinator wrote:
               | The blurrier font in the non-native one is another
               | difference.
        
               | EMM_386 wrote:
               | > The "look and feel" of the program usually gives it
               | away more or less immediately.
               | 
               | If you are talking about the base controls, then maybe.
               | But there are .Net cross-platform frameworks such as
               | Avalonia that can get you a modern loooking UI with
               | theming.
               | 
               | https://github.com/irihitech/Semi.Avalonia
               | 
               | https://github.com/AvaloniaUI/Citrus.Avalonia
               | 
               | etc.
        
         | H8crilA wrote:
         | I think this will miss the point though. You can consider the
         | specific .NET runtime as its own compute platform / operating
         | system, regardless of what has to be installed on the machine
         | for the program to actually run, and explore the limits of it.
         | It will teach you a great deal about the binary format of the
         | programs.
         | 
         | You can do this for any format that stores executables, for any
         | programming framework. .jar might be interesting, .py obviously
         | isn't. You can do this for obscure old formats, or for
         | something very common like the standard Linux .elf.
        
           | tempodox wrote:
           | I disagree. I cross-compile F# sources with .NET on my Mac to
           | get a stand-alone Linux executable without additional
           | dependencies that I can upload to my server host. It's enough
           | that I maintain the .NET stuff on my development host, I've
           | got no desire to duplicate that work on the public host.
        
             | H8crilA wrote:
             | What do you disagree with?
        
               | tempodox wrote:
               | Considering the runtime as something separate from a
               | single program. I want both in the same binary.
        
               | H8crilA wrote:
               | It's not about considering this or that, but about making
               | a fun little exercise that tells you how the binary
               | format works.
        
           | BiteCode_dev wrote:
           | > .py obviously isn't
           | 
           | Although .zipapp is to Python what .jar is to .py. Probably
           | even closer to .war.
           | 
           | But for purely stand alone stuff, "nuitka3 /tmp/hello.py
           | --standalone" does output a executable that can be used
           | without the user to manually bring in the Python run time. In
           | that case, the hello world is about 16Mb on Ubuntu.
           | 
           | It would be interesting to do this with MicroPython though.
        
       | markus_zhang wrote:
       | What can we do if we use C or x64 assembly?
        
         | Eiim wrote:
         | Something like this?
         | https://codegolf.stackexchange.com/questions/55422/hello-wor...
        
       | p0w3n3d wrote:
       | Congratulations. You've just dropped unicode support, and made a
       | C call to puts. Wouldn't be easier to compile it using C
       | compiler?
       | 
       | thats_just_c_with_extra_steps.jpg
        
         | thebears5454 wrote:
         | Yeah skipping compiling kinda really ruins it
        
       | DeathArrow wrote:
       | Meanwhile it seems to be possible to write a Hello World program
       | for DOS using just 20 bytes:
       | 
       | https://www.gnostice.com/nl_article.asp?id=225&t=The_Smalles...
        
         | userbinator wrote:
         | It's possible in 7 bytes + length of string (including
         | terminator). "xchg ax, bp" will save 1 byte over what is in
         | that article.
        
       | hliyan wrote:
       | On a side note, did anyone notice that the author's location is
       | McMurdo Station, Antarctica? I didn't think they would have
       | someone of their skillset at that station:
       | https://usscar.org/directory?combine=&field_usscar_nsf_progr...
       | 
       | Could be a fascinating story.
        
         | roamingryan wrote:
         | I've been down to McMurdo for a research trip. A couple of
         | things that outsiders find surprising is that most of the
         | people there are support staff and only a small fraction (maybe
         | 20% or so) are the scientists. The support staff are basically
         | people from all walks of life... fireman, carpenters, cooks,
         | mechanics, etc. Basically everyone you need to make a modern
         | city run. However, because of the selection processes, those
         | folks are almost all extremely talented and at the top of their
         | specialties. The number of "swiss army knife" individuals I met
         | with very broad skill sets was astounding. The challenges
         | associated with living and working down there tend to draw
         | quirky and motivated individuals like bugs to a light. And many
         | return year after year. It's a wonderful community.
        
         | spamtarget wrote:
         | This case I don't understand the sentence: "This was a dumb way
         | to spend my Saturday." What else you could do there?
        
         | Metacelsus wrote:
         | Check out https://brr.fyi/
        
         | tester756 wrote:
         | How did you find it?
         | 
         | I don't see it in .net meta data nor about
        
           | hliyan wrote:
           | Github profile.
        
       | junon wrote:
       | I remember when the entirety of your C# binary would include the
       | source code, and back before the concept of Release or Debug was
       | prolific enough, closed source C# binaries would be distributed
       | in Debug mode erroneously, allowing someone to extract pretty
       | much a 1:1 replica of the codebase.
       | 
       | Just a fun anecdote. I don't know if this is the case anymore.
        
         | daigoba66 wrote:
         | You're probably thinking of debug symbols, i.e. the PDB file
         | that is generated alongside the binary. You can generate this
         | for both Debug and Release builds (should be on by default in
         | fact). It's super useful for debugging crash dumps from
         | production, and for exception logging in web apps.
         | 
         | You can think of this like a source map, but only files and
         | line numbers.
        
         | nazgulsenpai wrote:
         | At least according to the format and specification since VS
         | 2005, .NET 2.0, the assembly format has been consistent and
         | doesn't have any section for source code.
         | 
         | It has always been trivial to load an assembly in something
         | like dnSpy or another IL Disassembler and generate C# code and
         | patch .NET assemblies. At least in the versions < 5.
         | 
         | https://learn.microsoft.com/en-us/dotnet/standard/assembly/f...
        
         | moron4hire wrote:
         | There was never a time when the distinction between Release and
         | Debug was not "prolific".
        
           | junon wrote:
           | Yes, there definitely was.
        
             | moron4hire wrote:
             | Maybe if you started life as a VB6 developer.
             | 
             | But your original post especially doesn't make sense
             | because the default Debug configuration has always put the
             | debug symbols in a separate PDB file. So even if you were
             | arguing that early C# developers didn't understand how to
             | use their tools, they wouldn't have had the source embedded
             | in the EXE.
        
               | junon wrote:
               | I guess I'm lying then. :)
        
               | masterofmisc wrote:
               | I think you may be miss-remembering.
        
         | majikandy wrote:
         | I remember this too. Actually I (maybe incorrectly)think
         | release builds contained it too. You used to have to actively
         | obfuscate to make sure it was protected. I definitely used
         | decompilers and got great results with very readable code that
         | felt very close to the original.
        
           | junon wrote:
           | That could be, actually. I don't know if it was release or
           | just debug, but I remember obfuscation tools definitely being
           | a thing, in large part because of this.
        
           | orphea wrote:
           | MSIL is easily decompiled back to C# (or VB.NET if you
           | prefer), it has always been a thing, yes. But binaries never
           | included the source code.
        
             | junon wrote:
             | I am perfectly aware of what IL decompilation is. I've
             | written a decompiler before before. That's not what was
             | happening.
        
         | landr0id wrote:
         | I don't think it ever included actual source code, but .NET IL
         | is just so rich that it made it extremely trivial to decompile,
         | no?
        
           | junon wrote:
           | It indeed did, you could open up the .exe in notepad for
           | example. It was all there.
        
             | orphea wrote:
             | This doesn't sound right. C# has always been a compiled
             | language (to MSIL), from the earliest versions of .NET.
        
               | junon wrote:
               | Never said the source was used to run the program. It was
               | included for debug builds.
        
               | orphea wrote:
               | Sorry - I wasn't able to convey my thought. There was no
               | reason for the C# compiler to include the source code
               | into the binary. Into the debugging symbols (which is a
               | separate file) - maybe..?
               | 
               | Anyway, I went ahead and compiled a debug app on .NET
               | 1.1. The binary does not include the source code. And
               | neither does the PDB. It includes a file path to the
               | source though ("Visual Studio
               | Projects\ConsoleApplication1\Class1.cs").
        
               | junon wrote:
               | Back then things were different. I'm talking c. 2008.
        
               | orphea wrote:
               | .NET 1.1 is 2003, should be "back then" enough.
               | 
               | When compiled with VS 2008 and .NET 3.5, the binary does
               | not include the source code either.
               | 
               | You might confuse readable parts in the binary with the
               | metadata.
               | 
               | [0] https://stackoverflow.com/q/65699183
               | 
               | [1] https://stackoverflow.com/q/4700317
        
               | junon wrote:
               | Nope. I explicitly remember the source code being there.
        
               | orphea wrote:
               | The cool thing about being in 2023 is that you don't have
               | to believe me. Winding up a Windows XP virtual machine
               | with Visual Studio of your choice takes 15-20 minutes.
        
               | xen0 wrote:
               | But that doesn't mean the source code wouldn't
               | necessarily be exuded in a debug build.
               | 
               | I don't know the truth of the original statement, but it
               | isn't too unbelievable.
        
               | pjerem wrote:
               | This does sound unsurprising if you talk about a debug
               | binary. It would allows you to debug it with the original
               | source code. You are not supposed to run debug builds in
               | production. But nothing will prevent you from doing it.
               | 
               | Production binaries of course doesn't embed any source or
               | debugging symbols.
               | 
               | It's like erroneously shipping source maps files into
               | your front end production build. It shouldn't happen and
               | it's not necessary, but it's just one configuration
               | variable away so it happens a lot.
        
               | sacrosancty wrote:
               | [dead]
        
             | [deleted]
        
         | magicalhippo wrote:
         | I can't recall this from C#, though I only started using it
         | when .Net 1.1 was released.
         | 
         | I do recall good old Visual Basic did this though, as it ran
         | interpreted. The executable it generated was just a small
         | loader with the source code appended.
        
       | nathell wrote:
       | Related:
       | 
       | A Whirlwind Tutorial on Creating Really Teensy ELF Executables
       | for Linux:
       | http://www.muppetlabs.com/~breadbox/software/tiny/teensy.htm...
       | 
       | Tiny ELF Files: Revisited in 2021:
       | https://nathanotterness.com/2021/10/tiny_elf_modernized.html
        
       | ledgerdev wrote:
       | This is far more relevant and exciting This is way more exciting.
       | https://twitter.com/MStrehovsky/status/1669502394827419648
       | 
       | > "A fully self-contained natively compiled C# Hello World,
       | including GC and everything can be as small as ~440 kB."
       | 
       | .net classic framework is windows deprecated legacy, and really
       | no new apps should be built on it.
        
         | ComputerGuru wrote:
         | The resulting exe doesn't really "use" the framework in any
         | way, other than relying on an implementation detail of the
         | backing ucrt.dll library exposed as an unmanaged pinvoke (c#
         | ffi) call.
        
         | TravHatesMe wrote:
         | Unless you're building an app relying on office/COM apis
        
           | pjmlp wrote:
           | Or a proper development experience for Windows Forms and WPF.
        
             | kcb wrote:
             | What's different about the development experience. In
             | Visual Studio it doesn't make much of a difference if
             | you're target framework or .Net 5+.
        
               | pjmlp wrote:
               | Designer is still buggy, the new out of process model
               | makes it incompatible with existing component libraries,
               | requiring a rewrite.
               | 
               | In many things, .NET Core/.NET 5+ is the Python 3 of .NET
               | ecosystem.
        
       | SillyUsername wrote:
       | Ok this is like saying let's make an interpreted Python program
       | run fast, knowing full well it's not intended for that. .Net's
       | intention by using bytecode is safety, sandboxing and
       | performance.
       | 
       | Why not teach the right tool for the job and illustrate it being
       | ported to C (or lower) if the aim is to make it smaller?
       | 
       | Can you imagine a mechanical engineer trying to turn a screw with
       | a pair of pliers with the justification that it's just a simple
       | screw and they want to see what the minimal number of turns is
       | whilst shaving the screw to improve grip? You'd think they were
       | nuts...
        
         | colejohnson66 wrote:
         | > .Net's intention by using bytecode is safety, sandboxing and
         | performance.
         | 
         | Not true. You can get all of that in C++ if you're careful and
         | enable compiler flags that no one uses. The entire reason for
         | the IL platform that .NET uses is for cross-platform
         | executables, just like Java.
         | 
         | .NET does give one safety in the form of bounds checking and
         | what-not, but that's the runtime, not the byte code. There's no
         | "bounds check" opcode; array dereferences are managed by the
         | runtime and bounds checks are elided if safety can be proven
         | (like Rust does).
        
         | colanderman wrote:
         | I actually just did this experiment yesterday with C. Smallest
         | "hello world" I could get from GCC on Linux is somewhere around
         | 14 KiB. (Lots of startup code and unnecessary ELF sections.) So
         | C# has a leg up here.
         | 
         | (Granted it's apples-to-oranges, given that this blog post is
         | for C# managed mode (as opposed to AoT).)
        
           | colanderman wrote:
           | Code-golfing some more, I could get it down to 448 bytes
           | (leaving out standard library, reimplementing syscall/2 by
           | hand, and convincing the linker to drop useless sections). So
           | C does win out here, but moreso by virtue of object format
           | than anything I think.
        
         | idlewords wrote:
         | Because the true aim of this exercise is to understand and
         | illustrate the various components of a .NET binary. It's like
         | tuning your Geo Metro to go as fast as possible; you do it to
         | learn about the inner workings, not to win a Formula 1 race.
        
       | creativenolo wrote:
       | How would this compare to a non-.Net binary?
        
         | _kbh_ wrote:
         | You can make super small hello world binaries if you try.
         | 
         | https://nathanotterness.com/2021/10/tiny_elf_modernized.html
         | 
         | has a 120 byte hello world program for x86_64 ELF.
        
         | Rochus wrote:
         | I just generated C99 with my Oberon+ example (see
         | https://news.ycombinator.com/item?id=36652878) and compiled it
         | with -O2. The generated Hello.c is 382 bytes, the Hello.h is
         | 240 bytes, and the compiled Hello.o is 1264 bytes (compared to
         | the 2048 bytes of the Hello.dll assembly). If I build a shared
         | library which includes the runtime and some boiler-plate stuff
         | the stripped version is 17760 bytes; if I instead build an
         | executable, the stripped size is 17952 bytes (compared to the
         | ~10 MB including mono and mscorlib.dll to run the assembly).
        
         | DeathArrow wrote:
         | I think the limitation here is mostly the PE format, not the
         | .Net framework.
        
       | makach wrote:
       | This was definitively not a dumb thing to spend time on. Diving
       | into the details of a binary gives deep insight into how things
       | are made and the concepts learned will enrich your knowledge. If
       | it is interesting and you learn something it is always worth your
       | time and effort.
        
       | Alifatisk wrote:
       | Love these, keep it up! I am glad I am not the only one
       | faschinated in producing lowest possible builds.
        
       | ClumsyPilot wrote:
       | i was hoping this is about 'real' baniraies, natively compiled
       | .net executables, like the .Net AOT
       | 
       | https://learn.microsoft.com/en-us/dotnet/core/deploying/nati...
        
         | bob1029 wrote:
         | You can get a .NET AOT example in a few megabytes now. Looks
         | like the most stripped down AOT compiled builds are about to
         | crack the 1mb barrier:
         | 
         | https://github.com/AustinWise/SmallestDotnetHelloWorlds
        
           | archargelod wrote:
           | But isn't a dotnet runtime a feature? AOT strips a main
           | feature of the language, while still doesn't even get close
           | to compiled languages ( hello world < 300kb (or < 100 kb
           | compressed))
        
             | bob1029 wrote:
             | Technically, the runtime is the thing you need if you
             | _didnt_ use [native] AOT. A big example being that you don
             | 't need to emit/interpret IL in an AOT binary.
             | 
             | I agree though, the minimal .NET example is still not close
             | to a full-featured .NET platform. I kind of like the idea
             | of opting-out of features though. Bringing the whole
             | runtime for the party each time is a bit overkill (unless
             | its already on every target machine). Lots of code doesn't
             | need reflection. Some special cases actively dislike GC,
             | etc.
        
             | Rapzid wrote:
             | That's why I love the ready to run option. It's
             | aggressively AOT compiled but maintains the ability to JIT
             | during runtime.
        
             | pjmlp wrote:
             | I am quite sure that Go as compiled language doesn't do a
             | 300 KB hello world (unless TinyGo is used), nor don't
             | plenty of others.
        
       | rcarmo wrote:
       | it would be interesting to compare this with non-Windows
       | platforms. A while back I did a similar (but much less in depth)
       | comparison of .NET Core 5 and 6:
       | https://taoofmac.com/space/blog/2021/11/14/1600
        
       | osigurdson wrote:
       | It seems amazing to that a post about making something small on
       | an end-of-life, Windows only framework. It would have been far
       | more interesting to see what is possible with AOT and trimming.
        
       | planckscnst wrote:
       | There is also this article about creating a tiny elf executable
       | that's pretty interesting:
       | https://news.ycombinator.com/item?id=21846785
        
       | tgtweak wrote:
       | Probably 2-4MB in memory while running
        
       | tgtweak wrote:
       | Probably 4-5MB in memory while running . CLR is heavy.
        
         | metaltyphoon wrote:
         | .NET 8 Aot changes that drastically. It's on par or better than
         | Go.
        
       | tkubacki wrote:
       | Personally I don't like .NET anymore because it's almost
       | exclusively C# only (which seems quite dated compared to eg.
       | Kotlin). JVM env is much more scattered across multiple
       | languages.
        
         | jsight wrote:
         | That's funny, because so many of the early sales pitches for
         | .net focused on polyglot as a main feature.
        
         | jug wrote:
         | And personally I think C# is moving forward almost too quickly,
         | as if Microsoft has a compulsion to always introduce new
         | features to the language with each new release, accelerating
         | especially since post-.NET Framework. Funny how different views
         | one might have!
        
       | caeciliusest wrote:
       | That was eye opening for me how the .exe could be brought down to
       | less than 1KB... if someone could get a Hello World .exe in
       | Flutter down to just 1MB, that would really be something.
        
         | Kwpolska wrote:
         | The first step would be to convince Microsoft to ship Flutter
         | with Windows.
        
         | Alifatisk wrote:
         | Does it count if I only compile "Hello world" in dart?
        
       ___________________________________________________________________
       (page generated 2023-07-09 23:00 UTC)