That crtdll.dll and msvcrt10.dll which use OEM code page in C locale by default can be really confusing. But on the other hand, this is fine from C point of view (runtime can use whatever wants). There are ways how to query code page via CRT functions, so it is fine. Properly written C or POSIX application should not expect any specific locale or codepage.
Since WinNT3.5 and Win95, FAT stores filename two times, once in old shortname 8.3 format which is in OEM code page, and second time in new LFN format which is UTF-16 with space of 520 bytes (somehow limited just to 255 code words). So changing system OEM code page does not change file names (unless they are stored only in shorname 8.3 format). The only one exception in FAT which is always stored in shortname format is LABEL. So there can be funny things that FAT flash disk can show different LABEL identification based on Windows system language. As CRT codepage is not used for filenames, the remaining possible usage is the console input / output. And here by default all Windows versions are using OEM codepage for interaction with console. So my guess is that older CRT versions are using by default OEM codepage, so console input and output can be "correctly" processed without need to use conversion function. As written in that older MS SetFileApisToOEM() documentation, that function was there to allow passing result of FindFirstFileA() into the WriteConsoleA(). So switching file apis to OEM, using old crtdll.dll and msvcrt10.dll (heh, at the release time it was new CRT) ensured that every char* was in OEM codepage and no conversion was needed. On Thursday 01 January 2026 01:15:10 Kirill Makurin wrote: > Thanks for testing. I should have chosen better wording. > > I was aware that CRT functions which accept filenames depend on > `AreFileApisANSI` and do not use LC_CTYPE's code page (which is very > confusing if you ask me; assuming most people have no idea about > `AreFileApisANSI`), missing only on detail that when CRT locale is set to > UTF-8 it ignores `AreFileApisANSI` and uses UTF-8. > > With newer CRTs, assuming that AreFileApisANSI() == TRUE by default, in most > cases calling setlocale(LC_ALL, "") will set locale to active ANSI code page > (GetACP); notable exception is pre-UCRT with GetACP() == CP_UTF8. This way, > passing filenames to both Win32 -A and CRT (_open/fopen) functions will give > correct results. > > With crtdll.dll and msvcrt10.dll, which by default use OEM code pages, they > will disagree in code page used by such functions. "Confusing" would be more > appropriate that "non-sensual". > > Please correct if I'm wrong, but doesn't FAT use active OEM code page for > filenames? Maybe it has to do with using OEM code pages as default when > setting ancient CRT's locales? > > - Kirill Makurin > ________________________________ > From: Pali Rohár <[email protected]> > Sent: Thursday, January 1, 2026 9:42 AM > To: Kirill Makurin <[email protected]> > Cc: mingw-w64-public <[email protected]> > Subject: Re: [Mingw-w64-public] [PATCH 1/3] crt: Improve > __mingw_filename_cp() to work on systems without AreFileApisANSI() function > > Well, it is questionable whether it is non-sensual. Just file apis do > not use CRT LC_CTYPE codepage on all CRT versions (except when it is set > to UTF-8 in UCRT). > > I tried to do that test on Win95 too, so you would have full image. > But the test said that it does not map to any character, which is very > suspicious. After debugging the test, I figured out that it failed on > CreateFileW call -- which returned NULL, not the INVALID_HANDLE_VALUE (-1). > > CreateFileW is unsupported on Win9x but does not signal error via value > INVALID_HANDLE_VALUE (-1) as documented, but via value NULL (0). > > NULL handle is also invalid, so maybe we should be more careful when > calling winapi functions that they returned handle which is not NULL. > > So I tried different test. Create 3 new different files via 3 different > calls: > > #include <stdio.h> > #include <io.h> > #include <fcntl.h> > #include <sys/stat.h> > #include <locale.h> > #include <errno.h> > #include <windows.h> > > int main() { > HANDLE handle; > int fd; > > printf("WinAPI ANSI codepage: %u\n", GetACP()); > printf("WinAPI OEM codepage: %u\n", GetOEMCP()); > > if (GetACP() != 1250 || GetOEMCP() != 852) { > printf("This test is specially for ANSI codepage 1250 and OEM > codepage 852\n"); > return 1; > } > > handle = CreateFileA("C:\\1_\xED.txt", 0, 0, NULL, CREATE_ALWAYS, > FILE_ATTRIBUTE_NORMAL, NULL); > if (handle == NULL || handle == INVALID_HANDLE_VALUE) { > printf("Cannot create file C:\\1_\\xED.txt: %lu\n", GetLastError()); > return 1; > } > CloseHandle(handle); > > fd = _open("C:\\2_\xED.txt", _O_RDONLY | _O_CREAT, _S_IREAD | _S_IWRITE); > if (fd < 0) { > printf("Cannot create file C:\\2_\\xED.txt: %lu\n", errno); > return 1; > } > _close(fd); > > printf("Calling setlocale\n"); > setlocale(LC_ALL, ""); > > printf("CRT codepage: %s\n", strchr(setlocale(LC_CTYPE, NULL), '.')); > > fd = _open("C:\\3_\xED.txt", _O_RDONLY | _O_CREAT, _S_IREAD | _S_IWRITE); > if (fd < 0) { > printf("Cannot create file C:\\3_\\xED.txt: %lu\n", errno); > return 1; > } > _close(fd); > > printf("Done\n"); > return 0; > } > > And the result on Win95 is: > > WinAPI ANSI codepage: 1250 > WinAPI OEM codepage: 852 > Calling setlocale > CRT codepage: (null) > Done > > All 3 files on FAT filesystem were created as LFN in UTF-16 and contains > codepoint U+ED. Hence WinAPI and CRT before and CRT after setlocale used > ANSI codepage. (OEM would result in U+DD codepoint) > > Thanks to UTF-16 LFN support in FAT, it is possible to inspect the real > encoding. > > This behavior seems to be consistent across all Windows versions, and > matches what we have in __mingw_filename_cp() function. > > Hopefully this now answer all your questions regarding the file apis > encodings. > > On Wednesday 31 December 2025 22:13:36 Kirill Makurin wrote: > > So, we just discovered another non-sensual Windows behavior... With ancient > > CRTs on ancient systems `setlocale (LC_ALL, "")` will set locale to OEM > > code page, but FileAPIs will use ANSI code pages. > > > > I still wonder how Win9x system would behave (aren't they DOS-based? I'm > > sorry if I'm mistaken), but I guess there's nearly zero practical value in > > testing them, or is it? > > > > I actually followed behavior of using OEM code pages by default with > > crtdll.dll and msvcrt10.dll in my library. Now I question whether I should > > use ANSI code pages by default with all CRTs... > > > > - Kirill Makurin > > ________________________________ > > From: Pali Rohár <[email protected]> > > Sent: Thursday, January 1, 2026 4:09 AM > > To: Kirill Makurin <[email protected]> > > Cc: mingw-w64-public <[email protected]> > > Subject: Re: [Mingw-w64-public] [PATCH 1/3] crt: Improve > > __mingw_filename_cp() to work on systems without AreFileApisANSI() function > > > > Ok, you give me an interesting idea, so I decided to test it today. > > I prepared the oldest Windows NT 3.1 version which is available here: > > https://winworldpc.com/product/windows-nt-3x/31 > > configured system to ANSI CP 1250 and OEM CP 852 and then compiled and > > run following test code against the system crtdll.dll: > > > > #include <stdio.h> > > #include <io.h> > > #include <fcntl.h> > > #include <locale.h> > > #include <windows.h> > > > > /* > > * UNICODE code point U+ED = LATIN SMALL LETTER I WITH ACUTE (í) > > * UNICODE code point U+DD = LATIN CAPITAL LETTER Y WITH ACUTE (Ý) > > * CP1250 0xED = LATIN SMALL LETTER I WITH ACUTE (í) > > * CP1250 0xDD = LATIN CAPITAL LETTER Y WITH ACUTE (Ý) > > * CP852 0xA1 = LATIN SMALL LETTER I WITH ACUTE (í) > > * CP852 0xED = LATIN CAPITAL LETTER Y WITH ACUTE (Ý) > > */ > > > > int main() { > > HANDLE handle; > > int fd; > > > > printf("WinAPI ANSI codepage: %u\n", GetACP()); > > printf("WinAPI OEM codepage: %u\n", GetOEMCP()); > > > > if (GetACP() != 1250 || GetOEMCP() != 852) { > > printf("This test is specially for ANSI codepage 1250 and OEM > > codepage 852\n"); > > return 1; > > } > > > > handle = CreateFileW(L"C:\\\xED", 0, 0, NULL, CREATE_ALWAYS, > > FILE_ATTRIBUTE_NORMAL, NULL); > > if (handle == INVALID_HANDLE_VALUE) { > > printf("Cannot create file \\xED: %lu\n", GetLastError()); > > return 1; > > } > > CloseHandle(handle); > > > > handle = CreateFileW(L"C:\\\xED", 0, 0, NULL, OPEN_EXISTING, > > FILE_ATTRIBUTE_NORMAL, NULL); > > if (handle == INVALID_HANDLE_VALUE) { > > printf("Cannot open created file \\xED: %lu\n", GetLastError()); > > DeleteFileW(L"C:\\\xED"); > > return 1; > > } > > CloseHandle(handle); > > > > handle = CreateFileA("C:\\\xDD", 0, 0, NULL, OPEN_EXISTING, 0, NULL); > > printf("WinAPI CreateFileA \\xDD is mapped to UNICODE \\xED: %s\n", > > handle != INVALID_HANDLE_VALUE ? "yes" : "no"); > > if (handle != INVALID_HANDLE_VALUE) CloseHandle(handle); > > > > handle = CreateFileA("C:\\\xED", 0, 0, NULL, OPEN_EXISTING, 0, NULL); > > printf("WinAPI CreateFileA \\xED is mapped to UNICODE \\xED: %s\n", > > handle != INVALID_HANDLE_VALUE ? "yes" : "no"); > > if (handle != INVALID_HANDLE_VALUE) CloseHandle(handle); > > > > handle = CreateFileA("C:\\\xA1", 0, 0, NULL, OPEN_EXISTING, 0, NULL); > > printf("WinAPI CreateFileA \\xA1 is mapped to UNICODE \\xED: %s\n", > > handle != INVALID_HANDLE_VALUE ? "yes" : "no"); > > if (handle != INVALID_HANDLE_VALUE) CloseHandle(handle); > > > > fd = _open("C:\\\xDD", _O_RDONLY); > > printf("CRT _open \\xDD is mapped to UNICODE \\xED: %s\n", fd >= 0 ? > > "yes" : "no"); > > if (fd >= 0) _close(fd); > > > > fd = _open("C:\\\xED", _O_RDONLY); > > printf("CRT _open \\xED is mapped to UNICODE \\xED: %s\n", fd >= 0 ? > > "yes" : "no"); > > if (fd >= 0) _close(fd); > > > > fd = _open("C:\\\xA1", _O_RDONLY); > > printf("CRT _open \\xA1 is mapped to UNICODE \\xED: %s\n", fd >= 0 ? > > "yes" : "no"); > > if (fd >= 0) _close(fd); > > > > printf("Calling setlocale\n"); > > setlocale(LC_ALL, ""); > > > > printf("CRT codepage: %s\n", strchr(setlocale(LC_CTYPE, NULL), '.')); > > > > fd = _open("C:\\\xDD", _O_RDONLY); > > printf("CRT _open \\xDD is mapped to UNICODE \\xED: %s\n", fd >= 0 ? > > "yes" : "no"); > > if (fd >= 0) _close(fd); > > > > fd = _open("C:\\\xED", _O_RDONLY); > > printf("CRT _open \\xED is mapped to UNICODE \\xED: %s\n", fd >= 0 ? > > "yes" : "no"); > > if (fd >= 0) _close(fd); > > > > fd = _open("C:\\\xA1", _O_RDONLY); > > printf("CRT _open \\xA1 is mapped to UNICODE \\xED: %s\n", fd >= 0 ? > > "yes" : "no"); > > if (fd >= 0) _close(fd); > > > > DeleteFileW(L"C:\\\xED"); > > return 0; > > } > > > > > > The output is: > > > > WinAPI ANSI codepage: 1250 > > WinAPI OEM codepage: 852 > > WinAPI CreateFileA \xDD is mapped to UNICODE \xED: no > > WinAPI CreateFileA \xED is mapped to UNICODE \xED: yes > > WinAPI CreateFileA \xA1 is mapped to UNICODE \xED: no > > CRT _open \xDD is mapped to UNICODE \xED: no > > CRT _open \xED is mapped to UNICODE \xED: yes > > CRT _open \xA1 is mapped to UNICODE \xED: no > > Calling setlocale > > CRT codepage: .852 > > CRT _open \xDD is mapped to UNICODE \xED: no > > CRT _open \xED is mapped to UNICODE \xED: yes > > CRT _open \xA1 is mapped to UNICODE \xED: no > > > > > > So from this test it can be seen that the CRT code page after setlocale > > is the OEM one, but both WinAPI -A and CRT _open functions for filenames > > are using ANSI codepage. > > > > So it matches the __mingw_filename_cp() implementation. > > > > Hence filenames are another exception in CRT which do not follow CRT > > codepage, but rather the codepage returned by our helper > > __mingw_filename_cp() function. > > > > > > I have found old MS documentation for SetFileApisToOEM() which says: > > https://web.archive.org/web/20001206100700/http://msdn.microsoft.com/library/psdk/winbase/filesio_5xv1.htm > > > > "The 8-bit console functions use the OEM code page by default. > > All other functions use the ANSI code page by default." > > > > I was searching a bit more and I have found another documentation, not > > sure what is the source, but says the same thing and contains old note: > > http://winapi.freetechsecrets.com/win32/WIN32SetFileApisToOEM.htm > > > > "On Win32s all file APIs are ANSI." > > > > So it looks like that for file names are ancient systems using > > only ANSI, but for console functions they are using only OEM. > > > > On Wednesday 31 December 2025 09:53:38 Kirill Makurin wrote: > > > I have a funny feeling that ancient systems which do not have > > > AreFileApisANSI may use OEM code page for filenames, but I do not have a > > > way to verify it. > > > > > > Remember that crtdll.dll and msvcrt10.dll use OEM code pages by default, > > > this makes me think that such ancient system would use OEM code pages in > > > general. > > > > > > - Kirill Makurin > > > ________________________________ > > > From: Pali Rohár <[email protected]> > > > Sent: Wednesday, December 31, 2025 3:29 AM > > > To: [email protected] > > > <[email protected]> > > > Subject: [Mingw-w64-public] [PATCH 1/3] crt: Improve > > > __mingw_filename_cp() to work on systems without AreFileApisANSI() > > > function > > > > > > It the AreFileApisANSI() function is not available then fallback to the > > > default value that ANSI (ACP) encoding for filenames is used. > > > --- > > > mingw-w64-crt/misc/__mingw_filename_cp.c | 17 ++++++++++++++--- > > > 1 file changed, 14 insertions(+), 3 deletions(-) > > > > > > diff --git a/mingw-w64-crt/misc/__mingw_filename_cp.c > > > b/mingw-w64-crt/misc/__mingw_filename_cp.c > > > index f6de486e983b..4fc92fcb269f 100644 > > > --- a/mingw-w64-crt/misc/__mingw_filename_cp.c > > > +++ b/mingw-w64-crt/misc/__mingw_filename_cp.c > > > @@ -10,9 +10,20 @@ > > > #include <windows.h> > > > #include <locale.h> > > > > > > +/* By default the ANSI (ACP) is used, fallack to default ANSI when > > > function AreFileApisANSI() is not available */ > > > +static BOOL WINAPI fallbackAreFileApisANSI(VOID) { return TRUE; } > > > + > > > unsigned int __cdecl __mingw_filename_cp(void) > > > { > > > - return (___lc_codepage_func() == CP_UTF8) > > > - ? CP_UTF8 > > > - : AreFileApisANSI() ? CP_ACP : CP_OEMCP; > > > + if (___lc_codepage_func() == CP_UTF8) > > > + return CP_UTF8; > > > + > > > + /* Function AreFileApisANSI() is not available in older Windows > > > versions, so resolve it at runtime */ > > > + static BOOL (WINAPI *myAreFileApisANSI)(VOID) = NULL; > > > + if (!myAreFileApisANSI) { > > > + HMODULE kernel32 = GetModuleHandleA("kernel32.dll"); > > > + FARPROC farproc = kernel32 ? GetProcAddress(kernel32, > > > "AreFileApisANSI") : NULL; > > > + (void)InterlockedExchangePointer((PVOID*)&myAreFileApisANSI, farproc > > > ?: fallbackAreFileApisANSI); > > > + } > > > + return myAreFileApisANSI() ? CP_ACP : CP_OEMCP; > > > } > > > -- > > > 2.20.1 > > > > > > > > > > > > _______________________________________________ > > > Mingw-w64-public mailing list > > > [email protected] > > > https://lists.sourceforge.net/lists/listinfo/mingw-w64-public _______________________________________________ Mingw-w64-public mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/mingw-w64-public
