The biggest chungus
I have to confess - I own the domain bigchungus.net.
Somehow, that stupid meme stuck with me for a long time, and I wanted to... well, as I'm writing this, I want to say "have an NFT" of it, but in a more meaningful way than whatever bullshit NFT provides as a technology. I'd say that having a gTLD domain named after a thing is a pretty good "non-fungible" "token" of it.
The next logical thing after buying it was to host something. Now, I'm not good ay any sort of digital art, so it's not like I could make a funny landing page just for the fun of it. Instead, I somehow decided to make a .NET library named after the meme, and use the domain as the landing page for that. It makes sense that bigchungus.net is dedicated to a .NET library named BigChungus.NET, right?
Does the library need to have anything to do with the meme? Nope!
The perfect library
The idea of what this library should be evolved for about a year. Interestingly, I can pinpoint exactly how each aspect of the library came to be, and how they influenced one another over time.
It has to do with UI
A bit of a backstory. My first programming language was PascalABC.NET - it's a Pascal/Delphi dialect that compiles into a .NET application. It also comes with a complete Windows Forms form designer, so when I decided that I wanted to write a simulator for the surgery minigame in Growtopia, it was my first tool of choice. That's how .NET and Windows Forms came to be my first UI stack.
Later on I (painfully) switched to C#, and eventually got a job as a support engineer for a proprietary WinForms control suite, so writing desktop UIs with WinForms is kind of my thing now.
At some point, I had to troubleshoot cases where the issue in a WinForms application actually stems from the underlying Win32 API (Windows 11 having needed more time to cook, etc.), so I had ChatGPT write a Win32 "Hello world" in C# for me. Having worked with this code snippet for some time, I decided to try and implement more of raw Win32 functionality in C# - at the time, I already thought of WinForms as a framework as bloated and overengineered.
You can see the eventual result in TheLeftExit/BigChungus.NET-old - an overengineer attempt to expose the entire Win32 API scope in the managed form. It was overengineered in its own right, and probably unusable in real applications.
Now that I'm looking through the commit history, I see exactly where I reframed the project from TheLeftExit.WindowsWindows (you know, how WinForms works with Forms, but Win32 works with Windows) to BigChungus.NET: the commit is literally named "chungus".
Over time, I learned of a concept named Win32 dialog boxes. It's basically a set of Win32 functions that make it easier to just display a window with controls and handle user interactions, with much less interop glue required. In hindsight, that's all I ever wanted from my own library, so I quickly switched to that. You can see the result in TheLeftExit/BigChungus.NET.
See also: WS_DONTNEEDTHAT, a small rant about Win32 window styles that I wrote during this stage of development.
It must look beautiful
I spent a lot of time balancing between simplicity and functional scope of the library. On one hand, I wanted everything to be object-oriented, so that it had the flexibility of WinForms, and WinForms code could roughly translate into code compatible with my library. On the other hand, that required a lot of custom infrastructure on top of raw Win32, and I'm too lazy to write and maintain all of that.
While I was rewriting the library to use dialog boxes, I naturally stumbled onto a solution that I'm still gloating over having created: MVVM-like expression-based bindings. Each binding translates to a "dialog procedure handler" that gets injected into the dialog procedure (basically its event loop). In the first proof-of-concept, the demo application's Program.cs looked roughly like this:
var builder = new DialogBuilder<MyViewModel>();
var textBox = builder.AddControl<TextBox>(control =>
{
control.Bounds = new(10, 10, 50, 30);
control.Text = "ControlText";
});
textBox.SetBinding(x => x.Text, x => x.Text);
var runner = builder.Build();
var viewModel = new MyViewModel() { Text = "ViewModelText" };
runner.Run(viewModel);
It's simple, it's readable, it's beautiful. No event handlers - only the view model with two-way change notifications. Binding declarations use .NET expressions for compile-time safety; internal logic extracts raw property accessors, and I get to indulge myself in juggling handler classes with 4 generic parameters under the hood. From that point on, my goal became to implement as much functionality as reasonably possible and fitting within this syntax.
It must be NativeAOT-compatible
I really liked the idea of .NET NativeAOT deployment. It makes the application truly portable (to non-technical users) by combining the best of two deployment models:
- Framework-dependent applications are small, usually a few megabytes, but they require you to have a matching .NET version. It wasn't a big deal with .NET Framework, but these days you get a new .NET version (with a corresponding installer) releasing every year, and none of them are pre-installed on Windows.
- Self-contained applications can run anywhere, but they ship with the entire framework (upward of 50 MBs), and it's either ~50 files in the same directory as the executable (which looks ugly), or it's bundled into the main executable (but some native dependencies still temporarily get unpacked while the application is running - I want to ship a small specialized utility, not a WinRAR self-extracting archive).
As a downside, you have to stick to a strict set of coding rules - primarily, no arbitrary reflection, and all P/Invoke bindings have to use and comply with the rules of LibraryImportAttribute (or be simple enough to not warrant that). It's easy enough to follow these rules in your own code, but most third-party libraries don't, so in practice, the main downside is that you can't use most NuGet packages out there.
It was at this point that I realized I'm writing something unique. I was not aware of a .NET UI library that would let me display a simple utility dialog from a NativeAOT application, and I really wanted one. I couldn't bring myself to force users to download all that bloat just to try my app.
It must be cross-platform
Wait, weren't we just talking about Win32 dialog boxes as the central point of the entire library?
Anyway, when I migrated to Linux, I knew that the existence of this library wouldn't stop the migration, but I didn't have a plan on what to do with it afterward. If I were to continue working on it, it would have to be in a Windows VM, targeting that same VM or Wine. Both of these options are silly, so I pretty much abandoned the project for half a year.
I knew that there are two cross-platform frameworks that I can target - GTK and Qt, and I toyed around with the idea of targeting one of them instead.
GTK would be the easiest, since it has a C-native API that's very similar, and just as easy to P/Invoke into, as Win32. However, it's GNOME-native (with its large control margins/paddings and other stylistic choices), and I'm more of a KDE kind of guy. In late 2025 I wrote a small code snippet that instantiates a GtkApplication from C#, just to see if I could dynamically link to my system's GTK binaries. It worked, and I could print the application object's handle. However, GTK being GTK (and me being too dumb for their docs, I suppose), I dodn't have it in me to take this any further.
Qt is much more visually and functionally consistent with Win32, but it's a C++-native API - that chucks any possibility of interop with C# out the window.
Or so I thought.
Long story short - after getting Perplexity to dumb down the concept of C++-C# interop for me, I came up with a working proof-of-concept where the same dialog builder pattern works with Qt APIs: TheLeftExit/QtTest. As I'm writing this, I have VS Code open with further work on it.
Considerations on Qt
Qt is much more pleasant to work with than Win32. I've read that Win32 API is pretty dated, since the only reason it still exists is to maintain compatibility with apps written for Windows 95 or something like that. Qt is object-oriented (which makes IntelliSensing my way through it easier), has amazing documentation, and commercial backing that ensures it will stay healthy for the foreseeable future (even if they don't get a dime from me).
Funnily enough, using Qt makes the final application more portable on Linux than on Windows. Most distros have something like qt6base in the package manager that automatically makes Qt binaries available to all applications; Windows will require you to ship it alongside the application, killing part of the reason to use NativeAOT. From what I've read, it's also possible to statically link Qt into the application executable, and that might make Windows deployments easier (at the cost of (L)GPL-ing the entire application, which I don't mind).
The unfortunate part is that I'll need to implement a separate C export for each C++ API I want to use. This seems doable so far, but still a bit icky. I also had to brush up on my rudimentary knowledge of C++, which is probably not going to be useful outside of this project.
Now what
I'll admit, it's pretty rich to write a retrospective on a project that I just started working on (or at least, the last iteration of it). I just wanted to get all of the gloating out of the way, so I could focus on actually writing it.
I keep pushing my changes to GitHub, only because I couldn't be arsed to finally deploy Forgejo, and I need some sort of a backup for my code. I hope I'll get to that soon.
I still don't know what to name the project. My GitHub already has two BigChungus.NET repositories, and both of them are uploaded to NuGet as deprecated versions (I really shouldn't rush with NuGet uploads). I was thinking QtChungus. Though, if my plans to terminate my Microsoft account (and thus lose access to NuGet publishing) come to life, maybe that won't be an issue. I've already decided that the main motivation to develop this library is to use it for myself, most likely for a Factorio factory calculator; so I might just host it on the NuGet feed integrated into my Forgejo instance, and call it a day. Maybe then I'll finally beat Gleba.