Как поступать с файлами, имя которых превышает 259 символов?


82

Я работаю над приложением, которое просматривает каждый файл в некоторых каталогах и выполняет некоторые действия с этими файлами. Среди прочего, я должен получить размер файла и дату, когда этот файл был изменен.

Некоторые полные имена файлов (каталог + имя файла) слишком длинные, я не мог использовать .NET Framework FileInfo, который ограничен MAX_PATH(260 символов). Многие веб-источники советуют использовать встроенные функции Win32 через P / Invoke для доступа к файлам, имена которых слишком длинные.

В настоящее время точно такая же проблема возникает с функциями Win32. Например, GetFileAttributesEx(kernel32.dll) не работает с ошибкой Win32 3 ERROR_PATH_NOT_FOUND для пути размером 270 байт.

Тот же самый файл можно успешно открыть из Notepad2 и успешно отобразить в проводнике Windows (но Visual Studio 2010, например, не может открыть его из-за ограничения в 259 символов¹).

Что я могу сделать, чтобы получить доступ к файлу, если путь к файлу составляет 270 символов?

Ноты:

  • Удаление или игнорирование файлов с длиной пути к файлу более 259 символов не является решением.

  • Я ищу только Unicode-совместимые решения.

  • Приложение будет работать под Windows 2008 / Vista или новее с установленной .NET Framework 4.


¹ Удивительно, но Microsoft Word 2007 терпит неудачу, жалуясь на то, что «дискета слишком мала» на компьютере, на котором нет дисковода для гибких дисков, или что «RAM memory is low», когда осталось 4 GB RAM, или, наконец, что «антивирусное [...] программное обеспечение необходимо обновить». Перестанут ли они когда-нибудь отображать такие глупо бессмысленные ошибки хотя бы в таких ключевых продуктах, как Microsoft Office?


1
Я считаю, даже в наши дни, что каждое имя файла сопоставляется с именем файла в формате 8.3, разве вы не можете это использовать? en.wikipedia.org/wiki/…
Грант Томас

6
Даже имя файла в формате 8.3 может превышать 260 символов, вам просто нужно глубоко вложить папки.
Дэвид Хеффернан

1
Обратите внимание, что вы можете (и, возможно, захотите, поскольку это добавляет накладные расходы ввода-вывода), отключить создание имени 8.3, поэтому нет, вы не можете быть уверены, что существует 8.3. Смотрите fsutil.exe 8dot3name.
Bacon Bits

Ответы:


81

.NET 4.6.2 Решение

Используйте \\?\C:\Verrrrrrrrrrrry long pathсинтаксис, как описано здесь .

Решение .NET Core

Это просто работает, потому что фреймворк добавляет за вас синтаксис длинного пути.

Решение до .NET 4.6.2

Также используйте синтаксис длинного пути и Unicode-версию функции Win32 API с P / Invoke. Из именования файлов, путей и пространств имен :

Windows API имеет множество функций, которые также имеют версии Unicode, позволяющие использовать путь расширенной длины для максимальной общей длины пути 32 767 символов. Этот тип пути состоит из компонентов, разделенных обратной косой чертой, каждый до значения, возвращаемого параметром lpMaximumComponentLength функции GetVolumeInformation (это значение обычно составляет 255 символов). Чтобы указать путь с увеличенной длиной, используйте \\?\префикс. Например, \\?\D:\very long path.

Также может быть интересно прочитать эту страницу поддержки Microsoft .

В очень подробном объяснении длинных путей в .NET, написанном Кимом Гамильтоном в блоге BCL Team, перечислены некоторые ошибки в обработке этих путей, которые, как он утверждает, являются причиной того, что этот синтаксис до сих пор не поддерживается напрямую в .NET:

Есть несколько причин, по которым мы неохотно добавляли длинные пути в прошлом, и почему мы все еще осторожны в этом <...>.

<...> \\?\префикс не только разрешает длинные пути; это приводит к передаче пути в файловую систему с минимальными изменениями со стороны Windows API. Следствием этого является \\?\отключение нормализации имен файлов, выполняемой Windows API, включая удаление конечных пробелов, расширение '.' и '..', преобразовывая относительные пути в полные пути и т. д. <...>

<...> Длинные пути с \\?\префиксом могут использоваться в большинстве API-интерфейсов Windows, связанных с файлами , но не во всех API-интерфейсах Windows. Например, LoadLibrary <...> завершится ошибкой, если имя файла длиннее MAX_PATH. <...> Подобные примеры есть во всех API Windows; существуют обходные пути, но они применяются в каждом конкретном случае.

