masternight
3 days ago
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
2 days 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.
bmacho
a day ago
Is the font-size adjustable? It's too small on my screen
scripturial
3 days 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
3 days 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
3 days 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
3 days 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
2 days ago
> On freebsd you have strlcpy()
strlcpy() came from OpenBSD and was later ported to FreeBSD, Solaris, etc.
masternight
2 days ago
Yup.
Lots of good security & safety innovations came from OpenBSD.
sirwhinesalot
2 days ago
You shouldn't use any of those garbage functions. Just ignore \0 entirely, manage your lengths, and use memcpy.
hkpack
2 days 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.
sirwhinesalot
2 days 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.
renewedrebecca
2 days 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.
int_19h
2 days ago
You still need a 0-terminated string to pass to API of most libraries (including ones included with the OS - in this case, Win32).
masternight
2 days ago
Yeah, Drepper said the same thing.
fsckboy
3 days 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
3 days ago
I thought string is just byte array that has Null as last element?
How can a string not Null-terminated ?
BenjiWiebe
2 days 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
2 days 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.
MaxBarraclough
2 days ago
> Everything in C is just array of bytes, some would argue uint32_t is just array of 4 bytes
That isn't how the C language is defined. The alignment rules may differ between those two types. Consider also the need for the union trick to portably implement type-punning in C. Also, the C standard permits for CHAR_BIT to equal 32, so in C's understanding of a 'byte', the uint32_t type might in principle correspond to just a single byte, in some exotic (but C compliant) platform.
No doubt there are other subtleties besides.
int_19h
2 days ago
That's only one possible convention, and it's not a particularly good one at that.
userbinator
3 days 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
3 days 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
3 days ago
That's how the MSVC intrinsics work. Turn on the option and memset/memcpy, among others, gets replaced automatically:
https://learn.microsoft.com/en-us/cpp/preprocessor/intrinsic...
rcarmo
3 days 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.
codebolt
3 days ago
> 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
2 days 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
2 days 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.
Suppafly
2 days ago
>I certainly don't subscribe to the whole rewrite-the-world in rust.
Good because those Rust people get really upset when you point out that Rust mostly seems to exist for people to "Rewrite X in Rust".
int_19h
2 days ago
To be fair, CreateFile etc are a lot more verbose than fopen.
codebolt
12 hours ago
Oh yes, all those parameters are absolutely a pain to work with. But it can still be good to have an understanding of what options are abstracted away by fopen etc. Trying to write an app only using <Windows.h> can be a good learning exercise if you want to understand the fundamentals of the OS.
raverbashing
2 days 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 :)
int_19h
2 days ago
> It's amazing how much better Borland built their "Delphi like" C++ library.
As I recall, it wasn't "Delphi like", but rather literally the same VCL that Delphi used. That's why C++Builder had all those language extensions - they mapped 1:1 to the corresponding Delphi language features so that you could take any random Delphi unit (like VCL) and just use it from C++. In fact, C++Builder could even compile Delphi source code.
pjmlp
a day ago
Yes, and it grew out of Object Windows Library, which also add extensions, and was definitly much more pleasant to use than MFC has ever managed to.
No need for the past tense, both products are still on the market with frequent releases and developer conferences, even if no longer at the same adoption level.
int_19h
a day ago
I remember OWL being somewhat weird in that it rendered quite a few stock controls itself, in ways that made OWL apps really stick out on Win 3.11.
MFC gets a lot of flak, but I think that a large chunk of it is undeserved because it's a fundamentally different kind of framework - a wrapper that tries to streamline the use of underlying APIs without concealing their fundamental nature, whereas OWL and VCL (and VB6, and WinForms) are higher-level wrappers that do quite a lot for you even when they use native widgets under the hood. From that perspective, if anything, the more appropriate criticism of MFC is that it tries to do too much - e.g. that whole document/view thing is clearly a high-level abstraction that always felt out of place to me given the overall design of the framework. WTL is basically what MFC tried to be but failed.
pjmlp
a day ago
That was optional, it just turned out too many people liked to enable custom rendering.
That is exactly the reason why many of us dislike MFC, its low level wrapping of Windows APIs.
With OWL you could already have a kind of C# like experience, but in Windows 3.x, that is how far ahead the experience was versus MFC.
This is the tragedy of C++ frameworks, the tooling could be as good as VB, Delphi, Java, .NET, but then we have a big chunk of developers that insist in pushing for low level Cisms instead.
Honestly I never see much uptake on WTL, especially because dealing with ATL was already bad enough.
From the outside it feels like the chain of command at Microsoft has some big issue with producing great GUI development experiences for C++ developers.
When they finally nailed it, having a C++ Builder like experience, it was killed in the name of extensions by a rebel group, that nowadays is having fun writing Rust bindings for Windows APIs.
MortyWaves
3 days 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
3 days 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.
int_19h
2 days ago
The WINAPI calling convention is a cross between C and Pascal - C-style order of arguments on the stack, but Pascal-style callee cleaning the stack before return.
The reason for its use in Windows is that it makes generated code slightly smaller and more efficient, at the cost of not supporting varags easily (which you don't need for most functions anyway). Back when you had 640 Kb of RAM, saving a few bytes here and there adds up quickly.
masternight
3 days 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().
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...
mike_hearn
2 days 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
2 days 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.
pjmlp
a day ago
Apple was the one going with Pascal for the OS, originally the Object Pascal linage was started at Apple, in collaboration with Niklaus Wirth that gave feedback on the design.
lmz
3 days ago
You could write code without using libc / the C runtime. You still can.
int_19h
2 days ago
Unlike Unix, Windows historically didn't have a standard C runtime at all. Stuff like MSVCRT.DLL etc came later (and are themselves implemented on top of Win32 API, not directly on top of syscalls as is typical in Unix land).
rlkf
3 days ago
I second this, and just want to add that strsafe.h contains replacements for the runtime string routines.