Jak radzić sobie z plikami o nazwie dłuższej niż 259 znaków?

82

Pracuję nad aplikacją, która przegląda każdy plik w niektórych katalogach i wykonuje pewne działania z tymi plikami. Między innymi muszę pobrać rozmiar pliku i datę modyfikacji tego pliku.

Niektóre pełne nazwy plików (katalog + nazwa pliku) są zbyt długie, nie mogłem użyć .NET Framework FileInfo, który jest ograniczony do MAX_PATH(260 znaków). Wiele źródeł internetowych zaleca używanie natywnych funkcji Win32 poprzez P / Invoke w celu uzyskania dostępu do plików, których nazwy są zbyt długie.

Obecnie wydaje się, że dokładnie ten sam problem występuje w przypadku funkcji Win32. Na przykład,GetFileAttributesEx (kernel32.dll) kończy się niepowodzeniem z błędem Win32 3 ERROR_PATH_NOT_FOUND dla ścieżki o długości 270 bajtów.

Ten sam plik można z powodzeniem otworzyć w Notepad2 i pomyślnie wyświetlić w Eksploratorze Windows (ale na przykład Visual Studio 2010 nie otwiera go z powodu ograniczenia do 259 znaków¹).

Co mogę zrobić, aby uzyskać dostęp do pliku, gdy ścieżka do pliku ma 270 znaków?

Uwagi:

  • Usunięcie lub zignorowanie plików, których ścieżka jest dłuższa niż 259 znaków, nie jest rozwiązaniem.

  • Szukam tylko rozwiązań zgodnych z Unicode.

  • Aplikacja będzie działać w systemie Windows 2008 / Vista lub nowszym z zainstalowanym .NET Framework 4.


¹ Zaskakujące jest, że program Microsoft Word 2007 zawodzi, narzekając, że „dyskietka jest za mała” na komputerze bez stacji dyskietek, lub że „mało pamięci RAM”, gdy pozostało 4 GB pamięci RAM, lub wreszcie, że „oprogramowanie antywirusowe [...] wymaga aktualizacji”. Czy pewnego dnia przestaną wyświetlać tak głupio nic nie znaczące błędy przynajmniej w tak kluczowych produktach jak Microsoft Office?

Arseni Mourzenko
źródło
1
Uważam, że nawet w dzisiejszych czasach każda nazwa pliku jest odwzorowywana na nazwę w formacie 8.3, czy nie możesz tego użyć? en.wikipedia.org/wiki/…
Grant Thomas,
6
Nawet nazwa pliku w formacie 8.3 może przekraczać 260 znaków, wystarczy głębokie zagnieżdżenie folderów.
David Heffernan,
1
Zauważ, że możesz (i możesz chcieć, ponieważ dodaje narzut we / wy) wyłączyć tworzenie nazw 8.3, więc nie, nie możesz być pewien, że 8.3 istnieje. Zobacz fsutil.exe 8dot3name.
Bacon Bits

Odpowiedzi:

81

Rozwiązanie .NET 4.6.2

Użyj \\?\C:\Verrrrrrrrrrrry long pathskładni opisanej tutaj .

Rozwiązanie .NET Core

Po prostu działa, ponieważ framework dodaje dla ciebie składnię długich ścieżek.

Rozwiązanie starsze niż .NET 4.6.2

Użyj również składni długich ścieżek i wersji Unicode funkcji API Win32 z P / Invoke. Z nazewnictwa plików, ścieżek i przestrzeni nazw :

Interfejs API systemu Windows ma wiele funkcji, które mają również wersje Unicode, aby umożliwić ścieżkę o rozszerzonej długości dla maksymalnej całkowitej długości ścieżki wynoszącej 32767 znaków. Ten typ ścieżki składa się z komponentów oddzielonych ukośnikami odwrotnymi, każdy do wartości zwróconej w parametrze lpMaximumComponentLength funkcji GetVolumeInformation (zwykle ta wartość ma 255 znaków). Aby określić ścieżkę o rozszerzonej długości, użyj \\?\przedrostka. Na przykład \\?\D:\very long path.

Przeczytanie tej strony pomocy Microsoft może być również interesujące.

Bardzo obszerne wyjaśnienie w Long Paths in .NET autorstwa Kima Hamiltona na blogu BCL Team wymienia kilka problemów z obsługą tych ścieżek, które według niego są powodem, dla którego ta składnia nadal nie jest obsługiwana bezpośrednio w .NET:

