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