Другой фактор <...> - совместимость с другими Windows-приложениями и самой оболочкой Windows <...>

Поскольку эта проблема становится все более распространенной <...> Microsoft прилагает усилия для ее решения. Фактически, как своевременный плагин Vista, вы заметите пару изменений, которые уменьшают вероятность достижения предела MAX_PATH: многие специальные имена папок были сокращены, и, что более интересно, оболочка использует функцию автоматического сокращения пути <...> попытаться сжать их до 260 символов.


Предупреждение: вам может потребоваться напрямую вызвать Windows API, поскольку я думаю, что .NET Framework может не поддерживать такой синтаксис пути.


Да, 3.5 не поддерживал такой путь. Сомневаюсь, что 4.0 добавил.
Джейми Пенни

3
Да, вам нужно будет вызвать функции Win32 API и вызвать их непосредственно из приложения .NET. Внутренняя сеть .NET (в частности, PathHelperкласс) проверяет путь и выдает исключение, если он превышает MAX_PATH(260) символов.
Коди Грей

12
@AmaniKilumanga: Тогда ваш путь - это эссе из 6000 слов, и система не справится с этим.
user541686 06

1
@denahiro: Возможно, вы позволите себе отредактировать его ... как это сделал предыдущий человек, который поместил это туда (я не делал) ...
user541686

1
Очень точно - \\? \ Не работает на 4.6.1, но работает на 4.6.2
jw_

33

Я создал свой собственный LongFileи LongDirectoryклассы , чтобы решить эту проблему. Я использую его всякий раз, когда обычно использую System.IO.File.

На нем могут быть оптимизации и т. Д., Но он отлично работает уже много лет.

public static class LongFile
{
    private const int MAX_PATH = 260;

    public static bool Exists(string path)
    {
        if (path.Length < MAX_PATH) return System.IO.File.Exists(path);
        var attr = NativeMethods.GetFileAttributesW(GetWin32LongPath(path));
        return (attr != NativeMethods.INVALID_FILE_ATTRIBUTES && ((attr & NativeMethods.FILE_ATTRIBUTE_ARCHIVE) == NativeMethods.FILE_ATTRIBUTE_ARCHIVE));
    }

    public static void Delete(string path)
    {
        if (path.Length < MAX_PATH) System.IO.File.Delete(path);
        else
        {
            bool ok = NativeMethods.DeleteFileW(GetWin32LongPath(path));
            if (!ok) ThrowWin32Exception();
        }
    }

    public static void AppendAllText(string path, string contents)
    {
        AppendAllText(path, contents, Encoding.Default);
    }

    public static void AppendAllText(string path, string contents, Encoding encoding)
    {
        if (path.Length < MAX_PATH)
        {
            System.IO.File.AppendAllText(path, contents, encoding);
        }
        else
        {
            var fileHandle = CreateFileForAppend(GetWin32LongPath(path));
            using (var fs = new System.IO.FileStream(fileHandle, System.IO.FileAccess.Write))
            {
                var bytes = encoding.GetBytes(contents);
                fs.Position = fs.Length;
                fs.Write(bytes, 0, bytes.Length);
            }
        }
    }

    public static void WriteAllText(string path, string contents)
    {
        WriteAllText(path, contents, Encoding.Default);
    }

    public static void WriteAllText(string path, string contents, Encoding encoding)
    {
        if (path.Length < MAX_PATH)
        {
            System.IO.File.WriteAllText(path, contents, encoding);
        }
        else
        {
            var fileHandle = CreateFileForWrite(GetWin32LongPath(path));

            using (var fs = new System.IO.FileStream(fileHandle, System.IO.FileAccess.Write))
            {
                var bytes = encoding.GetBytes(contents);
                fs.Write(bytes, 0, bytes.Length);
            }
        }
    }

    public static void WriteAllBytes(string path, byte[] bytes)
    {
        if (path.Length < MAX_PATH)
        {
            System.IO.File.WriteAllBytes(path, bytes);
        }
        else
        {
            var fileHandle = CreateFileForWrite(GetWin32LongPath(path));

            using (var fs = new System.IO.FileStream(fileHandle, System.IO.FileAccess.Write))
            {
                fs.Write(bytes, 0, bytes.Length);
            }
        }
    }