Istnieje kilka powodów, dla których nie chcieliśmy dodawać długich ścieżek w przeszłości i nadal z tym uważamy <...>.

<...> \\?\prefiks nie tylko włącza długie ścieżki; powoduje, że ścieżka jest przekazywana do systemu plików przy minimalnej modyfikacji przez interfejsy API systemu Windows. Konsekwencją jest to\\?\ wyłącza normalizację nazw plików wykonywaną przez interfejsy API systemu Windows, w tym usuwanie spacji końcowych, rozwijanie znaku „.” i „..”, konwertowanie ścieżek względnych na pełne ścieżki itd. <...>

<...> Długie ścieżki z \\?\prefiksem mogą być używane w większości plików związanych interfejsów API systemu Windows , ale nie we wszystkich interfejsach API systemu Windows. Na przykład LoadLibrary <...> kończy się niepowodzeniem, jeśli nazwa pliku jest dłuższa niż MAX_PATH. <...> Istnieją podobne przykłady w całym API systemu Windows; istnieją pewne obejścia, ale są one rozpatrywane indywidualnie.

Kolejnym czynnikiem <...> jest kompatybilność z innymi aplikacjami Windows i samą powłoką Windows <...>

Ponieważ ten problem staje się coraz bardziej powszechny <...>, firma Microsoft podejmuje wysiłki, aby go rozwiązać. W rzeczywistości, jako aktualna wtyczka do systemu Vista, zauważysz kilka zmian, które zmniejszają prawdopodobieństwo przekroczenia limitu MAX_PATH: wiele specjalnych nazw folderów zostało skróconych i, co ciekawsze, powłoka używa funkcji automatycznego zmniejszania ścieżki <...>, aby spróbować ścisnąć je do 260 znaków.


Ostrzeżenie: może być konieczne bezpośrednie wywołanie interfejsów API systemu Windows, ponieważ wydaje mi się, że platforma .NET Framework może nie obsługiwać tego rodzaju składni ścieżek.

user541686
źródło
Tak, 3.5 nie obsługuje tego rodzaju ścieżki. Wątpię, czy dodano to 4.0.
Jamie Penney
3
Tak, to będzie musiał P / Invoke funkcji Win32 API i nazywają je bezpośrednio z aplikacji .NET. Wewnętrzna instalacja .NET (a konkretnie PathHelperklasa) sprawdza poprawność ścieżki i zgłasza wyjątek, jeśli ma więcej niż MAX_PATH(260) znaków.
Cody Grey
12
@AmaniKilumanga: W takim razie twoja ścieżka to w zasadzie esej zawierający 6000 słów i system nie może sobie z tym poradzić.
user541686
1
@denahiro: Może możesz sobie pozwolić na edycję ... tak jak poprzednia osoba, która to tam umieściła (nie zrobiłem) ...
user541686
1
Bardzo precyzyjne - \\? \ Nie działa na 4.6.1, ale działa na 4.6.2
jw_
33

Stworzyłem własne LongFilei LongDirectoryzajęcia, aby rozwiązać ten problem. Używam go zawsze, gdy normalnie bym używał System.IO.File.

Być może są na nim optymalizacje itp., Ale od lat działa dobrze.

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);
        }
    }

}

I odpowiadający 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);
}
Wilk 5
źródło
StackOverflow nie jest dobrym miejscem do udostępniania kodu źródłowego bibliotek. Jeśli chcesz, aby faktycznie był używany przez innych programistów, uważam, że powinieneś (1) opublikować go w serwisie GitHub lub podobnej usłudze, (2) dołączyć testy jednostkowe i (3) opublikować go jako pakiet NuGet; Opcjonalnie powinieneś rozważyć dodanie dokumentacji, jeśli chcesz zachęcić inne osoby do współtworzenia Twojej biblioteki. Następnie możesz edytować tę odpowiedź, aby wyjaśnić, co zrobiłeś i jak ta biblioteka odpowiada na oryginalne pytanie (ponieważ tak!), I dołączyć odpowiednie linki do GitHub i NuGet.
Arseni Mourzenko
13
To tylko kilka zajęć z większego projektu, który mam w moim osobistym repozytorium TFS (visualstudio.com). Pomyślałem, że dzielę się tym, ponieważ wszyscy często widzę problemy z oprogramowaniem z brakującą obsługą długich ścieżek (nawet TFS 2013 zawodzi, jeśli przekroczysz 259 długości ...). Ale tak. Mógłbym zrobić coś takiego w przyszłości (na przykład, jeśli ten post otrzyma dużo głosów :)).
Wolf5,
To rozwiązanie działa u mnie, ale w funkcji InternalGetDirectories rekurencja nie działa, jeśli wybrana jest opcja AllDirectories i gdy wzorzec wyszukiwania nie znajduje się na liście podkatalogów. Musiałem zamienić wiersz ThrowWin32Exception (); przez coś w rodzaju wyszukiwania wstępnego z "*" jako wzorcem (kod zbyt długi, aby był tu zawarty, ale bardzo podobny do kodu do wykonania w funkcji).
Alex
Wypróbowano projekt nuget / github ZetaLongPaths, kopiowanie plików z jakiegoś powodu powodowało uszkodzenie plików, próbuję tego teraz. Myślę, że używa również tych wzorów pinvoke, ale nigdy nie wiadomo!
fartwhif
Dzięki za tę wiadomość, ale tylko do Twojej wiadomości, jeśli ścieżka docelowa jest względna (".. \ Fu \ Bar.txt"), to nie zadziała. Naprawiłem to po mojej stronie, zmuszając to jako absolutną ścieżkę.
Tipx
22

