我们有一个使用Visual Studio Tools for Office(VSTO)在C#中构建的PowerPoint插件,VS2019,目标为.NET框架4.7.2.有时,此加载项需要打开用户指定的文件,它会使用常用的System.IO.File.Open API打开该文件.我们最近收到一位客户的错误报告,他无法打开他们的一个文件.它们包括一个堆栈跟踪,看起来像这样:

System.IO.PathTooLongException: The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.
   at System.IO.PathHelper.GetFullPathName()
   at System.IO.Path.LegacyNormalizePath(String path, Boolean fullCheck, Int32 maxPathLength, Boolean expandShortPaths)
   at System.IO.Path.NormalizePath(String path, Boolean fullCheck, Int32 maxPathLength, Boolean expandShortPaths)
   at System.IO.Path.NormalizePath(String path, Boolean fullCheck, Int32 maxPathLength)
   at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)
   at System.IO.File.Open(String path, FileMode mode, FileAccess access, FileShare share)

事实证明,他们文件的路径比原来的259个字符的限制要长.令人烦恼的是,我们甚至无法通过使用文件的"短"路径来解决这个问题(即,用遗留的8.3版本替换每个路径组件).该路径远低于限制,但当我们试图打开它时,得到了相同的异常,堆栈跟踪略有不同:

System.IO.PathTooLongException: The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.
   at System.IO.PathHelper.TryExpandShortFileName()
   at System.IO.Path.LegacyNormalizePath(String path, Boolean fullCheck, Int32 maxPathLength, Boolean expandShortPaths)
   at System.IO.Path.NormalizePath(String path, Boolean fullCheck, Int32 maxPathLength, Boolean expandShortPaths)
   at System.IO.Path.NormalizePath(String path, Boolean fullCheck, Int32 maxPathLength)
   at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)
   at System.IO.File.Open(String path, FileMode mode, FileAccess access, FileShare share)

所以看起来,为了打开文件,它首先将短名称扩展为长名称,然后抱怨它太长.当文件可以使用其短名称完全打开时,似乎没有必要,但好吧,我们只需要另一个解决方案.

接下来我try 的是用\\?\条"原始"路径前缀的完整路径,它总是让我在监狱中度过(尽管是直接使用Windows API的C++代码).仍然没有乐趣——我得到了与完整路径sans前缀完全相同的堆栈跟踪相同的异常.

在谷歌搜索了一下之后,我找到了this page documenting the AppContextSwitchOverrides element in app.config个switch ——特别引起我兴趣的switch 是Switch.System.IO.UseLegacyPathHandlingSwitch.System.IO.BlockLongPaths.在外接程序启动期间,我判断了这些switch 的值(使用AppContext.TryGetSwitch),并将它们都设置为true,所以我想我应该try 将它们都关闭.将元素添加到我的应用程序中.但是config不起作用(可能是因为加载项不是它自己的应用程序,而是在PowerPoint中托管的).因此,在try 打开任何文件之前,我在外接程序中使用了以下代码片段:

AppContext.SetSwitch("Switch.System.IO.UseLegacyPathHandling", false);
AppContext.SetSwitch("Switch.System.IO.BlockLongPaths", false);

然后我试着用上面提到的每个路径打开文件(完整、简短和带有\\?\前缀的完整路径),然后...我得到了与之前完全相同的异常,每种情况下都有完全相同的堆栈跟踪.

对于那些还没有失go 生存意志的人来说,这就是我的ThisAddIn.cs门课在这一点上的相关部分:

using System;
using System.Diagnostics;
using System.IO;

static void TryOpen(string path, string desc)
{
  try
  {
    using (var strm = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.None))
    {
    }
  }
  catch (IOException ex)
  {
    Debug.WriteLine($"Opening {desc} path failed with exception: {ex}");
  }
}

