I've done a lot of Win32 development in C and C++.
People here are saying don't expect it to be Unix, and that's fine. When I write for Windows, everything is PWSTR and that's fine.
But I have seen a lot of people get tripped up on this. People who are not well versed in the issue have no idea that using fopen() or CreateFileA() will make their program unable to access the full range of possible filenames, varying with the language setting. I've absolutely seen bugs arise with this in the 21st century.
The old "ACP" is meant for compatibility with pre-unicode software, but that hasn't been a big concern for something like 20 years. It is a much bigger issue that people are unaware of this history and expect char* functions to just work.
I have done a lot of Win32 as well, and recently started a new project after a long haitus. The problem is there are some very occasional things that use char*, and so you have to keep that in mind: for example, some parameters in wWinMain, many cross-platform libraries that I want to link to, or some of the C++ library functions mentioned in that article. Converting between char* and wchar_t requires being a bit careful not to cause a buffer overflow. It's a lot of mental overhead to deal with it all, but you're right that if you're mainly just calling Win32, then sticking with the wide version of functions and strings will mostly be fine. Microsoft could certainly make this easier by offering a "wWin32" project that only has the wide version of libraries and documentation to prevent mistakes.
> Microsoft could certainly make this easier by offering a "wWin32" project that only has the wide version of libraries
Using API functions ending with ...A (for the ANSI version) like CreateFileA is actually rather lowlevel style and means that you know what you do (the same holds for the ...W versions) and explicitly want it this way.
Normally, you use CreateFile and Visual C++ by default sets the proper preprocessor symbols such that CreateFileW will be used. If you pass a char*, you will get compile errors.
In other words: the infrastructure is there to avoid this kind of errors - and it is even used by default. But if you make it explicit in your code that the ANSI version of an API function (CreateFileA) should be used instead of using CreateFile, don't complain that the compiler does obey and this turns out to be a bad idea.
This used to be the case, but not anymore, I think. If you look at MSDN docs these days, they explicitly document ...A and ...W functions separately. If you create a new Win32 project in Visual Studio, it's halfway through - sometimes it will call the ...W functions explicitly, other times it will use the macros. But overall it feels like the intent is to push devs towards explicitness.
The macros in question were always a bad idea, because they applied to all identifiers. So e.g. if you had a method named CreateFile in your class somewhere, and somebody did #include <windows.h> along with the header defining that class, you'd get a linker error.
To me, it's stylistically very weird to include the W in source code that humans will read and write, because the fact that it's the unicode version is immaterial, and should appear on literally every API call. If macros are a problem, I feel like the ideal situation would be to declare without the W, and use some compiler or linker hint to append the 'W' to the symbol. (Kind of reminds me of how this topic is approached when doing interop from .NET)
Not every call - only those that take TCHAR arguments, or structs with TCHAR fields.
Why do you think it's immaterial, though? It was back when A-funcs were considered legacy, and W-funcs were the way to go. But now the official stance is that A-funcs are for use with UTF-8. Being explicit about whether you're handling UTF-8 or UTF-16 strikes me like a good idea.
As you say, at various points the A funcs were talked about as legacy or deprecated. WinCE did not have them. During the win7 era refactor of things like kernel32 and advapi, they were also kind of deprioritized, left in the compatibility wrapper and not the first class interfaces. I haven't followed in recent years but when WinRT was coming on the scene they were talking about not including them?
But I think of the canonical name as not having either A or W. Rather what you get for TCHAR is a per compilation unit setting, and the A or W in the linker name is sort of an implementation detail. That is to say I'd internalized the world the macros presented, and labelled that as "what makes sense". In particular it is distracting from the semantics of the APIs to have the string type show up in the name.
If they could do c++ style name mangling in plain C I feel like they might've and it'd be somewhat more "pure". The macro thing feels like sort of a hack to achieve this.
You're absolutely correct, this is all how it used to work. The flip to UTF-8 support and the use of A-funcs for that is very recent; the ability of the app to force itself into this mode first showed up in 2019. The naming doesn't really make much sense anymore, but then that wouldn't be the first for a 35-year old API set...
People here are saying don't expect it to be Unix, and that's fine. When I write for Windows, everything is PWSTR and that's fine.
But I have seen a lot of people get tripped up on this. People who are not well versed in the issue have no idea that using fopen() or CreateFileA() will make their program unable to access the full range of possible filenames, varying with the language setting. I've absolutely seen bugs arise with this in the 21st century.
The old "ACP" is meant for compatibility with pre-unicode software, but that hasn't been a big concern for something like 20 years. It is a much bigger issue that people are unaware of this history and expect char* functions to just work.