Możesz wypróbować bibliotekę Delimon, opartą na .NET Framework 4 w Microsoft TechNet, aby rozwiązać problem długich nazw plików:

Biblioteka Delimon.Win32.I O (V4.0).

Ma własne wersje kluczowych metod z System.IO. Na przykład zamieniłbyś:

System.IO.Directory.GetFiles

z

Delimon.Win32.IO.Directory.GetFiles

co pozwoli ci obsłużyć długie pliki i foldery.

Ze strony internetowej:

Delimon.Win32.IO zastępuje podstawowe funkcje plików System.IO i obsługuje nazwy plików i folderów do 32 767 znaków.

Ta biblioteka została napisana na platformie .NET Framework 4.0 i może być używana w systemach x86 i x64. Ograniczenia plików i folderów standardowej przestrzeni nazw System.IO mogą działać z plikami, które mają 260 znaków w nazwie pliku i 240 znaków w nazwie folderu (MAX_PATH jest zwykle skonfigurowany jako 260 znaków). Zwykle występuje błąd System.IO.PathTooLongException ze standardową biblioteką .NET.

TripleAntigen
źródło
4
Istnieje również biblioteka AlphaFS dla ścieżek przekraczających 260 znaków.
Mark G
5

Kiedyś napotkałem ten problem z aplikacją, którą pisałem. Kiedy zbliżałem się do przekroczenia limitu 260 znaków, mapowałem dysk sieciowy w locie do jakiegoś segmentu pełnej ścieżki, znacznie skracając w ten sposób długość pełnej ścieżki + nazwy pliku. To niezbyt eleganckie rozwiązanie, ale spełniło swoje zadanie.

Brooks
źródło
2

Dokumentacja MSDN dla GetFileAttributesEx mówi:

W wersji ANSI tej funkcji nazwa jest ograniczona do MAX_PATH znaków. Aby rozszerzyć ten limit do 32 767 szerokich znaków, wywołaj wersję funkcji Unicode i dodaj przed ścieżką „\\? \”. Aby uzyskać więcej informacji, zobacz nazywanie pliku .

Więc chcesz użyć GetFileAttributesExW i poprzedzić swoją ścieżkę „\\? \”

lunixbochs
źródło
Twój cytat jest poprawny, ale nieco mylący: to ograniczenie nie ma nic wspólnego z wersją ANSI (jest również ograniczone w wersji Unicode).
użytkownik541686
1
wyraźnie stwierdza, że ​​musisz użyć zarówno wersji Unicode, jak i prefiksu, aby zwiększyć limit.
lunixbochs
2

Zaktualizuj swój plik konfiguracyjny w następujący sposób:

<configuration>
  <runtime>
    <AppContextSwitchOverrides value="Switch.System.IO.UseLegacyPathHandling=false;Switch.System.IO.BlockLongPaths=false" />
  </runtime>
</configuration>
Sakthi Vel C.
źródło
0

Utworzenie oddzielnego procesu korzystającego z Robocopy jest również rozwiązaniem omówionym tutaj: Jak przenieść folder / pliki o nazwach ścieżek> 255 znaków w systemie 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();
        }

Jak widać na: Kopiowanie plików przy użyciu kopiowania i przetwarzania robo

Oskar Henriksson
źródło