An example from one of my projects: https://pastebin.com/Jvjn5C6S
You need to reference it from your resource source like so: https://pastebin.com/8FUi4tMz
And then compile that into an object file with windres: x86_64-w64-mingw32-windres rsrc/metadata/windows.rc -o winbuild/windowsrc.o
And link it with your project like you would any other object file.
While MSDN is a bit impractical to browse (there's simply so much stuff in there) it's usually the best place to go to for documentation when doing Windows dev.
https://learn.microsoft.com/en-us/windows/win32/sbscs/applic...
If an EXE doesn't have a manifest file, Windows assumes that it's ancient, so it falls back to conservative defaults like ye olde USER controls to try and avoid breaking it.
DLLs only reduce size if their code is meant to be shared between different programs.
Yes they are. Exercising the native Windows API is the entire point of this project, and the only artifact it builds is an executable.
edit: See the thread; I had the wrong end here. I haven't worked with Win32 or C in so long I'd forgotten what balls of fishhooks and hair they both tend to be.
Not sure. I haven't really done anything serious with MinGW, or Cygwin or even Windows really, in at least a decade now. But there's not really a lot going on in the build here, so I would imagine with your much more recent experience you'd be better able to interpret what's there.
edit: Wait, are you even discussing the old (okay, ancient) Win32 API? I'm confused, but as I said, it's been a very long time since I attended the space.
I hope that clears my point up.
Say what you like about the modern JS ecosystem, the practical requirement of a single exhaustive declaration of all external dependencies and their sources is not to be sneered at.
These days it's perfectly solved at some cost in storage and memory, which in entire fairness are lately about as cheap as any other resource. There is also lately a movement in the space significantly away from "taking dependencies," a phrase I quote to highlight its pejorative connotation: presumptively undesirable, however necessary.
If a large language model is satisfactorily efficient for 2025, then to scruple over the disk space and RAM used in the current model of exhaustive dependency resolution employed by modern JS/TS package managers, seems impossible to regard as other than ideologically motivated.
In any case, I took some care earlier to avoid talking beyond the package.json dependency declaration model, in hopes of forestalling precisely this bikeshed. If you want to talk further about things that don't interest me in this context, please find someone else with whom to do so.
(I don't wish to seem rude in this. Only that I have had that discussion too many more times than I can count, and I have long since stopped expecting it to vary noticeably in a given iteration.)
In Windows, the APIs provided by the OS are IMO better than these C standard headers.
WinAPI date/time is more precise, the kernel keeps int64 number of 100 nanosecond ticks. WinAPI supports arbitrary timezones, has functions to convert between timezones, has functions to deal with daylight saving.
About strings, WinAPI can convert strings between encodings, case-insensitive comparisons (including a version to use arbitrary locale), Unicode normalization, etc.
edit: Saw that you just now edited your comment, glad we're on the same page now.
A traditonal Windows application would be using the Windows APIs, and not the C standard library, e.g. FillMemory() instead of memset(), thus there is no DLLs to ship with the application.
As can be seen on Petzold books examples.
The only time you really can't static link to the CRT is LGPL compliance?
15kB quickrun.exe :) C, pure Win32 API.. No hacks to shrink binary, Mingw32 compiler.
Its GUI app to quickly launch any application via alias.
You get that for free on Windows.
no exception handling tables (necessary even in C in case exceptions traverse a c function,
Not necessary if you're using pure C. SEH is rarely necessary either.
Combined with early Palm Pilot 68k programming, those were the last hurrahs of non-retrocomputing asm I can remember.
Sometimes I too take a step back and look at the way things have changed. But then again we've made a lot of progress for the size-changes I guess.
Is it due to MinGw maybe?
I'm using x86_64-15.1.0-release-win32-seh-msvcrt-rt_v12-rev0.7z from https://github.com/niXman/mingw-builds-binaries/releases/tag... as the toolchain. This produces a 102 KB .exe file. Right off the bat we are doing much better than the claimed 278 KB. Maybe the author is using a different toolchain or different settings? Exact steps to reproduce would be welcome.
We can improve this by passing some switches to GCC.
gcc -Os => 100 KB
gcc -Oz => 99 KB
gcc -flto => 101 KB
gcc -s => 51 KB
gcc -s -Oz -flto => 47 KB
If all you are interested in is a small .exe size, there is plenty of room for improvement here.I wonder if they are compiling with debugging symbols? I don't know how much this would change things in vanilla C but that would be my first guess
Link-Time Optimisation with "-flto" might also help, depending on how the libraries were built.
What's modern about it? Also you could have used C++ instead to remove some potential issues, and those global variables...
Use std::string and std::array or std::list, some anonymous namespaces, remove all the malloc, etc. Your code would be half the size and still compile to the same assembly language without the bugs.
While nothing is modern about this approach, if we're going the WINAPI route, there's very little to be gained by using std::string instead of the LPWSTR that WINAPI offers (and plays nicely with). I would definitely avoid plain C strings (char[]) but rather use the wide version (which is what LPWSTR is under the hood). But for std::array or std::list, I don't see how the codebase would vastly benefit from them.
What about them? In a 500 loc app there is no practical difference and there's only ~20 of them with clear purpose.
> Use std::string <...> or std::list <...> remove all the malloc, etc
> still compile to the same assembly language without the bugs.
I see you have no clue what those things actually compile down to.
Already by Windows 95 timeframe, only C diehards would be writing Windows applications still in C instead of VB, Delphi and C++, as the more mainstream languages, there were obviously other ones to chose from as well.
Very nostalgic OP, warms my heart 10/10
As others have said, doing so in pure C and linking dynamically, you can easily remain under 20 KiB, at least on Linux, but Windows should be even simpler since it ships with much more out of the box as part of the OS.
In any event, I salute the effort! You can try the linking options I mentioned at the end of my article, it should help getting the size down.
I am not sure why but I believe writing C style code with a C++ compiler was how the windows API was documented to be used. I think Microsoft just went with the idea that C++ was an improved superset of C so should be used even for C-style code.
And as a consequence, for a long time their official C compiler was stuck on C89, while other platforms already had full C99 support and beyond. I believe their support for newer C standards has gotten better since then, but AFAIK they still don't have full C99 support.
Other language doesn't fundamentally change anything if you want to use win32 API, if anything it would make things more confusing.
People often fall prey to C++isms, and they would have made the whole thing an even more confusing mess (to people not familiar with win32 API).
This is a very cute thing to do and some familiarity with win32 APIs is a nice basic competency thing, regardless of what other people think.
Checks out: blurry fonts in scaled dpi, no Tab support, can't Ctrl-A select text in text fields and do all the other stuff that pre-modern frameworks offered you, errors on adding a row, ...
> modern
In what way?
(A lot of what you mention is missing is trivial to add, especially tabbing between controls.)
This code dynamically checks for and calls one of the following: user32:SetProcessDpiAwarenessContext, shcore:SetProcessDpiAwareness, then user32:SetProcessDPIAware. If the Windows version is extremely old and doesn't implement any of those (Windows XP or earlier), it won't call anything.
The high DPI support in Windows went through quite an evolution since XP, but mostly to fix what app programmers messed up. You can have nice and crisp XP at 250% dpi if you do things right, e.g.: https://x.com/TheBobPony/status/1733196004881482191/photo/1
Can you share how you can compile to only 10kb?
Resource files and their binary format are not a good API.
If you have those CreateWindow calls in a decently high level language, you can probably meta-program some resource-like DSL that fits right in the language.
Sure there was K&R C, which each OS outside UNIX cherry picked what would be available.
Additionally outside UNIX clones, the tradition among vendors has been that the C compiler is responsible for the standard library, not the platform.
Thus the C library was provided by Borland, Watcom, Symantec, Microsoft, Green Hills, Zortech,....
Note it was the same on Mac OS, until MPW came to be.
As it was in IBM and Unisys, micros and mainframes.
VMS before OpenVMS.
And so on.
Since Windows 10, you have the Universal C Runtime as well.
They are using gcc.
Btw. I like how Inno Setup used some very old Delphi 2 compiler to create exe so small it would fit without breaking the zip compliancy. I read it somewhere 10+ years ago, so not sure if this is still the case, but still. And the initial dialog was done in pure winapi.h (of course it was winapi.pas which made everything more difficult for me to learn from)
Raylib and raygui is truly incredible from my point of view. I succeeded in getting the macOS and Windows builds going on a bunch of cute little novel (not stock standard in the repo) examples in a matter of hours with AI help. I'm inspired by all I can do with this.
For ages I felt "cut off" from the world of Desktop GUI because it was so verbose, and had high friction - need a bunch of tooling, set up, and so on. And then everything was fragile. I like to work quickly, with feedback, and PoCs and results. I think in raylib I have found a method that really achieves this. For instance, check out this tiny little "text_input.c"
#define RAYGUI_IMPLEMENTATION
#include <raylib.h>
#include "deps/raygui.h"
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
#define MAX_INPUT_CHARS 32
int main(void) {
InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Text Input Demo");
SetTargetFPS(60);
// Load a larger font for better text appearance
Font font = LoadFontEx("resources/fonts/arial.ttf", 32, 0, 0);
if (font.texture.id == 0) {
font = GetFontDefault(); // Fallback to default font if loading fails
}
// Set the font for raygui controls
GuiSetFont(font);
// Customize raygui styles (using BGR order for hex values)
GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER);
GuiSetStyle(BUTTON, BASE_COLOR_NORMAL, 0x50AF4CFF); // Green (B=80, G=175, R=76, A=255) ‚Üí R=76, G=175, B=80
GuiSetStyle(BUTTON, TEXT_COLOR_NORMAL, 0xFFFFFFFF); // White text
GuiSetStyle(BUTTON, BASE_COLOR_PRESSED, 0x6ABB66FF); // Lighter green
GuiSetStyle(BUTTON, TEXT_COLOR_PRESSED, 0xFFFFFFFF);
GuiSetStyle(BUTTON, BASE_COLOR_FOCUSED, 0x84C781FF); // Hover color
GuiSetStyle(BUTTON, TEXT_COLOR_FOCUSED, 0xFFFFFFFF);
GuiSetStyle(BUTTON, BORDER_WIDTH, 2);
GuiSetStyle(BUTTON, BORDER_COLOR_NORMAL, 0x327D2EFF); // Dark green border
// Adjust font size for raygui controls (optional, since font is already 32pt)
GuiSetStyle(DEFAULT, TEXT_SIZE, 20); // Slightly smaller for button and text box to fit better
GuiSetStyle(DEFAULT, TEXT_SPACING, 1);
char inputText[MAX_INPUT_CHARS + 1] = "\0"; // Buffer for text input
bool textBoxEditMode = false; // Tracks if the text box is being edited
bool messageSubmitted = false; // Tracks if a message has been submitted
float effectTimer = 0.0f; // Timer for the flash effect
const float effectDuration = 0.5f; // Flash duration in seconds
while (!WindowShouldClose()) {
// Update effect timer
if (effectTimer > 0) {
effectTimer -= GetFrameTime();
}
BeginDrawing();
// Set background color based on effect
if (effectTimer > 0) {
ClearBackground((Color){ 255, 255, 150, 255 }); // Yellow flash (RGB order)
} else {
ClearBackground(RAYWHITE);
}
// Center the text box and button
int textBoxWidth = 200;
int textBoxHeight = 40;
int buttonWidth = 120;
int buttonHeight = 40;
int textBoxX = (WINDOW_WIDTH - textBoxWidth) / 2;
int textBoxY = (WINDOW_HEIGHT - textBoxHeight) / 2 - 40;
int buttonX = (WINDOW_WIDTH - buttonWidth) / 2;
int buttonY = textBoxY + textBoxHeight + 10;
// Draw the text box
if (GuiTextBox((Rectangle){ (float)textBoxX, (float)textBoxY, textBoxWidth, textBoxHeight }, inputText, MAX_INPUT_CHARS, textBoxEditMode)) {
textBoxEditMode = !textBoxEditMode; // Toggle edit mode on click
}
// Draw the button
if (GuiButton((Rectangle){ (float)buttonX, (float)buttonY, buttonWidth, buttonHeight }, "Submit")) {
messageSubmitted = true;
effectTimer = effectDuration; // Start the flash effect
TraceLog(LOG_INFO, "Message submitted: %s", inputText);
}
// Display the submitted message
if (messageSubmitted && inputText[0] != '\0') {
const char *label = "Message: ";
char displayText[256];
snprintf(displayText, sizeof(displayText), "%s%s", label, inputText);
int textWidth = MeasureTextEx(font, displayText, 32, 1).x;
int textX = (WINDOW_WIDTH - textWidth) / 2;
int textY = buttonY + buttonHeight + 20;
DrawTextEx(font, displayText, (Vector2){ (float)textX, (float)textY }, 32, 1, (Color){ 33, 150, 243, 255 }); // Bright blue (RGB order)
}
EndDrawing();
}
UnloadFont(font);
CloseWindow();
return 0;
}
I love it! I feel unleashed again to program in graphics and games and real GUI! The first real paid programming job I had was using a lot of ps5 in Java and JavaScript (Open Processing) and I dug it! :)And the file sizes are sweet (to me):
- macOS: text_input - 123736
- Windows: text_input.exe - 538909
Two dependencies to distribute with on Windows: glfw3.dll and libraylib.dll (322K and 2.1MB respectively)
Raylib was built to make game programming fun. And maybe I will use it for that! :) But right now I want to use it for GUI. The issue with Qt and others, is while I like the idea of standard-Andy controls, I don't want to pay a commercial license - when I figure "it can't be that hard to get what I want" - as I plan to use this stuff for commercial/proprietary control-panes and layers on my existing products: BrowserBox, DiskerNet, and more.
At the same time I really respect what Qt have done growing their business and might be inspired or even emulate some of their model myself in my business.
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow);
If you know, you know.I'm to understand that entire divisions of Microsoft itself no longer know how to code anywhere near this level, which is why many of their flagship applications (looking at you, Microsoft Teams (work or school)) are Electron monstrosities you can watch draw themselves like Windows 1.0 apps -- on modern multicore hardware.
EDIT: more correct function sig
It's probably too late, but this qualifies for "Show HN" if you update the title to have the prefix "Show HN: ".[0]
I think the size of the source is actually more impressive than the size of the binary. I'm impressed that you can implement the whole thing in what looks like about 1 KLOC in just four .c files.
x += labelW+20;
hDescEdit = createModernEdit(hwnd, x, y, editW, btnH, ID_DESC_EDIT);
x += editW + gap;
What no clang-format or equiv in 1990?The win32 API has its origins on the 8088 processor and doing things a certain way results in saving 40 bytes of code or uses one less register or something.
I wrote a lot of toy gui apps using mingw and Petzold's book back in the day. Writing custom controls, drawing graphics and text, handling scrolling, hit testing etc was all a lot of fun.
I see in your app you're using strcpy, sprintf. Any kind of serious programming you should be using the length-checked variants. I'm surprised the compiler didn't spew.
You'll also find that the Win32 API has a lot of replacements for what's in the C standard library. If you really want to try and get the executable size down, see if you can write your app using only <Windows.h> and no cstdlib. Instead of memset() you've got ZeroMemory(), instead of memcpy() you've got CopyMemory().
At some point writing raw C code becomes painful. Still, I think doing your first few attempts in raw C is the best way to learn. Managing all the minutiae gives you a great sense of what's going on while you're learning.
If you want to play more with win32 gui programming, I'd have a look at the WTL (Windows Template Library). It's a C++ wrapper around the win32 API and makes it much easier to reason about what's going on.
I believe MSVC intrinsics will use the rep stos/movs instructions, which are even smaller than calling functions (which includes the size of their import table entries too.)
https://learn.microsoft.com/en-us/cpp/preprocessor/intrinsic...
The padding is needed to make packets hashable and not leak uninitialized bytes.
You just never assume a string is null terminated when reading, using strnlen or strncpy when reading as well.
It's not really suitable for general purpose programming like the OP is doing. It won't null terminate the string if the buffer is filled, which will cause you all sorts of problems. If the buffer is not filled, it will write extra null bytes to fill the buffer (not a problem, but unnecessary).
On freebsd you have strlcpy(), Windows has strcpy_s() which will do what the OP needs. I remember someone trying to import strlcpy() into Linux, but Ulrich Drepper had a fit and said no.
You just never assume a string is null terminated when reading, using strnlen or strncpy when reading as well.
Not really possible when dealing with operating system level APIs that expect and require null-terminated strings. It's safer and less error-prone to keep everything null terminated at all times.
Or just write in C++ and use std::string, or literally any other language. C is terrible when it comes to text strings.
if you don't know how to solve/avoid a problem like that, you will have all sorts of other problems
pound-define strncopy to a compile fail, write the function you want instead, correct all the compile errors, and then, not only move on with your life, never speak of it again, for that is the waste of time. C++ std:string is trash, java strings are trash, duplicate what you want from those in your C string library and sail ahead. no language has better defined behaviors than C, that's why so many other languages, interpreters, etc. have been implemented in C.
strlcpy() came from OpenBSD and was later ported to FreeBSD, Solaris, etc.
Lots of good security & safety innovations came from OpenBSD.
How can a string not Null-terminated ?
What is or was the purpose of providing these instead of the existing Windows C std?
It looks like nowdays ZeroMemory() and RtlZeroMemory() are just macros for memset().
Here's an article on some of the RECT helper functions. Relevant for the 8088 CPU but probably not so much today: https://devblogs.microsoft.com/oldnewthing/20200224-00/?p=10...
Microsoft have always viewed C as just another language, it's not privileged in the way UNIX privileges C. By implication, the C standard library was provided by your compiler and shipped with your app as a dependency on Windows, it wasn't provided by the operating system.
These days that's been changing, partly because lots of installers dumped the MSVC runtime into c:\windows\system and so whether it was a part of the OS or not became blurred and partly because Microsoft got more willing to privilege languages at the OS level. Even so, the Windows group retains a commitment to language independence that other operating systems just don't have. WinRT comes with lots of metadata for binding it into other languages, for example.
I see he's also using fopen/fread/fclose rather than CreateFile/ReadFile/WriteFile/etc.
It's a todo list, not a network service. So what if it's using unbounded strcpy's all over the place? It has basically no attack surface. He wrote it for himself, not for criticism from the HN hoi polloi.
For once maybe take someone's work at face value instead of critiquing every mundane detail in order to feel like the smartest person in the room.
Computers are tools to get stuff done. Sometimes those tools are not pretty.
I place much of the criticism being levied here in the same category as the "we must rewrite 'ls' in Rust for security" nonsense that is regularly praised here.
I didn't point that out so I could be the smartest person in the room and I certainly don't subscribe to the whole rewrite-the-world in rust.
The sheer amount of time I spent debugging problems caused by buffer overruns and other daft problems is immense. It's literal days of my life that could have been saved had safer APIs been created in the first place.
It's a cool toy program and I encourage the learning but maybe let's try and avoid unnecessary problems.
Totally agree with you. I use an excellent PC app called AlomWare Toolbox, and it's the epitome of Win32 design (https://www.alomware.com/images/tab-automation.png), and despite it doing so much it's only about 3 MB in size because of it. No frameworks with it either, just a single executable file. I wish all software were still like this.
Even MFC barely took the edge out. It's amazing how much better Borland built their "Delphi like" C++ library.
> Instead of memset() you've got ZeroMemory(), instead of memcpy() you've got CopyMemory().
Yes. And your best API for opening (anything but files but maybe files as well) is... CreateFile
Aah the memories :)
It’s not that there aren’t cool apps for this stuff, it’s more that I have a trail of data across various todo and notes apps from years of different tools.
One solution to the problem of making things “feel native” is to go all in on letting go of native. Target a different style, be it minimalism, Commodore 64, pixel art, etc… it can be fun that way, especially if it’s mostly just a tool for you.
It displays basic computer info including user name, computer name, OS, model, serial number (service tag), CPU model, memory, IPv4 address and uptime.
I'd also suggest at ATL, to make your life a bit easier without making it much heavier.
So, a honest question from a Linux/Mac guy: what is the Windows-y way to do that?
They still notionally need to run on a Windows machine, although I recall people have managed to run them under wine before.
EDIT: It took me a few reads to parse what the link is saying, so: using the toolchain to compile open source dependencies is fine, even if your codebase is closed source, so long as the closed source part isn't being built with the Build Tools.
[1] https://akr.am/blog/posts/a-guide-to-compiling-programs-on-w...
Meanwhile, on MacOS, everything is an ObjectiveC Object, so if you want to write an app in pure C you can but it's about 1000x more verbose because you've gone an abstraction level deeper than Apple intended, and you essentially have to puppeteer the Objective C class hierarchy to make anything happen. It's incredibly icky.
I don't know why they can't rebase the ObjectiveC class-based API onto a basic win32-style procedural API (technically win32 is also "class-based" but it's minimal). It's part of why I don't see myself porting any of my C code to MacOS any time soon.
The thing is, outside of programmer fuzzies, UIs really, really want to be object-oriented. A tree of unrelated objects sharing some common behaviors describes basically 99% of all UI code. And Toolbox / Carbon really strained for lack of having one. That's actually the one original thought Windows added - window classes.
Personally, the weirdness you feel manipulating Obj-C classes directly from C is how I feel any time I have to define a window class or procedure in user32.dll code[0]. OOP wants dedicated language features, just like how UI wants OOP. You can make do without but it's 2000x less ergonomic.
[0] Or anything to do with GTK/GObject.
I assume this is a typo in the title, OP if you ask dang nicely I’m sure he would be willing to remove the typo.
Noticed this with Code Reviews. To a point, the smaller the code change, the more comments and input. Beyond a certain size, comments and reviews (without nagging or external incentives) trends towards zero.
vardump•17h ago
iforgotpassword•16h ago
sixtram•16h ago
exe34•16h ago
SoftTalker•16h ago
chuckadams•12h ago
mananaysiempre•11h ago
tom_•12h ago
mananaysiempre•11h ago
[1] http://www.malsmith.net/minicrt/
tom_•11h ago
I used to be into this stuff and created a number of useful <10 KB EXEs this way. I'm not sure it's really worth it though! I started out writing code for computers where you'd have like 16 KB free RAM, and this wasn't much, but for most purposes you didn't actually have to sweat every single last byte. So on the same basis, now that even my laptop (which is over 10 years old) has 16 GB RAM, I am not massively inclined to worry about anything less than 1 MB.
ziml77•6h ago
I blame people who constantly comment about how bloated software is now for making overly conscious of program size even when its down at a point where it doesn't matter :')