    public static void Copy(string sourceFileName, string destFileName)
    {
        Copy(sourceFileName, destFileName, false);
    }

    public static void Copy(string sourceFileName, string destFileName, bool overwrite)
    {
        if (sourceFileName.Length < MAX_PATH && (destFileName.Length < MAX_PATH)) System.IO.File.Copy(sourceFileName, destFileName, overwrite);
        else
        {
            var ok = NativeMethods.CopyFileW(GetWin32LongPath(sourceFileName), GetWin32LongPath(destFileName), !overwrite);
            if (!ok) ThrowWin32Exception();
        }
    }

    public static void Move(string sourceFileName, string destFileName)
    {
        if (sourceFileName.Length < MAX_PATH && (destFileName.Length < MAX_PATH)) System.IO.File.Move(sourceFileName, destFileName);
        else
        {
            var ok = NativeMethods.MoveFileW(GetWin32LongPath(sourceFileName), GetWin32LongPath(destFileName));
            if (!ok) ThrowWin32Exception();
        }
    }

    public static string ReadAllText(string path)
    {
        return ReadAllText(path, Encoding.Default);
    }

    public static string ReadAllText(string path, Encoding encoding)
    {
        if (path.Length < MAX_PATH) { return System.IO.File.ReadAllText(path, encoding); }
        var fileHandle = GetFileHandle(GetWin32LongPath(path));

        using (var fs = new System.IO.FileStream(fileHandle, System.IO.FileAccess.Read))
        {
            var data = new byte[fs.Length];
            fs.Read(data, 0, data.Length);
            return encoding.GetString(data);
        }
    }

    public static string[] ReadAllLines(string path)
    {
        return ReadAllLines(path, Encoding.Default);
    }

    public static string[] ReadAllLines(string path, Encoding encoding)
    {
        if (path.Length < MAX_PATH) { return System.IO.File.ReadAllLines(path, encoding); }
        var fileHandle = GetFileHandle(GetWin32LongPath(path));

        using (var fs = new System.IO.FileStream(fileHandle, System.IO.FileAccess.Read))
        {
            var data = new byte[fs.Length];
            fs.Read(data, 0, data.Length);
            var str = encoding.GetString(data);
            if (str.Contains("\r")) return str.Split(new[] { "\r\n" }, StringSplitOptions.None);
            return str.Split('\n');
        }
    }
    public static byte[] ReadAllBytes(string path)
    {
        if (path.Length < MAX_PATH) return System.IO.File.ReadAllBytes(path);
        var fileHandle = GetFileHandle(GetWin32LongPath(path));

        using (var fs = new System.IO.FileStream(fileHandle, System.IO.FileAccess.Read))
        {
            var data = new byte[fs.Length];
            fs.Read(data, 0, data.Length);
            return data;
        }
    }


    public static void SetAttributes(string path, FileAttributes attributes)
    {
        if (path.Length < MAX_PATH)
        {
            System.IO.File.SetAttributes(path, attributes);
        }
        else
        {
            var longFilename = GetWin32LongPath(path);
            NativeMethods.SetFileAttributesW(longFilename, (int)attributes);
        }
    }

    #region Helper methods

    private static SafeFileHandle CreateFileForWrite(string filename)
    {
        if (filename.Length >= MAX_PATH) filename = GetWin32LongPath(filename);
        SafeFileHandle hfile = NativeMethods.CreateFile(filename, (int)NativeMethods.FILE_GENERIC_WRITE, NativeMethods.FILE_SHARE_NONE, IntPtr.Zero, NativeMethods.CREATE_ALWAYS, 0, IntPtr.Zero);
        if (hfile.IsInvalid) ThrowWin32Exception();
        return hfile;
    }

    private static SafeFileHandle CreateFileForAppend(string filename)
    {
        if (filename.Length >= MAX_PATH) filename = GetWin32LongPath(filename);
        SafeFileHandle hfile = NativeMethods.CreateFile(filename, (int)NativeMethods.FILE_GENERIC_WRITE, NativeMethods.FILE_SHARE_NONE, IntPtr.Zero, NativeMethods.CREATE_NEW, 0, IntPtr.Zero);
        if (hfile.IsInvalid)
        {
            hfile = NativeMethods.CreateFile(filename, (int)NativeMethods.FILE_GENERIC_WRITE, NativeMethods.FILE_SHARE_NONE, IntPtr.Zero, NativeMethods.OPEN_EXISTING, 0, IntPtr.Zero);
            if (hfile.IsInvalid) ThrowWin32Exception();
        }
        return hfile;
    }