private void ThisAddIn_Startup(object sender, EventArgs e)
{
  AppContext.SetSwitch("Switch.System.IO.UseLegacyPathHandling", false);
  AppContext.SetSwitch("Switch.System.IO.BlockLongPaths", false);
  var shortPath = @"D:\LONGDI~1\LONGDI~1\LONGDI~1\LONGDI~1\LONGDI~1\test.txt";
  var longPath = @"D:\Long directory name that will be repeated a few times\Long directory name that will be repeated a few times\Long directory name that will be repeated a few times\Long directory name that will be repeated a few times\Long directory name that will be repeated a few times\test.txt";
  var prefixedPath = @"\\?\D:\Long directory name that will be repeated a few times\Long directory name that will be repeated a few times\Long directory name that will be repeated a few times\Long directory name that will be repeated a few times\Long directory name that will be repeated a few times\test.txt";
  TryOpen(longPath, "long");
  TryOpen(shortPath, "short");
  TryOpen(prefixedPath, "prefixed");
}

我还try 在控制台应用程序中使用相同的代码.NET Framework 4.7.2以及"长"和"短"路径在这方面也失败了,但"带前缀"的路径工作得很好.事实上,在控制台应用程序中,我可以省略AppContext.SetSwitch()个呼叫,得到完全相同的行为("prefixed"是唯一有效的).

我真的找到了解决办法,但我讨厌它.如果我总是使用\\?\前缀,并通过P/Invoke使用Windows CreateFile() API,这会给我一个可以传递给FileStream构造函数的文件句柄.我可以做到这一点,但我必须在打开文件的任何地方更新代码,确保新代码在错误情况下以及在"快乐路径"上的行为方式相同.这是可行的,但如果可能的话,我宁愿避免几天的工作,而同样的代码在控制台应用程序中运行良好的事实让我认为一定有办法让它在没有所有这些的情况下工作.

所以我的问题是:有没有其他人遇到过这个问题,当在VSTO PowerPoint加载项中运行时,有没有办法诱使System.IO.File.Open()人接受长路径?我可以在控制台应用程序中使用相同级别的功能(即通过添加\\?\前缀使其正常工作),而且我可以在控制台应用程序中使其正常工作,这让我觉得一定有某种方法可以做到这一点,而我目前缺少这种方法.

UPDATE:我放弃.我接受了Eugene的答案,因为它可能适用于某些人——如果您正在构建一个内部应用程序,并且您完全可以控制目标环境,那么为Office可执行文件部署应用程序配置文件可能是合适的.不过,我不能发布这样的商业应用程序.我go 看看有没有空的.NET库提供了对长路径的支持,如果这些库都不适用于我,也可以使用P/Invoke来实现.

推荐答案

最简单的方法是为主机应用程序使用一个包含以下内容的配置文件(即,在本例中,一个名为POWERPNT.EXE.config的文件,与POWERPNT.EXE位于同一目录中):

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <runtime>
    <AppContextSwitchOverrides value="Switch.System.IO.UseLegacyPathHandling=false;Switch.System.IO.BlockLongPaths=false" />
  </runtime>
</configuration>

你可以在How to deal with files with a name longer than 259 characters?线程中找到可能的解决方案.

Csharp相关问答推荐

为什么我不能更改尚未设置的模拟对象属性的值?

使用yaml将Azure函数代码部署到FunctionApp插槽时出现问题(zip未找到)

ASP.NET Core:如何在IPageFilter中注入ApplicationDbContext

dotnet集合中内部数组的局部变量副本的用途是什么?'

WPF DataGrid中的三维数据

Blazor Foreach仅渲染最后一种 colored颜色

在C#中,将两个哈希集连接在一起的时间复杂度是多少?

如何在C#中创建VS代码中的控制台应用程序时自动生成Main方法

如何将MemberInitExpression添加到绑定中其他Lambda MemberInitExpression

如何向事件添加成员

Polly重试URL复制值

使用System.Text.Json进行序列化时发生StackOverflow异常

正在从最小API-InvocationConext.Arguments中检索参数的FromBodyAttribute

发布.NET 8 Blazor WebAssembly独立应用程序以进行静态站点部署

ASP.NET MVC数据批注验证组复选框

RCL在毛伊岛应用程序和Blazor服务器应用程序.Net 8.0中使用页面

如何读取TagHelper属性的文本值?

Xamarin.Forms项目中缺少MainPage.xaml

当我在Git中暂存文件更改时,它们会消失

LINQ在GROUP BY和JOIN之后获取子列表