It has been a mess for 15 years and Microsoft keeps making it worse by adding new frameworks without retiring the old ones. Win32, WPF, WinUI, MAUI. Nobody knows which one to pick.
> And from what I can tell, neither are most developers. The Hacker News commentariat loves to bemoan the death of native apps. But given what a mess the Windows app platform is, I’ll pick the web stack any day, with Electron or Tauri to bridge down to the relevant Win32 APIs for OS integration.
Well yes as a user I prefer native apps for their performance. It's clearly a mess to develop native apps as the article shows. But as a user I don't see that problem. I do see ever worsening apps though. Like the total mess that is new outlook and teams.
"So when I went to work on my app, I was astonished to find that twenty years after the release of WPF, the boilerplate had barely changed."
Such is the benefit and the curse, I guess, of having the Windows API being locked in the distant past for backwards compatibility.
I've always been surprised that Microsoft didn't do a full operating system refactor and provide a compatibility layer for running old binaries. Perhaps they figure it would be better to just transition everything to software as a service using web tech? But I just don't see how that strategy is gonna work long-term.
Say what you will about Apple, they at least still think it’s important to support and do native development, especially for their OS. Microsoft might as well have bought webOS as their new Windows replacement and admitted they’ve given up on native apps.
I'm an embedded programmer who occassionally needs to write various windows programs to interface with embedded devices (usually via serial port or usb), and I find it a breeze to write native gui programs in pure win32 and c++.
Recently had to add a new feature to and old program that was last updated in the XP era and two things to note:
1. The program did not need to be updated to run on Vista, 7, 10 and 11, shit just kept working throughout the years.
2. I loaded the project into Visual Studio 2022, it converted from VC6 and compiled without problems, added the feature, shipped a new .exe to the customer, and it just worked.
What other platform has that backwards and forwards compatibility success story?
I wonder if Unity (the game engine) actually has a sneaky potential here. It’s cross platform, fast, and maybe just maybe less bloated than carrying around an entire browser like Electron?
Given the size of some Electron software, bundling TCL/Tk with IronTCL and TCLLib+TKLib weights 58MB and you can develop your own software with it, and that with the source of everything included.
And if you set a native theme for TTK in your code (literal two lines), your software will stop looking Motif-Industrial, the widgets will have the classic Win32 themes. It will look native from XP and up.
Looks like Tcl is still being developed, with 64 bit version released in 2024 [1]. How big is your software, is it mainly C code wrapped in Tcl for the GUI?
Author raises several good points. Why isn't the latest .NET runtime pulled down into Windows 11 devices via Windows Update? Why isn't there a better path forward for deployment?
It's another example of how they have completely abandoned any attempt at providing a good user experience across their products
I'm assuming but the versions are not fully backwards compatible so you can't just ship the latest version, they would need to ship all. There almost ten .NET versions released after the one which ships in Windows. And a new version is released every year.
The author does mention that .NET does have distribution options which don't require the user to install the runtime. You can have it package the full runtime with your build, either as a bunch of files, a self-extracting executable, or a standalone AOT-compiled native executable.
The author mentioned that the AOT-compiled executable is 9 MiB which is unacceptable to them. The other options will need even larger. Personally I don't see 9 MiB as a big deal especially when the author would rather go with Electron which is larger at worst (bundled Chromium) and only inefficient at best (system WebView).
Seems to me that really the simplest solution to authors problem is to write C++ safely. I mean...this is a trivial utility app. If you can't get that right in modern C++ you should probably just not even pretend to be a C++ programmer.
Most of the desktop applications I have wrote over the years have been in other languages like Java and Go as I have wanted them to mostly be cross platform. In these cases I have always used the Software UI, which in Java is Swing and in Go is Fyne. These are usually reasonably fast, don't necessarily look native depending on how its themed but ultimately fit the language better than trying to bridge to Win32 or GTK/QT.
This is quite timely as we need to write a simple UI for Windows (a few buttons, status, maybe a file menu). The main constraint is it must compile to a single binary (.exe) with no dependencies such as runtimes, DLLs, languages etc. It also needs to run on some older unsupported Windows systems, probably Windows >= 7, 32 bit.
My first thought was MFC. Basic, fast, well understood.
But then maybe WxWindows so we can cross-compile it (from Linux) and use the same UI on other platforms? It could probably be compiled statically although I've not tested it.
Or Mono, but that needs a runtime?
Edit: Some comments mention Qt which could also work although how large is the runtime? Can it be compiled statically?
Qt is compiled to a native .exe. It doesn't have a runtime. To give you a rough idea of size, I have 3 GUI application written using Qt/C++. The installers are 72 MB, 69 MB and 32 MB. The first 2 include a significant amount of documentation. I could probably get them a bit smaller if I really needed to.
For what you describe, .NET + WinForms or WPF will work just fine. These days it can built self-contained executables, although they won't be small (but then again, when many websites have multiple megabytes of JS...). Or you can target .NET 4.8 - for something this simple I doubt there'd be much difference but then you can ship an .exe that measures in kilobytes, and the runtime is included with every version of Windows going back 10 years.
You can build single-file executables for .NET 4, and I do, but there's no static linking, so you need to restrict yourself to assemblies that ship with the framework and whatever other prerequisites you're willing to install or assume will be installed.
Two notable caveats:
1. Certain newer C# language features require runtime support that doesn't ship with older versions of .NET, which can include quite a few DLLs.
2. In-process COM components can only be instantiated in .NET runtimes runtimes with the same processor architecture, even if the .NET program itself is processor-independent (which by default will use the runtime matching the native architecture), so you may need to ship multiple executables to match multiple COM dependency versions even if your program is otherwise processor-independent or you only intend to ship it for a single processor architecture.
For example, if you build a processor-independent program that relies on the Microsoft Access DAO library, it will by default work correctly on 32-bit Windows if 32-bit Access is installed, and on 64-bit Windows if 64-bit Access is installed, but there's no way to get it to work on 64-bit Windows with 32-bit Access without shipping a second executable (that can be identical with the exception of the 32BITREQUIRED flag in the CLR header[1] that can be set as part of the build process or with the .NET SDK corflags utility).
Rather than ship separate "32-bit (or 64-bit with 32-bit <bar>" and "64-bit" versions of a program <foo> that interops with some in-proc COM component <bar>, I prefer to build a single executable <foo>.exe, then create a copy <foo>32.exe with the flag set as part of the build process, and ship both to be installed side-by-side.
Then, since the native COM DLL isn't loaded until you actually create an interop object referencing it, you can just try to create an Interop.<Bar>.<something> object at program start, catch ComException with HResult == REGDB_E_CLASSNOTREG, then in the exception handler, run <foo>32.exe as a subprocess with identical command-line arguments and exit with <foo>32's exit code, unless you're already running in a 32-bit CLR, in which case you just fail normally.
I write .NET Framework 4.8 apps. And I will until .NET has an actual support lifetime. 4.8 will still be supported and receiving security updates in ten years, .NET 10 will be gone in 2.
Hobby projects should not be built on a platform that is constantly changing underneath.
It doesn't really matter anymore. When .NET 10 is done, there will be .NET 12 and all your apps will run just fine on it.
.NET framework used to get new versions as well; it's just that it's not anymore. After moving on to newer versions, .NET framework feels clunky now. Also you end up missing out on a decade of new libraries.
Really nice article, thanks - yes I found the same myself recently when trying to write a trivial (I thought) Windows app.
I first investigated the Windows native options and was pretty bamboozled; I wanted to use the "mainstream" "up to date" option (presumably c# and some framework) but as TFA describes, it wasn't at all clear which that was.
I ended up doing it in python with pyqt then finding out a clean deployment was a pain, so revisited the .Net options and remembered why I'd discarded them in the first place...
It is indeed a complete mess (at least coming in anew) and a very strange situation for the world's main desktop environment to be in.
It's always about the abstractions which try to cover the underlying mechanisms but not always can do it. The same with any programming, like named pipes for example. However I need to tell you that
1. Wow you have great knowledge of windows. Congratulations
It’s been a long time since I had to touch Windows development. If I had to do it over again, I would use React Native for Windows UI where possible and low-level Win32-React Native module bridges for user space code.
The last time I had to do Windows development was about 15 years ago. I used a library called WTL (I think a couple comments here mention it). I couldn’t use any of the newer stuff that Windows 8-10 were pushing because it needed backward compatibility. It seemed way less bloated than MFC, but not as annoying to use as ATL or rawdogging Win32 APIs.
Ironically, I was developing a Win32 app to build a cloud bridge to a Rails app (talking to Quickbooks COM API which was hell on Earth, with XML and XML definitions) on Mac, using VMware on Mac to talk to Quickbooks Windows. I was so annoyed with Win32 development I used the Chrome Embedded Framework library to build the UI for the Win32 app so I wouldn’t have to wrestle WTL for UI and just have browser-based views to drive UI.
I think it was very tempting to drop C/C++ development for .NET code, but I didn’t want to drop off user adoption by requesting users to download a huge .NET runtime if their computer didn’t already have it.
This was when I was building Levion, a Quickbooks Windows to Cloud Rails app…
You should run away from React Native Desktop screaming. It's built on top of WinUI 3, and inherits all it's problems, while also being dramatically slower. Almost all functionality requires writing C++ code, and communicating between C++ and JS is a huge pain in the ass. Use QT instead.
And if you think using it will let you port your app to other platforms, think again. There's no drop-in compatible equivalent of RNW for Linux, and the version for Mac is even worse than the Windows version.
t. works on a huge app written with React Native Desktop
Let me chime in and say that plain Win32 API is a perfectly viable option if you are using C++ (or another "OO" language) and if you are willing to sink a couple of weeks into writing your own MFC-like wrapper.
Clearly this is not an option for those who are just starting up with Windows GUI work, but with little experience it is really a matter of 2-3 weeks of ground work and then you have full control over all nuances of the UI, yours to extend and mend as you wish.
If there's one thing that Microsoft is really good at, it's ensuring deep backward compatibility. So anything that's based on Win32 API is going to be stable. If it works now, it will work later.
I have some examples from 10+ years of development updates accumulated here - https://bvckup2.com/wip
The last time I built a native Windows app years ago, I used WTL 3.0. It’s a light weight wrapper on the native Win32 API, lighter than MFC. It took out the unpleasantness of working directly on Win32 API and wrapped it in a simple OO framework. It had access to all features of Win32. It could produce runtime binary in dozens of K, instead of MB or GB.
Microsoft released it open source later on. Looking at the repository, looks like it has been kept up and maintained, up to version 10 now.
I just use JUCE. It solves all the problems I need solved on Windows and doesn’t lock me into anything. More and more, if its not cross platform C++, it just doesn’t make any sense to invest in it. This is getting more relevant as the years go by, alas.
99 comments
[ 3.2 ms ] story [ 73.2 ms ] threadWell yes as a user I prefer native apps for their performance. It's clearly a mess to develop native apps as the article shows. But as a user I don't see that problem. I do see ever worsening apps though. Like the total mess that is new outlook and teams.
Such is the benefit and the curse, I guess, of having the Windows API being locked in the distant past for backwards compatibility.
I've always been surprised that Microsoft didn't do a full operating system refactor and provide a compatibility layer for running old binaries. Perhaps they figure it would be better to just transition everything to software as a service using web tech? But I just don't see how that strategy is gonna work long-term.
Running with html/css/js has benefits it really is open and free development based on international standards and not locked into any single big tech.
Recently had to add a new feature to and old program that was last updated in the XP era and two things to note:
1. The program did not need to be updated to run on Vista, 7, 10 and 11, shit just kept working throughout the years.
2. I loaded the project into Visual Studio 2022, it converted from VC6 and compiled without problems, added the feature, shipped a new .exe to the customer, and it just worked.
What other platform has that backwards and forwards compatibility success story?
And if you set a native theme for TTK in your code (literal two lines), your software will stop looking Motif-Industrial, the widgets will have the classic Win32 themes. It will look native from XP and up.
[1] https://en.wikipedia.org/wiki/Tcl_(programming_language)
It's another example of how they have completely abandoned any attempt at providing a good user experience across their products
The author does mention that .NET does have distribution options which don't require the user to install the runtime. You can have it package the full runtime with your build, either as a bunch of files, a self-extracting executable, or a standalone AOT-compiled native executable.
The author mentioned that the AOT-compiled executable is 9 MiB which is unacceptable to them. The other options will need even larger. Personally I don't see 9 MiB as a big deal especially when the author would rather go with Electron which is larger at worst (bundled Chromium) and only inefficient at best (system WebView).
My first thought was MFC. Basic, fast, well understood.
But then maybe WxWindows so we can cross-compile it (from Linux) and use the same UI on other platforms? It could probably be compiled statically although I've not tested it.
Or Mono, but that needs a runtime?
Edit: Some comments mention Qt which could also work although how large is the runtime? Can it be compiled statically?
MFC, wx, Qt .. it's all overcomplex pointless bloat for this task imo.
Get started learning here: https://news.ycombinator.com/item?id=21459616
Two notable caveats:
1. Certain newer C# language features require runtime support that doesn't ship with older versions of .NET, which can include quite a few DLLs.
2. In-process COM components can only be instantiated in .NET runtimes runtimes with the same processor architecture, even if the .NET program itself is processor-independent (which by default will use the runtime matching the native architecture), so you may need to ship multiple executables to match multiple COM dependency versions even if your program is otherwise processor-independent or you only intend to ship it for a single processor architecture.
For example, if you build a processor-independent program that relies on the Microsoft Access DAO library, it will by default work correctly on 32-bit Windows if 32-bit Access is installed, and on 64-bit Windows if 64-bit Access is installed, but there's no way to get it to work on 64-bit Windows with 32-bit Access without shipping a second executable (that can be identical with the exception of the 32BITREQUIRED flag in the CLR header[1] that can be set as part of the build process or with the .NET SDK corflags utility).
Rather than ship separate "32-bit (or 64-bit with 32-bit <bar>" and "64-bit" versions of a program <foo> that interops with some in-proc COM component <bar>, I prefer to build a single executable <foo>.exe, then create a copy <foo>32.exe with the flag set as part of the build process, and ship both to be installed side-by-side.
Then, since the native COM DLL isn't loaded until you actually create an interop object referencing it, you can just try to create an Interop.<Bar>.<something> object at program start, catch ComException with HResult == REGDB_E_CLASSNOTREG, then in the exception handler, run <foo>32.exe as a subprocess with identical command-line arguments and exit with <foo>32's exit code, unless you're already running in a 32-bit CLR, in which case you just fail normally.
[1] https://ecma-international.org/wp-content/uploads/ECMA-335_5...
Not out of the box, but there are ways to do it, e.g. ILMerge.
> so you may need to ship multiple executables
OP's baseline is native code tho, so they are already planning to do that.
Hobby projects should not be built on a platform that is constantly changing underneath.
.NET framework used to get new versions as well; it's just that it's not anymore. After moving on to newer versions, .NET framework feels clunky now. Also you end up missing out on a decade of new libraries.
To be fair it's been super simple to upgrade .NET versions the last few versions especially.
I first investigated the Windows native options and was pretty bamboozled; I wanted to use the "mainstream" "up to date" option (presumably c# and some framework) but as TFA describes, it wasn't at all clear which that was.
I ended up doing it in python with pyqt then finding out a clean deployment was a pain, so revisited the .Net options and remembered why I'd discarded them in the first place...
It is indeed a complete mess (at least coming in anew) and a very strange situation for the world's main desktop environment to be in.
1. Wow you have great knowledge of windows. Congratulations
2. Boy windows API is a mess.
But imgui is a breeze of fresh air for internal stuff
The last time I had to do Windows development was about 15 years ago. I used a library called WTL (I think a couple comments here mention it). I couldn’t use any of the newer stuff that Windows 8-10 were pushing because it needed backward compatibility. It seemed way less bloated than MFC, but not as annoying to use as ATL or rawdogging Win32 APIs.
Ironically, I was developing a Win32 app to build a cloud bridge to a Rails app (talking to Quickbooks COM API which was hell on Earth, with XML and XML definitions) on Mac, using VMware on Mac to talk to Quickbooks Windows. I was so annoyed with Win32 development I used the Chrome Embedded Framework library to build the UI for the Win32 app so I wouldn’t have to wrestle WTL for UI and just have browser-based views to drive UI.
I think it was very tempting to drop C/C++ development for .NET code, but I didn’t want to drop off user adoption by requesting users to download a huge .NET runtime if their computer didn’t already have it.
This was when I was building Levion, a Quickbooks Windows to Cloud Rails app…
And if you think using it will let you port your app to other platforms, think again. There's no drop-in compatible equivalent of RNW for Linux, and the version for Mac is even worse than the Windows version.
t. works on a huge app written with React Native Desktop
Clearly this is not an option for those who are just starting up with Windows GUI work, but with little experience it is really a matter of 2-3 weeks of ground work and then you have full control over all nuances of the UI, yours to extend and mend as you wish.
If there's one thing that Microsoft is really good at, it's ensuring deep backward compatibility. So anything that's based on Win32 API is going to be stable. If it works now, it will work later.
I have some examples from 10+ years of development updates accumulated here - https://bvckup2.com/wip
Wine is better at it than Windows itself. Especially for really old programs.
Microsoft released it open source later on. Looking at the repository, looks like it has been kept up and maintained, up to version 10 now.