    internal static SafeFileHandle GetFileHandle(string filename)
    {
        if (filename.Length >= MAX_PATH) filename = GetWin32LongPath(filename);
        SafeFileHandle hfile = NativeMethods.CreateFile(filename, (int)NativeMethods.FILE_GENERIC_READ, NativeMethods.FILE_SHARE_READ, IntPtr.Zero, NativeMethods.OPEN_EXISTING, 0, IntPtr.Zero);
        if (hfile.IsInvalid) ThrowWin32Exception();
        return hfile;
    }

    internal static SafeFileHandle GetFileHandleWithWrite(string filename)
    {
        if (filename.Length >= MAX_PATH) filename = GetWin32LongPath(filename);
        SafeFileHandle hfile = NativeMethods.CreateFile(filename, (int)(NativeMethods.FILE_GENERIC_READ | NativeMethods.FILE_GENERIC_WRITE | NativeMethods.FILE_WRITE_ATTRIBUTES), NativeMethods.FILE_SHARE_NONE, IntPtr.Zero, NativeMethods.OPEN_EXISTING, 0, IntPtr.Zero);
        if (hfile.IsInvalid) ThrowWin32Exception();
        return hfile;
    }

    public static System.IO.FileStream GetFileStream(string filename, FileAccess access = FileAccess.Read)
    {
        var longFilename = GetWin32LongPath(filename);
        SafeFileHandle hfile;
        if (access == FileAccess.Write)
        {
            hfile = NativeMethods.CreateFile(longFilename, (int)(NativeMethods.FILE_GENERIC_READ | NativeMethods.FILE_GENERIC_WRITE | NativeMethods.FILE_WRITE_ATTRIBUTES), NativeMethods.FILE_SHARE_NONE, IntPtr.Zero, NativeMethods.OPEN_EXISTING, 0, IntPtr.Zero);
        }
        else
        {
            hfile = NativeMethods.CreateFile(longFilename, (int)NativeMethods.FILE_GENERIC_READ, NativeMethods.FILE_SHARE_READ, IntPtr.Zero, NativeMethods.OPEN_EXISTING, 0, IntPtr.Zero);
        }

        if (hfile.IsInvalid) ThrowWin32Exception();

        return new System.IO.FileStream(hfile, access);
    }


    [DebuggerStepThrough]
    public static void ThrowWin32Exception()
    {
        int code = Marshal.GetLastWin32Error();
        if (code != 0)
        {
            throw new System.ComponentModel.Win32Exception(code);
        }
    }

