Coming back from .NET Framework, it feels like Win32 API was invented by some rather cold people with total disregard for the well being of the application programmers. Every other function is a minefield designed to suck time and energy from its user.
Let’s start small. GetEnvironmentVariable. When given a small or NULL buffer, it returns the number of characters in the string including the null terminating character. When given a large enough buffer, it returns number of characters in the string not including the null terminating character. Of course, there are probably good philosophical reasons for that, but for a programmer who just wants to get the environment variable and be done with it this looks like simple lack of consistency.
Next level: CreateDirectory. In .NET it creates all subdirectories recursively if necessary: if I asked for
c:\foo\bar\baz, and I only have
c:\foo\bar would be created for me as well. Win32 requiers the application programmer to create each subdirectory separately. It means that we will have to manually parse the path, find the deepest subdirectory that is already created, and create its children, in order. This may seem like an easy task, but it’s not, especially in C or C++ where any string manipulation is pain. Breaking the path into parts is only half the problem. We also need to deal with variety of corner cases. Leading slash cannot be ignored:
\foo is not the same as
foo. Trailing slash usually must be ignored, but
c: is different from
\\foo\bar does not have a parent directory, it’s lexical parent
\\foo is not a directory at all.
con\foo is invalid path, since “con” cannot be used as file name. To get it all right you’ll need patience, good knowledge the corner cases, and some unit tests. It is unreasonable to demand this from every application programmer who just wants to create a directory, and to expect him to repeat this in every application.
Next level: CreateProcessW. Why does it have to write to the command line buffer? What value does it provide to the user? Was it a performance optimization? If so, it’s a bad one. We are creating a process, which typically means reading files and stuff. Copying a few hundred bytes to an internal buffer will have neglibile effect on performance in this case. Modifying otherwise logically read-only input, on the other hand, has profound implications. In particular, it causes an exception if said input resides in read-only memory, e.g. hard coded in the executable code. Small savings for Win32 developers, huge pain for everyone else.
Going even higher: CommandLineToArgvW. It is supposed to parse command line into an argument list. This is the most dangerous kind of API call: it sort of works, but not always, and fails when you need it the most. The documentation warns that the input must start with a valid file name, or the behavior may be unpredictable. The warning itself is buried deep in the documentation and is quite confusing, but that’s not an API design problem, so we will ignore it. This warning must be taken seriously. For example, given input string
a.exe b" a"z, the output is
["a.exe", "ba z"], which is correct given Windows command line parsing rules. But if the input is just
b" a"z, then the output is
["b\" a", "z"], and that is not correct at all. To add insult to injury,
lpCmdLine passed to WinMain does not begin with the executable path. Also, where is the ANSI version? Do I not need to parse command line in ANSI programs? The bottom line is, the way it is implemented, this function is virtually useless.
.NET Framework has much fewer trapdoors of this kind, at least on the basic API level, and revisiting Win32 API makes one really thankful for that.
CreateDirectory is too ancient. Use SHCreateDirectoryEx instead.)
There are a few problems with that. First, this function is deprecated (https://docs.microsoft.com/en-us/windows/desktop/api/shlobj_core/nf-shlobj_core-shcreatedirectoryexw), while CreateDirectory is not. Second, it is written with Wi does apps in mind, so it might create a Windows queue on the calling thread, even if I given NULL hWnd. Or not, but I would not want to risk it.