Як мати справу з файлами з іменем довшим за 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 зазнає невдачі, скаржачись, що "дискета занадто мала" на комп’ютері, на якому немає дисковода, або що „пам’яті оперативної пам’яті мало”, коли залишається 4 ГБ оперативної пам’яті, або, нарешті, що "антивірусне програмне забезпечення [...] потрібно оновити". Чи перестануть вони одного разу відображати такі безглуздо безглузді помилки принаймні в таких ключових продуктах, як 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. З іменування файлів, шляхів та просторів імен :

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

Читання цієї сторінки служби підтримки Microsoft також може бути цікавим.

У дуже обширному поясненні Кіма Гамільтона у блозі BCL Team у Довгих шляхах у .NET перелічено кілька завадок щодо обробки цих шляхів, які, на його думку, є причиною того, що цей синтаксис досі не підтримується безпосередньо в .NET:

Є кілька причин, через які ми неохоче додавали довгі шляхи, і чому ми все ще обережно ставимось до цього <...>.

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

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

Іншим фактором <...> є сумісність з іншими програмами на базі Windows та самою оболонкою Windows <...>

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


Попередження. Можливо, вам доведеться зателефонувати безпосередньо до API Windows, оскільки, на мою думку, .NET Framework може не підтримувати такий синтаксис шляху.


Так, 3,5 не підтримував такий шлях. Сумніваюсь, що додав 4.0.
Джеймі Пенні,

3
Так, ви будете повинні P / Invoke функції API Win32 і викликати їх безпосередньо з програми .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 (); за допомогою свого роду попереднього пошуку з "*" як шаблону (код, який потрібно довго включати сюди, але дуже схожий на це код в функції).
Олексій

Спробував проект nuta / github ZetaLongPaths, копіюючи файли, чомусь створював пошкоджені файли, намагаючись це зараз. Я думаю, що в ньому також використовуються ці схеми обертання, але ви ніколи не знаєте!
fartwhif

Дякуємо за це, сер, але просто FYI, якщо цільовий шлях відносний (".. \ 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 символами. Щоб розширити це обмеження до 32767 широких символів, зателефонуйте версії функції Unicode і додайте "\\? \" До шляху. Для отримання додаткової інформації див. Присвоєння імені файлу .

Отже, ви хочете скористатися GetFileAttributesExW та додати шлях до префіксу "\\? \"


Ваша пропозиція правильна, але дещо вводить в оману: це обмеження не має нічого спільного з версією ANSI (вона також обмежена у версії Unicode).
user541686

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.