    public static string GetWin32LongPath(string path)
    {
        if (path.StartsWith(@"\\?\")) return path;

        if (path.StartsWith("\\"))
        {
            path = @"\\?\UNC\" + path.Substring(2);
        }
        else if (path.Contains(":"))
        {
            path = @"\\?\" + path;
        }
        else
        {
            var currdir = Environment.CurrentDirectory;
            path = Combine(currdir, path);
            while (path.Contains("\\.\\")) path = path.Replace("\\.\\", "\\");
            path = @"\\?\" + path;
        }
        return path.TrimEnd('.'); ;
    }

    private static string Combine(string path1, string path2)
    {
        return path1.TrimEnd('\\') + "\\" + path2.TrimStart('\\').TrimEnd('.'); ;
    }


    #endregion

    public static void SetCreationTime(string path, DateTime creationTime)
    {
        long cTime = 0;
        long aTime = 0;
        long wTime = 0;

        using (var handle = GetFileHandleWithWrite(path))
        {
            NativeMethods.GetFileTime(handle, ref cTime, ref aTime, ref wTime);
            var fileTime = creationTime.ToFileTimeUtc();
            if (!NativeMethods.SetFileTime(handle, ref fileTime, ref aTime, ref wTime))
            {
                throw new Win32Exception();
            }
        }
    }

    public static void SetLastAccessTime(string path, DateTime lastAccessTime)
    {
        long cTime = 0;
        long aTime = 0;
        long wTime = 0;

        using (var handle = GetFileHandleWithWrite(path))
        {
            NativeMethods.GetFileTime(handle, ref cTime, ref aTime, ref wTime);

            var fileTime = lastAccessTime.ToFileTimeUtc();
            if (!NativeMethods.SetFileTime(handle, ref cTime, ref fileTime, ref wTime))
            {
                throw new Win32Exception();
            }
        }
    }

    public static void SetLastWriteTime(string path, DateTime lastWriteTime)
    {
        long cTime = 0;
        long aTime = 0;
        long wTime = 0;

        using (var handle = GetFileHandleWithWrite(path))
        {
            NativeMethods.GetFileTime(handle, ref cTime, ref aTime, ref wTime);

            var fileTime = lastWriteTime.ToFileTimeUtc();
            if (!NativeMethods.SetFileTime(handle, ref cTime, ref aTime, ref fileTime))
            {
                throw new Win32Exception();
            }
        }
    }

    public static DateTime GetLastWriteTime(string path)
    {
        long cTime = 0;
        long aTime = 0;
        long wTime = 0;

        using (var handle = GetFileHandleWithWrite(path))
        {
            NativeMethods.GetFileTime(handle, ref cTime, ref aTime, ref wTime);

            return DateTime.FromFileTimeUtc(wTime);
        }
    }

}

И соответствующий LongDirectory:

public class LongDirectory
{
    private const int MAX_PATH = 260;

    public static void CreateDirectory(string path)
    {
        if (string.IsNullOrWhiteSpace(path)) return;
        if (path.Length < MAX_PATH)
        {
            System.IO.Directory.CreateDirectory(path);
        }
        else
        {
            var paths = GetAllPathsFromPath(GetWin32LongPath(path));
            foreach (var item in paths)
            {
                if (!LongExists(item))
                {
                    var ok = NativeMethods.CreateDirectory(item, IntPtr.Zero);
                    if (!ok)
                    {
                        ThrowWin32Exception();
                    }
                }
            }
        }
    }

    public static void Delete(string path)
    {
        Delete(path, false);
    }

    public static void Delete(string path, bool recursive)
    {
        if (path.Length < MAX_PATH && !recursive)
        {
            System.IO.Directory.Delete(path, false);
        }
        else
        {
            if (!recursive)
            {
                bool ok = NativeMethods.RemoveDirectory(GetWin32LongPath(path));
                if (!ok) ThrowWin32Exception();
            }
            else
            {
                DeleteDirectories(new string[] { GetWin32LongPath(path) });
            }
        }
    }


    private static void DeleteDirectories(string[] directories)
    {
        foreach (string directory in directories)
        {
            string[] files = LongDirectory.GetFiles(directory, null, System.IO.SearchOption.TopDirectoryOnly);
            foreach (string file in files)
            {
                LongFile.Delete(file);
            }
            directories = LongDirectory.GetDirectories(directory, null, System.IO.SearchOption.TopDirectoryOnly);
            DeleteDirectories(directories);
            bool ok = NativeMethods.RemoveDirectory(GetWin32LongPath(directory));
            if (!ok) ThrowWin32Exception();
        }
    }

    public static bool Exists(string path)
    {
        if (path.Length < MAX_PATH) return System.IO.Directory.Exists(path);
        return LongExists(GetWin32LongPath(path));
    }

    private static bool LongExists(string path)
    {
        var attr = NativeMethods.GetFileAttributesW(path);
        return (attr != NativeMethods.INVALID_FILE_ATTRIBUTES && ((attr & NativeMethods.FILE_ATTRIBUTE_DIRECTORY) == NativeMethods.FILE_ATTRIBUTE_DIRECTORY));
    }


    public static string[] GetDirectories(string path)
    {
        return GetDirectories(path, null, SearchOption.TopDirectoryOnly);
    }

    public static string[] GetDirectories(string path, string searchPattern)
    {
        return GetDirectories(path, searchPattern, SearchOption.TopDirectoryOnly);
    }

    public static string[] GetDirectories(string path, string searchPattern, System.IO.SearchOption searchOption)
    {
        searchPattern = searchPattern ?? "*";
        var dirs = new List<string>();
        InternalGetDirectories(path, searchPattern, searchOption, ref dirs);
        return dirs.ToArray();
    }

    private static void InternalGetDirectories(string path, string searchPattern, System.IO.SearchOption searchOption, ref List<string> dirs)
    {
        NativeMethods.WIN32_FIND_DATA findData;
        IntPtr findHandle = NativeMethods.FindFirstFile(System.IO.Path.Combine(GetWin32LongPath(path), searchPattern), out findData);

        try
        {
            if (findHandle != new IntPtr(-1))
            {

                do
                {
                    if ((findData.dwFileAttributes & System.IO.FileAttributes.Directory) != 0)
                    {
                        if (findData.cFileName != "." && findData.cFileName != "..")
                        {
                            string subdirectory = System.IO.Path.Combine(path, findData.cFileName);
                            dirs.Add(GetCleanPath(subdirectory));
                            if (searchOption == SearchOption.AllDirectories)
                            {
                                InternalGetDirectories(subdirectory, searchPattern, searchOption, ref dirs);
                            }
                        }
                    }
                } while (NativeMethods.FindNextFile(findHandle, out findData));
                NativeMethods.FindClose(findHandle);
            }
            else
            {
                ThrowWin32Exception();
            }
        }
        catch (Exception)
        {
            NativeMethods.FindClose(findHandle);
            throw;
        }
    }

    public static string[] GetFiles(string path)
    {
        return GetFiles(path, null, SearchOption.TopDirectoryOnly);
    }

    public static string[] GetFiles(string path, string searchPattern)
    {
        return GetFiles(path, searchPattern, SearchOption.TopDirectoryOnly);
    }


    public static string[] GetFiles(string path, string searchPattern, System.IO.SearchOption searchOption)
    {
        searchPattern = searchPattern ?? "*";

        var files = new List<string>();
        var dirs = new List<string> { path };

        if (searchOption == SearchOption.AllDirectories)
        {
            //Add all the subpaths
            dirs.AddRange(LongDirectory.GetDirectories(path, null, SearchOption.AllDirectories));
        }

        foreach (var dir in dirs)
        {
            NativeMethods.WIN32_FIND_DATA findData;
            IntPtr findHandle = NativeMethods.FindFirstFile(System.IO.Path.Combine(GetWin32LongPath(dir), searchPattern), out findData);

            try
            {
                if (findHandle != new IntPtr(-1))
                {

                    do
                    {
                        if ((findData.dwFileAttributes & System.IO.FileAttributes.Directory) == 0)
                        {
                            string filename = System.IO.Path.Combine(dir, findData.cFileName);
                            files.Add(GetCleanPath(filename));
                        }
                    } while (NativeMethods.FindNextFile(findHandle, out findData));
                    NativeMethods.FindClose(findHandle);
                }
            }
            catch (Exception)
            {
                NativeMethods.FindClose(findHandle);
                throw;
            }
        }

        return files.ToArray();
    }



    public static void Move(string sourceDirName, string destDirName)
    {
        if (sourceDirName.Length < MAX_PATH || destDirName.Length < MAX_PATH)
        {
            System.IO.Directory.Move(sourceDirName, destDirName);
        }
        else
        {
            var ok = NativeMethods.MoveFileW(GetWin32LongPath(sourceDirName), GetWin32LongPath(destDirName));
            if (!ok) ThrowWin32Exception();
        }
    }

    #region Helper methods



    [DebuggerStepThrough]
    public static void ThrowWin32Exception()
    {
        int code = Marshal.GetLastWin32Error();
        if (code != 0)
        {
            throw new System.ComponentModel.Win32Exception(code);
        }
    }

    public static string GetWin32LongPath(string path)
    {

        if (path.StartsWith(@"\\?\")) return path;

        var newpath = path;
        if (newpath.StartsWith("\\"))
        {
            newpath = @"\\?\UNC\" + newpath.Substring(2);
        }
        else if (newpath.Contains(":"))
        {
            newpath = @"\\?\" + newpath;
        }
        else
        {
            var currdir = Environment.CurrentDirectory;
            newpath = Combine(currdir, newpath);
            while (newpath.Contains("\\.\\")) newpath = newpath.Replace("\\.\\", "\\");
            newpath = @"\\?\" + newpath;
        }
        return newpath.TrimEnd('.');
    }

    private static string GetCleanPath(string path)
    {
        if (path.StartsWith(@"\\?\UNC\")) return @"\\" + path.Substring(8);
        if (path.StartsWith(@"\\?\")) return path.Substring(4);
        return path;
    }

    private static List<string> GetAllPathsFromPath(string path)
    {
        bool unc = false;
        var prefix = @"\\?\";
        if (path.StartsWith(prefix + @"UNC\"))
        {
            prefix += @"UNC\";
            unc = true;
        }
        var split = path.Split('\\');
        int i = unc ? 6 : 4;
        var list = new List<string>();
        var txt = "";

        for (int a = 0; a < i; a++)
        {
            if (a > 0) txt += "\\";
            txt += split[a];
        }
        for (; i < split.Length; i++)
        {
            txt = Combine(txt, split[i]);
            list.Add(txt);
        }

        return list;
    }

    private static string Combine(string path1, string path2)
    {
        return path1.TrimEnd('\\') + "\\" + path2.TrimStart('\\').TrimEnd('.');
    }


    #endregion
}

NativeMethods:

internal static class NativeMethods
{
    internal const int FILE_ATTRIBUTE_ARCHIVE = 0x20;
    internal const int INVALID_FILE_ATTRIBUTES = -1;

    internal const int FILE_READ_DATA = 0x0001;
    internal const int FILE_WRITE_DATA = 0x0002;
    internal const int FILE_APPEND_DATA = 0x0004;
    internal const int FILE_READ_EA = 0x0008;
    internal const int FILE_WRITE_EA = 0x0010;

    internal const int FILE_READ_ATTRIBUTES = 0x0080;
    internal const int FILE_WRITE_ATTRIBUTES = 0x0100;

    internal const int FILE_SHARE_NONE = 0x00000000;
    internal const int FILE_SHARE_READ = 0x00000001;

    internal const int FILE_ATTRIBUTE_DIRECTORY = 0x10;

    internal const long FILE_GENERIC_WRITE = STANDARD_RIGHTS_WRITE |
                                                FILE_WRITE_DATA |
                                                FILE_WRITE_ATTRIBUTES |
                                                FILE_WRITE_EA |
                                                FILE_APPEND_DATA |
                                                SYNCHRONIZE;

    internal const long FILE_GENERIC_READ = STANDARD_RIGHTS_READ |
                                            FILE_READ_DATA |
                                            FILE_READ_ATTRIBUTES |
                                            FILE_READ_EA |
                                            SYNCHRONIZE;



    internal const long READ_CONTROL = 0x00020000L;
    internal const long STANDARD_RIGHTS_READ = READ_CONTROL;
    internal const long STANDARD_RIGHTS_WRITE = READ_CONTROL;

    internal const long SYNCHRONIZE = 0x00100000L;

    internal const int CREATE_NEW = 1;
    internal const int CREATE_ALWAYS = 2;
    internal const int OPEN_EXISTING = 3;

    internal const int MAX_PATH = 260;
    internal const int MAX_ALTERNATE = 14;

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    internal struct WIN32_FIND_DATA
    {
        public System.IO.FileAttributes dwFileAttributes;
        public FILETIME ftCreationTime;
        public FILETIME ftLastAccessTime;
        public FILETIME ftLastWriteTime;
        public uint nFileSizeHigh; //changed all to uint, otherwise you run into unexpected overflow
        public uint nFileSizeLow;  //|
        public uint dwReserved0;   //|
        public uint dwReserved1;   //v
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)]
        public string cFileName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_ALTERNATE)]
        public string cAlternate;
    }


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


    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern bool CopyFileW(string lpExistingFileName, string lpNewFileName, bool bFailIfExists);


    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern int GetFileAttributesW(string lpFileName);


    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern bool DeleteFileW(string lpFileName);


    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern bool MoveFileW(string lpExistingFileName, string lpNewFileName);


    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern bool SetFileTime(SafeFileHandle hFile, ref long lpCreationTime, ref long lpLastAccessTime, ref long lpLastWriteTime);


    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern bool GetFileTime(SafeFileHandle hFile, ref long lpCreationTime, ref long lpLastAccessTime, ref long lpLastWriteTime);


    [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern IntPtr FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData);


    [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern bool FindNextFile(IntPtr hFindFile, out WIN32_FIND_DATA lpFindFileData);


    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern bool FindClose(IntPtr hFindFile);


    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern bool RemoveDirectory(string path);


    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern bool CreateDirectory(string lpPathName, IntPtr lpSecurityAttributes);


    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern int SetFileAttributesW(string lpFileName, int fileAttributes);
}

StackOverflow - не лучшее место для обмена исходным кодом библиотек. Если вы хотите, чтобы его действительно использовали другие разработчики, я считаю, что вам следует (1) опубликовать его на GitHub или аналогичном сервисе, (2) включить модульные тесты и (3) опубликовать его как пакет NuGet; при желании вам следует подумать о добавлении документации, если вы хотите побудить других людей внести свой вклад в вашу библиотеку. Затем вы можете отредактировать этот ответ, чтобы объяснить, что вы сделали и как эта библиотека отвечает на исходный вопрос (потому что это так!), А также включить соответствующие ссылки на GitHub и NuGet.
Арсений Мурзенко

13
Это всего лишь несколько классов из более крупного проекта, который у меня есть в моем личном репозитории TFS (visualstudio.com). Думал, что разделяю это, поскольку я все часто вижу проблемы с программным обеспечением с отсутствующей поддержкой длинных путей (даже TFS 2013 выйдет из строя, если вы пройдете мимо 259 ...). Но да. Возможно, подобное будет сделано в будущем (например, если за этот пост наберется много голосов :)).
Wolf5

Это решение работает у меня, но в функции InternalGetDirectories рекурсия не работает, если выбран параметр AllDirectories и шаблон поиска не найден в списке подкаталогов. Пришлось заменить строку ThrowWin32Exception (); по какой -то предварительно-поиска с «*» в качестве шаблона (код длинный , чтобы здесь включены, но очень похоже на сделать код в функции).
Alex

Пробовал ZetaLongPaths проект nuget / github, копирование файлов по какой-то причине приводило к повреждению файлов, пробовал это сейчас. Я думаю, что он также использует эти шаблоны pinvoke, но вы никогда не знаете!
fartwhif

Спасибо за это, сэр, но к вашему сведению, если целевой путь является относительным (".. \ Fu \ Bar.txt"), это не сработает. Я исправил это на своей стороне, установив абсолютный путь.
Tipx

22

Вы можете попробовать библиотеку Delimon, ее библиотеку на основе .NET Framework 4 в Microsoft TechNet для решения проблемы длинных имен файлов:

Библиотека Delimon.Win32.I O (V4.0).

У него есть свои версии ключевых методов из System.IO. Например, вы бы заменили:

System.IO.Directory.GetFiles

с участием

Delimon.Win32.IO.Directory.GetFiles

что позволит вам обрабатывать длинные файлы и папки.

С сайта:

Delimon.Win32.IO заменяет основные файловые функции System.IO и поддерживает имена файлов и папок длиной до 32 767 символов.

Эта библиотека написана на платформе .NET Framework 4.0 и может использоваться в системах x86 и x64. Ограничения для файлов и папок стандартного пространства имен System.IO могут работать с файлами, которые содержат 260 символов в имени файла и 240 символов в имени папки (MAX_PATH обычно настраивается как 260 символов). Обычно вы сталкиваетесь с ошибкой System.IO.PathTooLongException со стандартной библиотекой .NET.


4
Также существует библиотека AlphaFS для путей, превышающих 260 символов.
Mark G


5

Однажды я столкнулся с этой проблемой с приложением, которое я писал. Когда я приближался к достижению ограничения в 260 символов, я на лету подключал сетевой диск к некоторому сегменту полного пути, тем самым значительно сокращая длину полного пути + имени файла. Это не совсем элегантное решение, но оно сделало свою работу.



2

Ссылка MSDN для GetFileAttributesEx говорит:

В версии ANSI этой функции имя ограничено символами MAX_PATH. Чтобы расширить этот предел до 32 767 широких символов, вызовите версию функции Unicode и добавьте к пути "\\? \". Для получения дополнительной информации см. Именование файла .

Итак, вы хотите использовать GetFileAttributesExW и префикс пути с "\\? \"


Ваша цитата верна, но немного вводит в заблуждение: это ограничение не имеет ничего общего с версией ANSI (оно также ограничено в версии Unicode).
user541686 04

1
в нем очень четко указано, что вам нужно использовать как версию Unicode, так и префикс, чтобы расширить ограничение.
lunixbochs

2

Пожалуйста, обновите свой файл конфигурации следующим образом:

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

0

Создание отдельного процесса, использующего Robocopy, также является решением, обсуждаемым здесь: Как переместить папку / файлы с именами пути> 255 символов в Windows 8.1?

  public static void RoboCopy(string src, string dst)
        {
            Process p = new Process();
            p.StartInfo.Arguments = string.Format("/C Robocopy {0} {1}", src, dst);
            p.StartInfo.FileName = "CMD.EXE";
            p.StartInfo.CreateNoWindow = true;
            p.StartInfo.UseShellExecute = false;
            p.Start();
            p.WaitForExit();
        }

Как видно из: Копирование файла с использованием робо-копирования и обработки

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.