NTFS文件系统中的文件名采用什么编码方式存储?

52

我刚开始处理WinXP系统上带有非英文名称的文件名的编程工作。我已经阅读了一些关于Unicode的推荐资料,我认为我基本理解了,但有些部分对我来说仍然不是很清晰。

具体来说,NTFS中存储的文件名称(而不是内容,而是文件的实际名称)采用哪种编码(UTF-8、UTF-16LE/BE)?是否可以使用接受char*的fopen()打开任何文件,还是我只能使用使用wchar_t*并假定采用UTF-16字符串的wfopen()?

我尝试手动将UTF-8编码的字符串提供给fopen(),例如:

unsigned char filename[] = {0xEA, 0xB0, 0x80, 0x2E, 0x74, 0x78, 0x74, 0x0}; // 가.txt

FILE* f = fopen((char*)filename, "wb+");

但是结果变成了 'ê°€.txt'。

我曾经认为(可能是错误的观念)使用UTF8编码的字符串可以打开Windows下的任何文件名,因为我模糊地记得一些Windows应用程序传递(char*)而不是(wchar_t*),没有遇到问题。

有人能解释一下这个吗?


PHP的行为从PHP 7.1开始发生了变化,请参见https://dev59.com/rXI_5IYBdhLWcg3wF_F3#38466772。 - Gogowitsch
“我曾经认为(可能是错误的)UTF8编码的字符串足以在Windows下打开任何文件名。” - Windows不支持UTF-8编码的文件名,只支持UTF-16和ANSI(内部转换为UTF-16)。然而,仅包含ASCII字符的UTF-8文件名将作为ANSI字符串工作。 “我似乎模糊地记得一些Windows应用程序传递(char),而不是(wchar_t),并且没有问题” - char *并不意味着UTF-8,但可以用于它。没有标准的Win32或C / C ++文件API接受UTF-8作为输入,但第三方库可能会接受。 - Remy Lebeau
3个回答

42

NTFS将文件名存储为UTF-16格式,然而fopen使用的是ANSI格式(不是UTF-8)。

如果要使用UTF16编码的文件名,需要使用文件打开调用的Unicode版本。在项目中定义UNICODE_UNICODE,然后使用CreateFile调用或wfopen调用。


16
如果将项目更改为使用UNICODE定义进行构建的变化过大,您可以在非Unicode版本的构建中调用wfopen()CreateFileW() - Michael Burr
2
鉴于Windows NT和NTFS比UTF-16标准更古老,是否有可能使用旧的UCS-2呢? - hillu
5
NTFS允许使用除0x0000以外的任何16位值作为名称编码的序列。这意味着支持UTF-16代码点,但是文件系统不会检查序列是否为有效的UTF-16。 - user
2
@hillu Win32 Unicode函数使用wchar_t字符串。NT和NTFS可能早于UTF-16,但在Windows上可以同时使用UCS-2和UTF-16,而Microsoft从Win2K开始迁移到UTF-16以摆脱UCS-2。 - Remy Lebeau

15

fopen() - 在Windows上的MSVC(默认情况下)不接受utf-8编码的char*。

不幸的是,utf-8在宏观上相对较近才被发明。Windows API分为Unicode和Ansi版本。每个与字符串有关的Windows API实际上都有W或A后缀-W表示“宽字符”/Unicode,A表示Ansi。宏定义使开发人员无需了解区别即可使用带有char *或wchar_t *的CreateFile调用建立配置。

'Ansi'编码实际上不是特定的编码方式:但意味着用于"char"字符串的编码方式是针对PC的本地设置而定的。

现在,由于C运行时函数(例如fopen)需要在没有开发人员知识的情况下正常工作,在Windows系统上它们希望接收其字符串以Windows本地编码形式提供。 MSDN表明Microsoft C运行时API setlocal可以更改当前线程的区域设置-但明确表示对于需要每个字符超过2个字节(例如utf-8)的任何区域设置将失败。

因此,在Windows上没有捷径。您需要使用wchar_t *字符串(通过wfopen或本机API CreateFileW),或者使用Unicode生成设置创建项目并只使用Createfile调用。


2
实际上,有一个快捷方式:您可以将UTF-8字符串转换为Unicode,使用GetShortPathNameW创建一个仅包含ASCII的“短路径名”,然后将其传递给fopen。这是将非ASCII文件名传递给仅使用fopen打开文件的旧版库(或使用可移植C编写的库)的唯一方法。 - user4815162342
1
每个处理字符串的Windows API实际上都有W或A后缀-W代表"宽字符/Unicode",A代表Ansi。大多数函数都有,但不是所有函数都有。那些存在已久的函数,特别是在Windows还是ANSI-based时期就存在的函数,肯定有。但是最近几年引入的新函数以及未来的函数往往只有Wide版本,而没有W后缀。Microsoft希望逐步淘汰ANSI。 - Remy Lebeau

8
正如其他人所回答的,处理UTF-8编码的字符串的最佳方法是将它们转换为UTF-16并使用本地Unicode API,例如_wfopenCreateFileW
然而,在调用无条件使用fopen()的库时,这种方法并不适用,因为它们不支持Unicode或者是可移植C语言编写的。在这种情况下,仍然可以利用传统的“短路径”将UTF-8编码的字符串转换为ASCII形式,以便与fopen一起使用,但需要进行一些工作:
  1. 使用MultiByteToWideChar将UTF-8表示转换为UTF-16。
  2. 使用GetShortPathNameW获取ASCII-only的“短路径”。GetShortPathNameW会返回一个带有全ASCII内容的宽字符串,您需要通过无损复制每个wchar_t char进行转换为窄字符串。
  3. 将短路径传递给fopen()或最终将使用fopen()的代码。请注意,该代码(如果有)打印的错误消息将引用丑陋的“短路径”(例如KINTO~1而不是kinto-un-筋斗雲)。
虽然这不是一个长期推荐的策略,因为Windows短路径是一项遗留功能,可以按卷关闭,但这可能是将文件名传递给使用fopen()和其他文件相关API调用(stataccessCreateFile的ANSI版本等)的代码的唯一方法。

1
太棒了,你救了我们,谢谢!! - Eric
处理UTF-8编码的字符串...将它们转换为Unicode。UTF-8(和UTF-16)是Unicode编码。我想你的意思是转换为UTF-16。 - leonbloy
1
@leonbloy 是的,我指的是Windows所定义的Unicode。第一点明确了需要UTF-16编码。我现在已经修改了答案,从一开始就提到了UTF-16。 - user4815162342
短路径解决方案只适用于读取文件,而不适用于写入,对吗? - skjerns
1
@skjerns 这种策略也可以用于编写。只需使用 open(name, 'w').close() 创建一个具有所需名称的空文件,然后继续进行配方即可。 - user4815162342

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接