There is something I like about win32 gui programming. It's a little idiosyncratic, but if you read Raymond Chen's blog you'll see why.
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.
BarryGuff · 8h ago
> There is something I like about win32 gui programming
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.
scripturial · 17h ago
At minimum, these days, if you dont use strncpy instead of strcpy, you’ll have to suffer through every man and his dog (or AI tool) forever telling you to do otherwise. (For me this is one of the main arguments of using zig, a lot of these common pitfalls are minimized by using zig, but c is fine as well)
masternight · 17h ago
Heh, and if you use strncpy() you'll have to suffer through me lecturing you on why strncpy() is the wrong function to use as well.
nly · 17h ago
strncpy is more or less perfect in my line of work where a lot of binary protocols have fixed size string fields (char x[32]) etc.
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.
masternight · 17h ago
Yep, that's intended use case for strncpy().
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.
donnachangstein · 8h ago
> On freebsd you have strlcpy()
strlcpy() came from OpenBSD and was later ported to FreeBSD, Solaris, etc.
masternight · 8h ago
Yup.
Lots of good security & safety innovations came from OpenBSD.
sirwhinesalot · 6h ago
You shouldn't use any of those garbage functions. Just ignore \0 entirely, manage your lengths, and use memcpy.
hkpack · 5h ago
I am not writing in C, but always wondered, why pascal-like strings wrappers are not popular, i. e. when you have first 2 bytes represent the length of the string following by \0 terminated string for compatibility.
renewedrebecca · 42m ago
Pascal originally required you to specify the length of the string before you did anything with it.
This is a totally good idea, but was considered to be too much of a pain to use at the time.
sirwhinesalot · 1h ago
2 bytes is not enough, usually you'll see whole "size_t" worth of bytes for the length.
But you could do something utf-8 inspired I suppose where some bit pattern in the first byte of the length tells you how many bytes are actually used for the length.
masternight · 5h ago
Yeah, Drepper said the same thing.
fsckboy · 14h ago
>It won't null terminate the string if the buffer is filled, which will cause you all sorts of problems.
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.
mrheosuper · 10h ago
I thought string is just byte array that has Null as last element?
How can a string not Null-terminated ?
BenjiWiebe · 3h ago
Whether the string ends in NULL or not is up to you as a programmer. It's only an array of bytes, even though the convention is to NULL-terminate it.
Well maybe more than just a convention, but there is nothing preventing you from setting the last byte to whatever you want.
mrheosuper · 2h ago
Everything in C is just array of bytes, some would argue uint32_t is just array of 4 bytes. That's why we need convention.
A string is defined as byte array with Null at last. Remove the Null and it's not a string anymore.
userbinator · 17h ago
Instead of memset() you've got ZeroMemory(), instead of memcpy() you've got CopyMemory().
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.)
kevin_thibedeau · 12h ago
The standard allows memset/memcpy to be replaced by inline code. There is no need to use non-standard extensions to get a performance boost.
userbinator · 10h ago
That's how the MSVC intrinsics work. Turn on the option and memset/memcpy, among others, gets replaced automatically:
> 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().
I see he's also using fopen/fread/fclose rather than CreateFile/ReadFile/WriteFile/etc.
donnachangstein · 8h ago
> 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.
masternight · 8h ago
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
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.
rcarmo · 17h ago
I spent a lot of time doing that and to be honest, I miss the ability to develop for native UIs with native code.
rlkf · 10h ago
I second this, and just want to add that strsafe.h contains replacements for the runtime string routines.
MortyWaves · 16h ago
> Instead of memset() you've got ZeroMemory(), instead of memcpy() you've got CopyMemory().
What is or was the purpose of providing these instead of the existing Windows C std?
userbinator · 15h ago
It's worth remembering that Windows 1.x and 2.x predates the C89 standard. This also explains why WINAPI calling convention was inherited from Pascal instead of C. The C standard library was "just another competitor" at the time.
mike_hearn · 7h ago
Windows didn't standardize on C. It was mostly assembly and some Pascal in the beginning with C and C++ later.
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.
Narishma · 1h ago
> Windows didn't standardize on C. It was mostly assembly and some Pascal in the beginning with C and C++ later.
No, it was never Pascal. It was always C from the beginning. You may have been confused by them using the Pascal calling convention because it was generally faster on the 16-bit CPUs of the time.
masternight · 16h ago
Those functions explicitly? I can't find any definitive explanation on why they exist.
It looks like nowdays ZeroMemory() and RtlZeroMemory() are just macros for memset().
You could write code without using libc / the C runtime. You still can.
raverbashing · 6h ago
I agree with most of this, but let's be honest, win32 gui programming (like this) is/was a pain
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 :)
electroly · 20h ago
Instead of laboriously calling CreateWindow() for every control, traditionally we would lay out a dialog resource in a .rc file (Visual Studio still has the dialog editor to do it visually) and then use CreateDialog() instead of CreateWindow(). This will create all the controls for you. Add an application manifest and you can get modern UI styling and high-DPI support.
pjmlp · 9h ago
Only UNIX overlaps C standard library with OS library, and back in 1985 (Windows 1.0 release), there was still no standard to speak of.
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.
userbinator · 18h ago
You also get automatic tabbing between controls, and a few other keyboard shortcuts this way. Note that resizing them still needs to be done manually if you want that, but that's usually easy and not more than a few hundred bytes of code.
urbandw311er · 18h ago
Great answer, helpful and not judgemental.
kazinator · 14h ago
However, this approach is easily translatable to a language that has decent FFI, and requires nothing else: no resource compiler and linker to make a resource DLL.
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.
tonyedgecombe · 9h ago
>Visual Studio still has the dialog editor to do it visually
They are using gcc.
electroly · 1h ago
That doesn't matter; you can still use Visual Studio to create the .rc file. This technique still works great for MinGW-based projects. The important thing is that Visual Studio has a .rc dialog editor.
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.
johnisgood · 7h ago
Well, my somewhat extended TUI (ncurses) TODO program is 15K. Linux. Not statically linked though. I did not get around to build ncurses yet with musl.
eviks · 20h ago
> no frameworks
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, ...
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.
Tringi · 16h ago
It's a little more complicated if you are to be using themes, GDI and common controls.
Some time ago I put together this example: https://github.com/tringi/win32-dpi
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
Ah isn't the user32:<windows api functions> a framework not related to 'pure' C?
ghewgill · 17h ago
The colons there don't represent C++. That's just a way of referring to a windows API function that exists in a specific DLL (in this case "user32"). Because the functions used here do not exist in older versions of Windows, the linked code dynamically loads user32.dll and tries to get the address of those functions so they can be called. That's why you need to know which Windows DLL they exist in.
jchw · 18h ago
Arguably, Windows itself is an object oriented UI framework.
sargstuff · 12h ago
Was going to point out would have to be an embedded program that can be booted into without need of an OS; but then program would still rely on various external api/abi interfaces aka bios, drivers & protocols (usb, scsi, pci, ansi, etc). Given today's hardware, think there's not really a realizstic thing as a realistic reall world 'pure C' program -- way to much knowledge / effort even if exclude NDA's. VM environment for 'hardware' as software still has abi interface, so not 'pure' C at source code level.
userbinator · 20h ago
It's "modern" in that it's much bigger than necessary, while missing a lot of functionality.
(A lot of what you mention is missing is trivial to add, especially tabbing between controls.)
card_zero · 20h ago
I think font scaling is fixed (i.e. turned on) with SetThreadDpiAwarenessContext(-4). Or whatever the constant that equates to -4 is called.
AaronAPU · 22h ago
The 6502 programmer in me is dying inside that 278kb now passes as lightweight.
abbeyj · 19h ago
I tried to reproduce this binary to see what the 278 KB was being taken up by. The first obstacle that I ran into was that the build.bat file doesn't work if you have git configured to use core.autocrlf=false. Changing that to core.autocrlf=true and recloning was sufficient to get me building.
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.
If all you are interested in is a small .exe size, there is plenty of room for improvement here.
azhenley · 18h ago
If you had a blog or YouTube channel where you just went around to open source projects optimizing them down, I’d be very interested.
jedimastert · 18h ago
> Maybe the author is using a different toolchain or different settings?
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
tecleandor · 7h ago
I think there's a typo somewhere. The repo and the release says 27KB (not 278).
debugnik · 6h ago
The v0.1 release from yesterday, at the time of posting, was 278 KB. The latest release, v0.3 from 9 hours ago, adds -Os -s and UPX to compress down to 27 KB.
tecleandor · 5h ago
Oh! I arrived late...
tomalbrc · 17h ago
They used mingw, read TFA
abbeyj · 16h ago
I also used mingw and yet I arrived at different results. Maybe it was a different version, or a different distro of MinGW, or a 32-bit vs. 64-bit issue, or I'm linking against a different CRT. Without details from OP, we can't really tell.
jcelerier · 21h ago
A lot of it is due to the platform and executable format. Things can be much more lightweight when there's no information for stack traces, no dynamic linking infrastructure, no exception handling tables (necessary even in C in case exceptions traverse a c function,) etc.
userbinator · 20h ago
no dynamic linking infrastructure
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.
nottorp · 21h ago
Maybe we could petition the demo scene competitions to have a '64kb TODO app' category.
jackjeff · 20h ago
I’m surprised it’s that big to be honest. I was expecting it to be smaller or half the size to be taken by some app icon. I remember writing this kind of stuff back in the days and it was smaller.
Is it due to MinGw maybe?
mhd · 20h ago
This reminds me of the days when all of a sudden win32 programming in assembly became hip enough, probably as a response to the increasing size of shareware downloads ('twas the dark time of MFC).
Combined with early Palm Pilot 68k programming, those were the last hurrahs of non-retrocomputing asm I can remember.
kgabis · 21h ago
6502? Luxury! In my times you were lucky to have a processor.
pineaux · 20h ago
A processor? Luuuxury! In my time we worked twenty-six hours a day, did all the calculations with pen and paper and would be thrilled to use an abacus!
psychoslave · 20h ago
Ah, back in my early existence, we didn't have time and all these superficial dimensions. Ontological creation out of nothing was all one needed, but it looks like it's all lost art now.
sterlind · 5h ago
Atrus? is that you?
p0w3n3d · 20h ago
you had paper? We had to use sand and sticks!
p0w3n3d · 20h ago
Btw. I love how you people refer to this sketch so seamlessly!
No comments yet
Borg3 · 21h ago
Hehe :) Okey.. I have sth easier to write.. but smaller:
15kB quickrun.exe :)
C, pure Win32 API.. No hacks to shrink binary, Mingw32 compiler.
Its GUI app to quickly launch any application via alias.
stevekemp · 20h ago
I'm spending some time this evening debugging a failure I have with an emulator I've written - it emulates a system running a Z80 processor with 64k of RAM.
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.
webprofusion · 14h ago
For some reason it's built with GCC instead of using MSVC, and there are no optimizations enabled (for speed or size).
p0w3n3d · 20h ago
it's about ABI, also 8bit << ... << 64bit architecture. Every pointer is 8 times longer. Don't complain, just admire. It's an art.
tonyarkles · 20h ago
That barely fits on a 5 1/4” floppy!
fx1994 · 8h ago
try to open "new" calculator on win10/win11. It's like loading another operating system... useless.
alternatex · 20m ago
Probably JIT-ing? Compiled against native should make startup instant. No idea how they compile these.
toxi360 · 20h ago
Hello friends, I made this app just to try it out and have some fun, haha, but the comments are right, something like this could have been done more sensibly with C++ or other languages, ahaha.
tomtomtom777 · 20h ago
This is exactly how I've learned to create my first Windows programs about 30 years ago, except that I'd use a C++ compiler.
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.
cesarb · 15h ago
> 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.
drooopy · 20h ago
Unironically, I would rather use your to-do app over the default Windows 11 one.
toxi360 · 20h ago
AHhhahah thanks
Johanx64 · 18h ago
It's just the way it should be.
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.
This is exactly the sort of project (clean, native UI) that motivated me to learn programming, kudos!
transcriptase · 17h ago
Seeing a lot of chirps in here from people who work on software or websites that load megabytes of JS or C# or in order to send 278kb of telemetry every time the user moves their mouse.
lostmsu · 16h ago
A similar app in C# + WinForms is under 10KB on disk and 6MB RAM. This app takes 1.5MB RAM. Both start instantly.
throwaway2037 · 15h ago
Impressive. Can you share a link to the source code?
phendrenad2 · 12h ago
The fact that you can do everything in C when developing a Windows app always makes me feel all warm and fuzzy. Building up from the lowest-level primitives just makes sense.
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.
kmeisthax · 9h ago
They did have a procedural API, it was called Carbon; which was a nearly drop-in replacement for the Macintosh Toolbox API that user32.dll blatantly copied.
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.
vparikh · 22h ago
Looks like you are linking to static libraries. You should link to DLL not to static libraries - this is will cut down on the application size dramatically.
TonyTrapp · 21h ago
That seems backwards. If you need to ship the DLLs with the program anyway (they are not part of the operating system, after all), they will contain their full functionality, each one potentially with its own C runtime, etc. If you statically compile everything into a single EXE, there will be only a single C runtime, all unused functions can be trivially removed, etc.
DLLs only reduce size if their code is meant to be shared between different programs.
throwanem · 21h ago
> they are not part of the operating system
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.
TonyTrapp · 21h ago
The CRT is not part of the operating system, unless you count the UCRT on Windows 10 onwards (yes there is also a MSVCRT copy in the Windows folder that Microsoft strongly discourages you from using, so let's ignore that for now). So unless you link against the system-provided UCRT, you will have to either ship a dynamic or a static copy of the C runtime, and linking it statically will be smaller because that will only contain the few string and time functions used by the program, instead of the whole CRT.
jug · 21h ago
Why would you not link against the UCRT if you’re intentionally designing for Win32 though? It’s a decade old library by now. Time flies…
TonyTrapp · 21h ago
Some people value being able to support older OSes without shipping the entire UCRT as an optional component. Granted, this will become less and less relevant in the future...
throwanem · 21h ago
The idea that something which ships with the OS, as a compatibility shim for legacy but still intentionally supported applications, can be called "not part of the OS," is religious. Strongly discouraged or otherwise, I believe it to be in use here.
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.
TonyTrapp · 21h ago
The important thing in the context of the original question is that mingw uses its own C runtime (MSVCRT is old and incomplete - you would not be able to use it as a drop-in replacement for a modern CRT). You can link the mingw CRT statically or dynamically, and OP suggested that linking it dynamically would reduce the size. But that is only true for the main executable. You would still have to ship the mingw CRT alongside, which in return would increase the distribution size of the program again.
I hope that clears my point up.
userbinator · 20h ago
MSVCRT works perfectly fine for something with this feature set.
TonyTrapp · 19h ago
I really didn't want to get into the specifics, because the situation is already complex enough without MSVCRT, but: Yes, it is. If you wanted to do a size-optimzied version of this particular program, it would be a good enough choice.
TonyTrapp · 21h ago
Regarding your edit: the CRT is responsible for headers such as time.h and string.h, which you can find being used in todo.h. Their implementations are not provided by the operating system but by the C runtime typically supplied by the compiler (mingw in this particular case). Other headers such as windows.h belong to the WinAPI, and result in functions being imported from OS libraries such as user32.dll. They are obviously not linked statically into the program.
Const-me · 14h ago
> such as time.h and string.h
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.
throwanem · 20h ago
Ugh, thanks. That clears it 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.
tacker2000 · 20h ago
i get you, but this also introduces a whole other set of issues, like "dependency hell", where if you update one package you need to re-jig everything else. Things were just moving much slower back "in the days"
throwanem · 20h ago
Dep version clashes were a problem until iirc some time about mid-late last decade, say ~2017.
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.)
90s_dev · 20h ago
You must not have worked with Win32 or C recently, they both tend to be giant balls of fishhooks and hair.
edit: Saw that you just now edited your comment, glad we're on the same page now.
throwanem · 20h ago
Quite literally, it would seem! What an entirely remarkable coincidence.
90s_dev · 20h ago
Call it a coincidence all you want, we both know you just copy/pasted my comment into your own edit.
throwanem · 19h ago
Bold, I'll give you that. Did we both get that from the UNIX-HATERS Handbook? I believe it originally described Perl, and not at all unjustly.
90s_dev · 19h ago
Don't even have to go that far, it's word for word in Perl's own documentation. They've become self aware at some point it seems.
throwanem · 18h ago
And who could say it had been too long coming?
pjmlp · 19h ago
Not really, because this is Windows we are talking about.
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.
TonyTrapp · 19h ago
This code is not from the Petzold books. It literally includes and uses string.h, stdlib.h and time.h. It is not using WinAPI equivalents of C functions.
pjmlp · 11h ago
Exactly why there is still enough room to be a smaller executable.
Dwedit · 19h ago
No, linking to a static version of the CRT is a good thing, it cuts out the unused code. If you dynamic link to MSVCRxx/VCRUNTIME, you force the user to download that exact DLL from Microsoft. Dynamic linking to MSVCRT doesn't have that problem, but it's very hard to do in Visual Studio.
The only time you really can't static link to the CRT is LGPL compliance?
TonyTrapp · 19h ago
Note that the project is using mingw, so it's not using any Microsoft DLLs for the CRT anyway. mingw brings its own CRT along.
Dwedit · 19h ago
I thought Mingw defaulted to MSVCRT.DLL as the CRT?
TonyTrapp · 18h ago
It looks looks mingw actually has several options to base its own CRT on (including MSVCRT, UCRT and various VS runtimes). Still, in the context of OP's original post, we are talking about the mingw DLLs that may or may not redirect most of their duties to Microsoft DLLs, depending on how mingw is configured.
dvdkon · 21h ago
If you're going for a small EXE, I'd recommend telling GCC to optimise for size with "-Os".
Link-Time Optimisation with "-flto" might also help, depending on how the libraries were built.
RavSS · 21h ago
I'd suggest `-Oz` instead, as it optimises for size above all else at the cost of performance, unlike `-Os` which is less aggressive (but likely produces similar code anyway). `-Oz` is somewhat new if I remember correctly, so it depends on the GCC version.
smusamashah · 17h ago
The title reminds of recently released really fast file explorer called File Pilot. Made in C and weighs 1.8MB only https://filepilot.tech/
webprofusion · 14h ago
Nostalgia: My first job in 1997 was a windows apps in C++, it was weather software used on ships and oil rigs, we used to ship updates on floppy disk via helicopter.
lucasoshiro · 14h ago
Every time I see something in C for Windows I see people using MinGW, gcc and friends just like they would do in a Unix-like system. But I wouldn't expect that they are tools that Microsoft recommends for developing on Windows.
So, a honest question from a Linux/Mac guy: what is the Windows-y way to do that?
dwattttt · 14h ago
A cursory browse says there's no Linux-isms in the code base, so the Windows-y way to build that (without going into licensing) would be to use the Visual Studio Build Tools. They're the CLI toolchain you get for Visual Studio, but free when compiling open source projects (as of recently: https://devblogs.microsoft.com/cppblog/updates-to-visual-stu...)
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.
lucasoshiro · 14h ago
Thanks!
thefilmore · 9h ago
I wrote a guide [1] that collects this information in one place.
I use Pelle's C. Never seen it mentioned here. Based (a long time ago) on LCC. I think it's sweet.
debugnik · 2h ago
I remember Pelles C being the first full C11 implementation, which I felt was impressive for such an unpopular toolchain.
I guess they can't switch to a FOSS licence because of the licence of LCC? How much of the original code even remains after more than 20 years and several C standards later?
p0w3n3d · 20h ago
Great respect! I've tried many times, without final result. I'll try to use this for learning purposes!
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)
rfl890 · 18h ago
There's no application manifest for the common controls so it will look outdated
burnt-resistor · 17h ago
Contains numerous memory leaks, doesn't permit arbitrarily long lists, and saves and restores uninitialized data. Really sloppy.
userbinator · 20h ago
That's more than 10x bigger than I expected, given that all it seems to do is manipulate a list view. Something like this should be doable in under 10KB.
leecommamichael · 7h ago
Odd that there’s so much conversation about this. Why is that? Genuine question.
Plasmoid2000ad · 6h ago
I find frequently the smaller and simpler the subject, the more people can easily confidently grasp it and generate quick to share opinions.
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.
scripturial · 17h ago
The allure of the perfect notes and todo app. Having gone through phases of various modern todo and note apps over the years, I’ve finally let it go and decided to embrace just using text files. (Neovim for me, not that it matters which text editor one uses)
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.
ghewgill · 17h ago
If text files are your world, then http://todotxt.org/ might be for you. I'm currently using "pter".
formerly_proven · 22h ago
If you add a manifest you’ll get post-Windows-2000 GUI styling.
Dwedit · 19h ago
You can do it without a manifest too, it involves calling "CreateActCtxA" and "ActivateActCtx" with the appropriate Activation Context object. Manifest file is much easier.
layer8 · 20h ago
But why would you possibly want that? ;)
MortyWaves · 21h ago
I’d be interested in seeing a PR for that. I’ve always found the manifests a bit confusing.
hard_times · 21h ago
can you elaborate?
fredoralive · 21h ago
As well as information about side by side assemblies (ie: library versions), a Windows manifest file has various settings and a declaration of compatible versions of Windows that affect Windows' handling of the app, such as whether it can handle paths over MAX_PATH, if it is hi-dpi aware, or if it knows about themed controls.
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.
torstenvl · 21h ago
In my experience, the wxWidgets documentation and forums are pretty good resources for Windows manifest files. YMMV.
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.
torstenvl · 20h ago
Strongly disagree. There's way too much mystery and magic number usage in the official documentation.
crimsontech · 19h ago
Can you recommend any good alternatives for someone looking to learn programming using windows APIs?
formerly_proven · 4h ago
The magic numbers (hashes and UUIDs) sort of make sense because there's a slightly adversarial interaction between developers and Microsoft. If you just had a compatibleWindowsMin and compatibleWindowsMax field with version numbers, people would just go ahead and put "9999" in the max field, and then OS upgrades break applications again. By using UUIDs instead, an application developer can't intentionally or unintentionally declare compatibility with unreleased Windows versions.
vardump · 22h ago
I think this Win32 app could be done in C under 20 kB.
iforgotpassword · 22h ago
Iirc mingw links its libc statically, that's probably why. Fwiw I still remember that an empty VB6 project with one form compiles to 20kb (16 if you select "optimize for size"). It still needs its VM, which is a dll that was considerably larger. I think about 1mb, but it's been a while.
sixtram · 21h ago
We shipped our VB6 software on a 1.44 MB floppy disk. Actually, we had to add some random stuff to the installer to make our software fit on two floppies instead of one, so that customers would think our application was more complex. Our CEO wanted to make sure that the end user had to eject disk 1 and insert disk 2 during the installation. Sometimes you want to go big. So size matters.
SoftTalker · 21h ago
Similar to programs like TurboTax that add delays and “working” animations to make the user think something really complicated is being achieved.
exe34 · 21h ago
That's amazing! It reminds me of when a client once asked to make the red more red. I asked him to look at it again the next day and he said it was much better. I didn't have to do anything.
tom_ · 17h ago
I built it with VC++ and it's 23,552 bytes. Close enough. Command line:
So, this uses the DLL version of the Microsoft CRT. Don’t know if that counts as cheating :) Why is a CRT needed here anyway, beyond very basic stuff like startup code and memcpy?.. Is MiniCRT[1] an option?
It possibly isn't, and you can typically do without by telling VC++ to build without linking to it - though the compiler has a bad habit of generating calls to memcpy, so you may have to provide your own implementation of that (and hope the compiler doesn't spot what you're doing and helpfully replace it with a call to memcpy). But aside from that, it's easy enough. You don't need the CRT startup code; you can use WriteConsole to print to stdout/stderr; you can use LocalAlloc/LocalFree to allocate memory; there's a few string routines in the Win32 DLLs that are a bit like the C equivalents, if nothing in the ordinary Win32 API does what you want.
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 · 12h ago
The downside to me knowing how small programs can be if I don't link the CRT is that I can't help but feel like what I've written is terribly bloated if I don't make an effort to avoid the CRT.
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 :')
chuckadams · 17h ago
I remember TheGun, a Win32 notepad app in 6K, written in MASM. Anyone know where to find it?
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.
Here is one of few Windows apps that I have written, albeit in C#. compinfo is a single, 431 KB windows executable.
It displays basic computer info including user name, computer name, OS, model, serial number (service tag), CPU model, memory, IPv4 address and uptime.
This is a blast from the past and it is neat to see some bare bones coding projects, but as others have said this is hardly "modern".
Disposal8433 · 21h ago
> A modern, native Windows Todo application
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.
musjleman · 20h ago
> those global variables...
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.
pjmlp · 19h ago
To note that even the latest editions of the famous Petzold book, before the C# edition, switched to using the common C subset from C++ and compiling with Visual C++ in C++ mode, instead of plain old C.
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.
trinix912 · 21h ago
> Use std::string and std::array or std::list /.../
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.
pjmlp · 19h ago
In that case, make yourself a favour and use WIL, aka Windows Implementation Library.
It’s not modern but there’s some value to programming close to the metal so you understand what’s really going on. The problem domain is simple and easy to keep in your head. Good learning exercise. I wonder if there’s an assembly language version of something like this.
dataflow · 17h ago
You'll definitely want to allow theming on your controls, if for no other reason than to let them feel native like the rest of the OS.
I'd also suggest at ATL, to make your life a bit easier without making it much heavier.
dochtman · 18h ago
Curious what this would look like written in Rust, using the windows-rs bindings.
_bin_ · 19h ago
Fun project! I think the smallest I ever shrunk a win32 application was on the order of 2-4kb by writing in ASM. It was a great illustration of why 10x binary size is actually a great trade-off in terms of productivity.
thecaio · 16h ago
Never easy to pull these off, so congrats! App might be modern in the sense that you coded it in 2025, but looks straight out of Windows 98
re-lre-l · 10h ago
It should be written UPPER CASE, in Pascal, maybe...
nsxwolf · 18h ago
The Readme emojis tell me this was vibe coded.
ghewgill · 20h ago
I think the hard limit of 100 todos is the best feature of this. Why don't other todo apps have this feature?
userbinator · 17h ago
I think 127 would make more sense.
jonny_eh · 20h ago
Some of us have a lot to do!
pshirshov · 19h ago
If only I can do the same across 3 desktop and two phone platforms...
slicktux · 21h ago
Simple and elegant!
yapyap · 9h ago
Title says 278 KB, github says 27 KB.
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.
hudo · 20h ago
Less LOC than React/Redux app... Makes you think, what were we doing last 30 years :/
Kwpolska · 18h ago
I’m pretty sure an app as mediocre as this would take up less code in React, or even plain JavaScript. The UI is a single table and a few inputs and buttons, and its main way of communicating with the outside world is message boxes - trivial to do in a web browser.
toxi360 · 15h ago
Right now it's only 27 kb and I've added the manifest file :)
pcunite · 20h ago
Back in the day I used to use UPX to compress my executables to achieve impressively small sizes.
jackjeff · 20h ago
I remember doing that for some custom installer I wrote. It felt like a good idea for 5mins until it got flagged by a bunch of anti virus software… had to sign the installer in the end and spent a lot of time reporting false positives.
dorianmariecom · 20h ago
ready for enterprise todo
tippytippytango · 21h ago
The pedantry in the comments of a todo app is exquisite. HN never disappoints.
Very nostalgic OP, warms my heart 10/10
bitwize · 19h ago
Hell to the yes!
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
saurik · 19h ago
I'm apparently so ancient I am just looking at that line of code thinking "isn't that the standard entrypoint for a Windows app?", lol; is there something else to know? ;P Oh! I guess, staring at it, isn't that wrong?... if you use LPCTSTR, one would expect that you are trying to be agnostic to UNICODE, but then you also have to use _tWinMain, no? (And, after considering that, I went and double-checked, and the C is also incorrect in this context.)
keepamovin · 20h ago
Interesting! I was just looking at raylib for this kind of thing: super light weight, cross platform, reliable, ideally C-based method to get GUI.
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.
thehias · 20h ago
278kb? you are doing something very wrong, this should be possible in 10kb!
musjleman · 20h ago
The actual code in the repo definitely compiles to less than 10k. The rest is bloat from linking CRT statically.
By "actual code" I meant the assembly that the application logic compiles down to, not the entire executable. But as far as the entire package goes, compiling it using clang with some flags I can get down to 19.5k without any effort. If I wanted to waste time on this, ripping out the CRT entirely and getting it to 16k would probably take less than an hour.
fizlebit · 19h ago
Me no like inconsistent use of spaces.
x += labelW+20;
hDescEdit = createModernEdit(hwnd, x, y, editW, btnH, ID_DESC_EDIT);
x += editW + gap;
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.
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.
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.
strlcpy() came from OpenBSD and was later ported to FreeBSD, Solaris, etc.
Lots of good security & safety innovations came from OpenBSD.
This is a totally good idea, but was considered to be too much of a pain to use at the time.
But you could do something utf-8 inspired I suppose where some bit pattern in the first byte of the length tells you how many bytes are actually used for the length.
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.
How can a string not Null-terminated ?
Well maybe more than just a convention, but there is nothing preventing you from setting the last byte to whatever you want.
A string is defined as byte array with Null at last. Remove the Null and it's not a string anymore.
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...
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.
What is or was the purpose of providing these instead of the existing Windows C std?
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.
No, it was never Pascal. It was always C from the beginning. You may have been confused by them using the Pascal calling convention because it was generally faster on the 16-bit CPUs of the time.
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...
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 :)
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.
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.
They are using gcc.
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.
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?
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
(A lot of what you mention is missing is trivial to add, especially tabbing between controls.)
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.
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
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.
Is it due to MinGw maybe?
Combined with early Palm Pilot 68k programming, those were the last hurrahs of non-retrocomputing asm I can remember.
No comments yet
15kB quickrun.exe :) C, pure Win32 API.. No hacks to shrink binary, Mingw32 compiler.
Its GUI app to quickly launch any application via alias.
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.
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.
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.
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.
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.
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.)
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?
Link-Time Optimisation with "-flto" might also help, depending on how the libraries were built.
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...
I guess they can't switch to a FOSS licence because of the licence of LCC? How much of the original code even remains after more than 20 years and several C standards later?
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)
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.
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.
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.
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.
[1] http://www.malsmith.net/minicrt/
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.
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 :')
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.
[0] https://news.ycombinator.com/showhn.html
It displays basic computer info including user name, computer name, OS, model, serial number (service tag), CPU model, memory, IPv4 address and uptime.
https://github.com/jftuga/compinfo
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.
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.
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.
https://github.com/microsoft/wil
I'd also suggest at ATL, to make your life a bit easier without making it much heavier.
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.
Very nostalgic OP, warms my heart 10/10
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
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"
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.
Can you share how you can compile to only 10kb?