https://docs.microsoft.com/zh-cn/windows/desktop/FileIO/naming-a-file
命名文件、路径和名称空间
Windows 支持的所有的文件系统都使用文件和目录的方式来访问磁盘或者设备上的内容。开发人员应该了解,windows API 中的各种规则,约定,以及文件、目录的名称限制。
可以通过文件I/O API 访问磁盘、设备或网络共享上的数据。文件和目录,以及命名空间,都是路径的概念的一部分,路劲即一个字符串,它代表了统一的该去哪里找到数据的规则,而不是针对不同的存储方式使用不同的表示方法(磁盘、设备、网络连接等)。
一些文件系统,比如NTFS,支持链接文件和目录,像普通的文件和目录一样,其也遵守文件命名约定和规则。
文件和目录名
所有的文件系统遵循单个文件的相同通用命名约定:一个基本的文件名称+可选的文件扩展名,中间用“.”分割。但不同的文件系统,它们有特定的规则,关于如何构建路径的各个独立的部分,以及如何构成路径或文件。目录只是具有特殊属性的文件,它需要遵守与常规文件相同的命名规则。因为术语目录,简单的指文件系统中一种特殊的文件,一些资料将使用通用术语文件,来概括目录和数据文件的概念。因此,除非特别指明,任何对于文件的命名或使用规则或例子,对于目录来说都是适用的。术语路径,一个或多个目录,反斜杠,以及可选的卷名。
字符数限制的规则比较复杂,依赖于底层的文件系统和所使用的目录名前缀格式。因为向后兼容机制,这一部分特别复杂。比如,更老的MS-DOS FAT文件系统来说,基本文件名最长8字符,扩展名最多3 字符,包括”.”在内,做多12 字符。这被称作8.3 文件名。FAT 和 NTFS 文件系统没有8.3 的限制,因为它们已经有了长文件名的支持,但它们依然支持8.3 版本的长文件名。
名称转换
下面的基本规则使得应用程序有能力在任何文件系统上,创建和处理合法的文件、目录名。
- 使用“.” 来分割基本文件名和扩展名
- 使用反斜线分离path 中的组件。Blackslash 将文件名从路径中分割出来,并将目录中单个的目录名分割出来。不能在实际的目录或文件名中使用反斜线,因为它是保留字符,用于将名称分割成单个的组件。
- 使用反斜线作为盘符的一部分,比如:”C:\path\file” 中的”C:\”,或 “\\server\share\path\file”中的\\server\\share,这是通用命名惯例的一部分。
- 不要做,大小写敏感的假设。NTFS 支持POSIX 的大小写敏感的语义,但是,默认是不支持的
- 卷别名(驱动器号)不区分大小写。
- 代码页上的几乎所有字符,包括扩展字符集中的字符(128-255)都是可用的,但不可用下面的字符:
- 数值0.
- 整数表示在1~31的字符,除非允许这些字符的备用数据流。
- 目标文件系统所不支持的字符。
- 使用“.”表示当前目录,比如”.\temp”
- 使用”..”表示上层目录
- 别使用下列的保留文件名
- CON, PRN, AUX, NUL, COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, and LPT9.
- 也不要使用这些名字,再加一个扩展名。
- 不要在文件或目录名的末尾加”.”,但是支持在文件的刚开始添加”.”,比如”.temp”
文件短名和长名
长文件名,即,扩展8.3 文件名得到的文件名。当你创建一个长文件名的时候,windows 可能会创建一个8.3 的别名,并存储在磁盘上。这个8.3 别名可以在系统范围或者某个磁盘上禁止,取决于具体的文件系统。
Windows Server 2008, Windows Vista, Windows Server 2003 and Windows XP: 8.3别名在特定的卷上是不可以被取消的。直到windows 7 和 windows server2008 R2,才可以取消。
许多文件系统上,当文件名太长,需要以8.3 格式显示,将包含一个“~”字符。
并不是所有的文件系统支持,“~”转换,且系统可以配置为关闭8.3 别名。不要默认系统上已经存在了8.3 别名。
从系统获取文件的8.3 文件名,长文件名,或者全路径名时,考虑下面的三个方法:
- 8.3 ,GetShortPathName
- 从短得到长文件名,GetLongPathName
- 获取长文件名,即从相对路径得到全路径等操作,详情看msdn,GetFullPathName
在较新的文件系统上,比如NTFS,exFAT,UDFS和FAT32,windows 使用Unicode 存储长文件名到磁盘上,这意味着,原始的长文件名肯定是被保留的。
拥有长文件名的文件可以在NTFS 和 FAT 文件系统的分区上互相拷贝,而不会丢失文件名信息。对于老的MS-DOS FAT 和 一些CDFS 文件系统来说,这并不可保证,取决于实际的文件名。这种情况下,如果可能的话,短文件名将被取代。
路径
一个文件的路径,包含一个或多个组成部分,被反斜线分割,每一个组成部分代表了一个目录名或文件名(但有一些意外情况将在下面讨论).系统对于路径的开头或前缀的解释是十分重要的,这个前缀决定了路径所使用的命名空间,以及路径中的哪个位置使用了那些特殊字符,包括最后一个字符。
如果路径中的一部分是一个文件名,那它必须是最后一个部分。
路径中的每一个部分都受限于特定的文件系统的最大长度限制。通常,规则分为两类:短和长。目录名被文件系统以特殊文件的方式存储,但是,文件的命名规则也会应用到目录名。总的来说,路径代表了组成路径的所有的目录和文件的一种等级关系。
全路径和相对路径
尽管一些API 要求全路径,windows 中的一些操作文件的API 支持相对路径。如果一个文件名不以下面所示的开场,那它将是相对于当前路径的:
- 以”\\” 开始的UNC 名
- 一个磁盘代号加反斜线,比如“C:”
- 当个反斜线,比如:“\directory” 或 “file.txt”被认为是绝对路径
如果文件名为“C:” 或者其它磁盘直接加文件名,则,认为是相对文件。”C:tmp.txt”,指磁盘C: 上的当前目录下的tmp.txt 文件名。”C:tempdir\tmp.txt” 类似。
如果一个目录包含”..”,那么它也被认为是相对路径。”..”表示当前目录的上层目录。
支持如下操作:
“C:..\tmp.txt”
最大路径长度限制
Windows API 中,最大路径长度是MAX_PATH(例外情况将单独讨论),定义为260。本地路径有如下顺序的组成部分:
驱动器号,冒号,反斜线,由反斜线分割的名称组成部分,最后的空字符。
例如:D 盘上的最长的路径为:
“D:\some256-character path string”
NT 风格的文件名中,Windows API 将”/” 转换为”\”,以作为部分的文件路径。但是使用“\\?\”的使用是例外。不能使用”//?//”
Windows API 中的一些函数拥有Unicode 版本,其可以支持扩展长度的路径,最大可达到32767 字符。这种类型的组件由反斜线分割的组成部分构成,每个部分的长度限制为:GetVolumeInformation 返回的lpMaximumComponentLength 信息。当需要指定扩展长度的路径,使用“\\?\”前缀,比如,“\\?\D:\very long path”。
“\\?\”前缀也可以被用于UNC,如果想指定我们的函数就是使用的UNC,可以使用\\?\UNC\前缀。这些前缀并不是路径本身的一部分。它们指出,路径应该尽量少的修改,不可以使用“/”代替”\”,不可以使用”.”代表当前目录,不可以使用“..”表示父目录。因为你不能使用”\\?\”前缀+相对路径,相对路径被限制了MAX_PATH 的长度。
无需对路径和文件字符串执行任何Unicode 规范化以供windows 文件I/O API 使用,因为文件系统将路径和文件名视为不透明的WCHAR 序列。
使用API 创建目录时,指定的路径不能太长,以至于无法附加8.3 文件名(即目录名不能超过MAX_PATH减去12)
Shell 和 文件系统的需求不同,使用windows API 是可以创建shell 用户接口无法解析的路径的。
从 windows 10,1607 开始,MAX_PATH 限制从普通的win32 文件和目录函数中移除了。
一个注册表键可以控制是否开启新的长路径行为。
HKLM\SYSTEM\CurrentControlSet\Control\FileSystem LongPathsEnabled (Type: REG_DWORD)
键值在进程第一次调用一些win32 文件或目录函数后,将缓存这个值。
通过组策略将有机会控制这个值。
也可以通过应用程序的manifest 文件对单个app 配置其行为。
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
<ws2:longPathAware>true</ws2:longPathAware>
</windowsSettings>
</application>
下面的这些函数,将没有MAX_PATH 限制,如果app 选择接受新的长路径行为:
CreateDirectoryW,CreateDirectoryExW,GetCurrentDirectoryW,RemoveDirectoryW,SetCurrentDirectoryW。
类似的,文件管理函数:
CopyFileW, CopyFile2, CopyFileExW, CreateFileW, CreateFile2, CreateHardLinkW, CreateSymbolicLinkW, DeleteFileW, FindFirstFileW, FindFirstFileExW, FindNextFileW, GetFileAttributesW, GetFileAttributesExW, SetFileAttributesW, GetFullPathNameW, GetLongPathNameW, MoveFileW, MoveFileExW, MoveFileWithProgressW, ReplaceFileW, SearchPathW, FindFirstFileNameW, FindNextFileNameW, FindFirstStreamW, FindNextStreamW, GetCompressedFileSizeW, GetFinalPathNameByHandleW.
命名空间
Win API中使用的,有两种主要类型的命名空间分类,NT 命名空间和Win32 命名空间。NT 命名空间被设计为最底层的命名空间,以承载其它的子系统和命名空间,比如,Win32 子系统,以及扩展的Win32 命名空间,POSIX 子系统等。之前版本的windows 还定义了几个预定义的或预留的名字给确定的特殊设备,比如通信端口,和默认显示控制台,现在它们被称为NT 设备命名空间。
Win32 文件命名空间
这些例子是为了让Win API 使用的,不一定能在Windows Shell 应用程序上,比如windows Explorer 上使用。
对于文件I/O 操作来说,”\\?\”前缀,告诉win API 不要解析字符串,直接将之后的字符串传递给文件系统。比如,如果系统支持大路径和文件命名,使用此种方式来传递路径下去即可。
因为它关闭了自动扩展,使用”\\?\”后,可以在路径名中使用”..”和”.”字符。但并不是所有的I/O API 支持”\\?\”,自行参考MSDN
Win32 设备命名空间
\\.\ 前缀将访问win32 设备命名空间,这样可以直接访问物理磁盘和卷,而不经过文件系统。可以通过这种方式访问除了磁盘的很多设备。
比如,通过”COM1”或者\\.\COM1来访问COM1,因为COM1-COM9 是NT 命名空间中预留的,当你想访问COM56,智能通过\\.\COM56的方式。
再比如,使用CreateFile 传入\\.\PhysicalDiskX 或者 “\\.\CdRomX” 。这将允许你透过文件系统直接访问这些设备。这些设备名时在系统枚举这些设备的时候创建的。一些驱动也会在系统中创建一些别名。比如,实现名称“C:\”的设备驱动程序具有自己的命名空间,该命名空间也恰好是文件系统。
NT 命名空间
大部分情况下,我们没有必要通过API 直接操作NT 命名空间,WinObj 可以比较直观的浏览windows NT 命名空间。根目录为”\.”,
子目录”Global??”就是Win32 命名空间所在的地方。
命名设备对象在NT 命名空间中的位置为“Device”子目录。
Device 子目录下,你可能会看到Serial0 和 Serial1,该设备对象代表了前两个COM 端口,如果它当前在你系统上。
代表卷的设备对象看起来应该类似:”HarddiskVolume1”,数字后缀可能不同。
“Harddisk0” 下的”DR0”是代表磁盘的设备对象的一个例子。
为了让Win32 API 访问这些设备对象,设备驱动在Win32 命名空间创建了代表设备对象的一个符号链接。比如,”Global??”下的COM0 和 COM1简单的链接到了Serial0 和 Serial1。”C:”被链接到HarddiskVolume1, "Physicaldrive0" 对应” DR0”。
没有链接的话,任何Windows APP 将无法访问该设备。支持NT 命名空间绝对路径(比如”\Device\Xxx”)的API 可以获得一个设备的句柄。
随着通过终端服务和虚拟机增加的多用户支持,在Win32 命名空间来虚拟化系统范围的根设备变得更加有必要。这通过在Win32 命名空间下添加名为“GLOBALROOT” 的链接来实现。可以使用\\?\GLOBALROOT 访问,这个前缀确保在它后面增加的路径看起来是在真的系统对象管理器的根路径下,而不是会话相关的路径。