在.NET 4.7中使用长路径时出现目录未找到异常

15

我已在本地组策略编辑器中设置了启用 Win32 长路径已启用,并重新启动计算机。

下面是代码:

string path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
for (int i = 0; i < 10; i++)
    path += "\\" + new string('z', 200);
Directory.CreateDirectory(path);

我遇到了错误:

System.IO.DirectoryNotFoundException:“找不到路径'C:\ Users ... \ Desktop \ zzzzzzzzzz ...

(实际上这是一个奇怪的错误消息。)

app.config已经拥有:

<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7" />

更多信息(可能不太重要)

我尝试按照此文章和其他地方的说明(虽然评论中指出在使用 .net 4.7 时不需要),将configuration标签下的内容添加到 app.config 中:

<runtime>
  <AppContextSwitchOverrides value="Switch.System.IO.UseLegacyPathHandling=false;Switch.System.IO.BlockLongPaths=false" />
</runtime>

依然是同样的错误。

如果我仅使用一个zzzzzz...,则会在桌面上创建它而没有错误。

我正在使用VS2017,Windows 10。 我尝试过Winforms和WPF。


1
这不是4.7的错误,而是Windows可用文件路径的限制。默认情况下,它被硬编码为260个字符。 - eocron
4
UseLegacyPathHandling 设置为 false 时,与 @eocron 无关。 - NtFreX
8
你需要说服操作系统相信你知道自己在做什么。 - Hans Passant
1
@HansPassant,我现在也重新启动了计算机,并尝试使用DirectoryInfo来创建目录。仍然不行。 - ispiro
1
我刚刚尝试了您的代码(启用了长路径),并将CreateDirectory移动到循环中,只是为了查看错误发生的时间:它发生在第一次迭代之后(因此它创建了一个目录而没有子目录)。 - Max Play
显示剩余15条评论
3个回答

19

周年更新(RS1)存在一个漏洞,允许长路径在没有清单的情况下工作。对于任何更新的Windows系统,您必须将"应用程序清单文件"项添加到您的项目中,否则它将无法工作。

<application xmlns="urn:schemas-microsoft-com:asm.v3">
  <windowsSettings>
    <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
  </windowsSettings>
</application>

2
谢谢。+50 (+25)。如果您有相关来源,那就更好了。但无论如何 - 这解决了问题。这解释了为什么有些人似乎没有这个问题,而我却有。因为我正在运行Windows 10创作者更新版。 - ispiro
ASP .Net 应用程序怎么样? - Mike W

2
这可能不能回答你的问题,但可以为你提供一个解决方法。我在Ubuntu Linux下使用mono 4.5测试了你的片段,效果非常好,但在Windows下情况可能会有所不同。根据这篇文章这篇文章,.NET Framework本身不支持长路径,因此应该归咎于它。

因此,正如@Anastasiosyal在这个StackOverflow答案中建议的那样,解决方案是依赖于Windows Api本身。有两种方式:直接绕过或Api调用。
Directory.CreateDirectory(@"\\?\" + veryLongPath);

API调用(代码不是我的,我从@Anastasiosyal的答案中获得):

// This code snippet is provided under the Microsoft Permissive License.
using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern SafeFileHandle CreateFile(
    string lpFileName,
    EFileAccess dwDesiredAccess,
    EFileShare dwShareMode,
    IntPtr lpSecurityAttributes,
    ECreationDisposition dwCreationDisposition,
    EFileAttributes dwFlagsAndAttributes,
    IntPtr hTemplateFile);

public static void TestCreateAndWrite(string fileName) {

    string formattedName = @"\\?\" + fileName;
    // Create a file with generic write access
    SafeFileHandle fileHandle = CreateFile(formattedName,
        EFileAccess.GenericWrite, EFileShare.None, IntPtr.Zero,
        ECreationDisposition.CreateAlways, 0, IntPtr.Zero);

    // Check for errors
    int lastWin32Error = Marshal.GetLastWin32Error();
    if (fileHandle.IsInvalid) {
        throw new System.ComponentModel.Win32Exception(lastWin32Error);
    }

    // Pass the file handle to FileStream. FileStream will close the
    // handle
    using (FileStream fs = new FileStream(fileHandle,
                                    FileAccess.Write)) {
        fs.WriteByte(80);
        fs.WriteByte(81);
        fs.WriteByte(83);
        fs.WriteByte(84);
    }
}

此外,我建议您使用Path.Combine而不是path + "\\" + subpath

谢谢。但是自从 .net 4.6.2 版本以来,.net 确实支持长路径。至于 Path.Combine,a) 这是示例中最快的方法。b) 虽然我通常同意使用方法更好 - Path.Combine 是一个例外。请参见 https://stackoverflow.com/questions/53102/why-does-path-combine-not-properly-concatenate-filenames-that-start-with-path-di#comment8540140_53102。 - ispiro

2

我有一些经验:

1)在桌面应用程序中(.NET 4.7),您只需要使用带前缀 @"\?\(不需要清单,在app.confing中设置UseLegacyPathHandling),就可以使用路径名,所有内容都可以正常工作

2)在Web应用程序中,您必须设置以下内容:

  bool legacyPaths;
  if (AppContext.TryGetSwitch("Switch.System.IO.UseLegacyPathHandling", out legacyPaths) && legacyPaths)
  {
    var switchType = Type.GetType("System.AppContextSwitches"); 
    if (switchType != null)
    {
      AppContext.SetSwitch("Switch.System.IO.UseLegacyPathHandling", false);   
      var legacyField = switchType.GetField("_useLegacyPathHandling", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
      legacyField?.SetValue(null, (Int32)0); 
      AppContext.TryGetSwitch("Switch.System.IO.UseLegacyPathHandling", out legacyPaths);
      Assert.IsFalse(legacyPaths, "Long pathnames are not supported!");
    }
  }

我希望能帮到你!


2
你是不是想要使用前缀 @"\?"(注意字符串开头的两个斜杠)? - Roland Pihlakas

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