4 ms·
Thanks for steering the discussion in a more constructive direction than where I was going. There are C++ UI toolkits wrapping Win32, Cocoa, etc., and sometime
by mwcampbell 2y ago
Thanks for steering the discussion in a more constructive direction than where I was going.
There are C++ UI toolkits wrapping Win32, Cocoa, etc., and sometimes such toolkits are statically linked into the executable, meaning that dead code elimination should be able to minimize the executable size. I'm thinking in particular of wxWidgets. But even with C++ and static linking, it's easy for a library to be designed in such a way that you end up paying for stuff you don't use. The last time I did a project in C++ with wx, 10+ years ago, my program itself was quite trivial, and it only used very basic widgets, but the statically linked Win32 executable still ended up being ~2.5 MB. I didn't get very deep into why, but my understanding is that code for a bunch of features (printing, drag and drop, etc.) got linked in, even though my application wasn't using it, because the window procedure had to handle messages related to those features regardless. I guess, to optimize for minimal executable size, the API would have to be designed to require the application developer to explicitly initialize only the features they need, and in this case, the message handler would have to be more dynamic.
Sometime I'd like to design a new toolkit inspired by wx and SWT, in either Rust or Java. In the latter case, I'd use the new Foreign Function and Memory API to call the native APIs. I'd be interested to see how much GraalVM Native Image could optimize an application written with such a toolkit; I recall you've said good things about how much Native Image can optimize program initialization in particular.
There seems to be a broad consensus, though, that Win32 in particular isn't suitable for "modern" desktop apps, and so, by extension, neither are wx and SWT. Certainly in the Rust GUI space, all the popular toolkits are drawing their own widgets, and even reimplementing lower layers of the stack like text rendering, thus contributing to the trend you talked about where applications bring along their own runtime. I assume there are actual deficiencies in OS-supplied APIs that are leading to this, though a sufficiently jaded person might argue that developers are in fact wasting their time on unnecessary things for one reason or another. Anyway, if the classic Win32 widgets are still good enough for some parts of the OS itself (although, admittedly, fewer and fewer with each release), then presumably they're good enough for some subset of applications as well, and if the developers of those applications had a convenient and lightweight abstraction layer over those classic widgets, then we could start to reduce bloat.
- mike_hearn 2y agoYeah, static DCE is a limited optimization. I've personally given up on it. It's not useless, but it's a big lift for developers and the app size will keep growing anyway. These days I'm more interested in dynamic code paging systems as a way forward, but, at the moment I'm not able to work on that right now. Think about how "bloated" an app like Google Maps or Search is, yet it still feels fast because it downloads what it needs on demand. Everything should work that way. The Conveyor installer is a native Win32 app written in C++. It's pretty trivial, it mostly just invokes some Windows APIs, works around bugs in those and ties the results to a progress bar. It's a classic app that uses HWNDs, window messages and the like. The bulk of the logic is in one file. Despite that it's about a 500kb EXE. Where do the bytes come from? Partly, it's C++. Native code is verbose and template instantiation creates a lot of it. You don't need to instantiate many std::vectors before suddenly binary size has crept up on you. The widespread adoption of C++ increased binary sizes significantly over C, because suddenly you were using value typed containers everywhere instead of a single shared linked list utility library. GraalVM native executables start extremely fast, but they aren't small. Partly this is the mismatch problem again: operating systems don't provide stuff like built in garbage collectors, even though GC is a fundamental service nearly every app needs, so, programs ship their own. Partly it's because GraalVM makes apps start fast by pre-initializing the app, so the binaries come with a heap image that it just mmaps and starts using. But then you have to download and store the initial heap instead of recomputing it at startup. It's a disk space/startup time tradeoff. And partly it's because a static analysis must be inherently conservative. Anything that could potentially run has to be included, even if in reality it never does (think exception handlers, assert messages...) So IMO the right fix is to just assume an always-on internet connection and then stream code in as it's needed, with pre-emptive paging in the background to get apps into a state where you can disconnect. The system would aggressively deduplicate data to avoid redownloading stuff you already have. A smarter version of what browsers do. This lets you have apps that are hundreds of gigabytes in size but they still start fast and sip resources on the client side where you're most constrained. I think modern languages ignore Win32 partly because it sucks and partly because virtually no working developers have ever used it. Many modern devs won't even realize it's there. How many devs that slung window messages in the 90s are still coding? I think a lot have retired or moved into management roles, or were hired to work on Chrome. There are Microsoft security engineers, working on Edge, who announced in a blog post that they had no idea how to use COM and had to rely on AI to write the needed code for them! [1] And a lot of devs aren't developing on Windows, they're probably writing these Rust toolkits on macOS or Linux. Anyway, Win32 UI is something I'd not recommend using. It's been unmaintained for decades. Conveyor uses it because, well, it's an installer that downloads the rest of the app, so size matters more than anything else, and the UI needs are simple. The moment you want table stakes stuff like High DPI support, responsive layout, dark mode, vector icon support, data binding etc, you end up needing big frameworks that sit on top and/or you end up in a nightmare of twisty barely documented APIs. [1] https://microsoftedge.github.io/edgevr/posts/Escaping-the-sandbox-A-bug-that-speaks-for-itself/ https://microsoftedge.github.io/edgevr/posts/Escaping-the-sa...