Làm thế nào để xử lý các tệp có tên dài hơn 259 ký tự?


81

Tôi đang làm việc trên một ứng dụng duyệt qua mọi tệp trong một số thư mục và thực hiện một số hành động với các tệp đó. Trong số những người khác, tôi phải truy xuất kích thước tệp và ngày tệp này được sửa đổi.

Một số tên đầy đủ của tệp (thư mục + tên tệp) quá dài, tôi không thể sử dụng .NET Framework FileInfobị giới hạn ở MAX_PATH(260 ký tự). Nhiều nguồn web khuyên nên sử dụng các hàm Win32 bản địa thông qua P / Invoke để truy cập các tệp có tên quá dài.

Hiện tại, vấn đề giống hệt nhau dường như phát sinh với các hàm Win32. Ví dụ: GetFileAttributesEx(kernel32.dll) không thành công với lỗi Win32 3 ERROR_PATH_NOT_FOUND cho đường dẫn 270 byte.

Có thể mở thành công cùng một tệp từ Notepad2 và hiển thị thành công bằng Windows Explorer (nhưng ví dụ: Visual Studio 2010 không mở được vì giới hạn 259 ký tự¹).

Tôi có thể làm gì để có thể truy cập tệp khi đường dẫn tệp dài 270 ký tự?

Ghi chú:

  • Xóa hoặc bỏ qua các tệp có độ dài đường dẫn tệp dài hơn 259 ký tự không phải là một giải pháp.

  • Tôi chỉ tìm kiếm các giải pháp tương thích với Unicode.

  • Ứng dụng sẽ chạy trong Windows 2008 / Vista trở lên với .NET Framework 4 được cài đặt.


¹ Đáng ngạc nhiên là Microsoft Word 2007 bị lỗi, phàn nàn rằng "đĩa mềm quá nhỏ" trên máy tính không có bất kỳ ổ đĩa mềm nào hoặc "bộ nhớ RAM sắp hết" khi còn 4 GB RAM hoặc cuối cùng là "phần mềm chống vi-rút [...] cần được cập nhật". Liệu họ có ngừng một ngày nào đó hiển thị những lỗi vô nghĩa ngu ngốc như vậy ít nhất là trong các sản phẩm chủ chốt như Microsoft Office?


1
Tôi tin rằng, ngay cả những ngày này, mọi tên tệp đều ánh xạ tới một tên tệp định dạng 8.3, bạn không thể sử dụng nó? vi.wikipedia.org/wiki/…
Grant Thomas

6
Ngay cả một tên tệp định dạng 8.3 có thể vượt quá 260 ký tự, bạn chỉ cần lồng vào thư mục sâu.
David Heffernan

1
Lưu ý rằng bạn có thể (và có thể muốn vì nó thêm chi phí I / O) vô hiệu hóa tính năng tạo tên 8.3, vì vậy, không, bạn không thể chắc chắn rằng 8.3 tồn tại. Thấy chưa fsutil.exe 8dot3name.
Bacon Bits

Câu trả lời:


80

Giải pháp .NET 4.6.2

Sử dụng \\?\C:\Verrrrrrrrrrrry long pathcú pháp như được mô tả ở đây .

Giải pháp cốt lõi .NET

Nó chỉ hoạt động vì khung công tác thêm cú pháp đường dẫn dài cho bạn.

Giải pháp Pre .NET 4.6.2

Cũng sử dụng cú pháp đường dẫn dài và phiên bản Unicode của hàm Win32 API với P / Invoke. Từ đặt tên tệp, đường dẫn và không gian tên :

API Windows có nhiều chức năng cũng có các phiên bản Unicode để cho phép một đường dẫn có độ dài mở rộng cho tổng độ dài đường dẫn tối đa là 32.767 ký tự. Loại đường dẫn này bao gồm các thành phần được phân tách bằng dấu gạch chéo ngược, mỗi thành phần có giá trị được trả về trong tham số lpMaximumComponentLength của hàm GetVolumeInformation (giá trị này thường là 255 ký tự). Để chỉ định một đường dẫn có độ dài mở rộng, hãy sử dụng \\?\tiền tố. Ví dụ \\?\D:\very long path,.

Đọc trang Hỗ trợ của Microsoft này cũng có thể thú vị.

Một lời giải thích rất sâu rộng về Đường dẫn dài trong .NET của Kim Hamilton tại blog BCL Team liệt kê một số điểm khó khăn trong việc xử lý các đường dẫn này mà anh ấy khẳng định là lý do khiến cú pháp này vẫn không được hỗ trợ trực tiếp trong .NET:

Trước đây có một số lý do khiến chúng tôi không muốn thêm các đường dẫn dài và tại sao chúng tôi vẫn cẩn thận về nó <...>.

<...> \\?\tiền tố không chỉ cho phép các đường dẫn dài; nó làm cho đường dẫn được chuyển đến hệ thống tệp với sự sửa đổi tối thiểu của các API Windows. Hậu quả là \\?\tắt tính năng chuẩn hóa tên tệp được thực hiện bởi các API Windows, bao gồm cả việc xóa dấu cách ở cuối, mở rộng '.' và '..', chuyển đổi các đường dẫn tương đối thành đường dẫn đầy đủ, v.v. <...>

<...> Các đường dẫn dài có \\?\tiền tố có thể được sử dụng trong hầu hết các API Windows liên quan đến tệp , nhưng không phải tất cả các API Windows. Ví dụ: LoadLibrary <...> không thành công nếu tên tệp dài hơn MAX_PATH. <...> Có các ví dụ tương tự trong các API Windows; một số cách giải quyết tồn tại, nhưng chúng tùy thuộc vào từng trường hợp cụ thể.

Một yếu tố khác <...> là khả năng tương thích với các ứng dụng dựa trên Windows khác và chính Windows shell <...>

Vì vấn đề này đang ngày càng trở nên phổ biến nên Microsoft đã có những nỗ lực để giải quyết nó. Trên thực tế, với tư cách là một plug-in Vista kịp thời, bạn sẽ nhận thấy một vài thay đổi làm giảm cơ hội đạt đến giới hạn MAX_PATH: nhiều tên thư mục đặc biệt đã được rút ngắn và thú vị hơn, shell đang sử dụng tính năng tự động thu hẹp đường dẫn. <...> để cố gắng ép chúng thành 260 ký tự.


Cảnh báo: Bạn có thể cần gọi trực tiếp các API Windows, vì tôi cho rằng .NET Framework có thể không hỗ trợ loại cú pháp đường dẫn này.


Vâng, 3.5 không hỗ trợ loại đường dẫn này. Tôi nghi ngờ 4.0 đã thêm nó.
Jamie Penney

Nếu bạn cần truy cập server-share với đường dẫn dài, bạn cần viết nó như sau: \\? \ UNC \ Server \ Share, hay nói cách khác là thêm \\? \ UNC \ làm tiền tố. Thông tin thêm về điều này: installsetupconfig.com/win32programming/windowsfileapis4_2.html
Spiralis

3
Có, bạn sẽ cần P / Gọi các hàm API Win32 và gọi chúng trực tiếp từ ứng dụng .NET. Hệ thống ống nước nội bộ của .NET (cụ thể là một PathHelperlớp) xác thực đường dẫn và ném một ngoại lệ nếu nó có nhiều hơn MAX_PATH(260) ký tự.
Cody Grey

12
@AmaniKilumanga: Vậy thì đường dẫn của bạn về cơ bản là một bài luận 6000 từ và hệ thống không thể xử lý nó.
user541686,

1
@denahiro: Có lẽ bạn có thể lấy tự do để chỉnh sửa nó ... cũng như những người trước người đặt nó ở đó (tôi không) ...
user541686

33

Tôi đã tạo riêng LongFileLongDirectorycác lớp để giải quyết vấn đề đó. Tôi sử dụng nó bất cứ khi nào tôi thường sử dụng System.IO.File.

Có thể có các tối ưu hóa, v.v. trên đó nhưng nó vẫn hoạt động tốt trong nhiều năm nay.

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

}

Và một tương ứng 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 không phải là nơi tốt để chia sẻ mã nguồn của các thư viện. Nếu bạn muốn nó thực sự được các nhà phát triển khác sử dụng, tôi tin rằng bạn nên (1) xuất bản nó trên GitHub hoặc một dịch vụ tương tự, (2) bao gồm các bài kiểm tra đơn vị và (3) xuất bản nó dưới dạng gói NuGet; tùy ý, bạn nên xem xét thêm tài liệu nếu bạn muốn khuyến khích người khác đóng góp cho thư viện của bạn. Sau đó, bạn có thể chỉnh sửa câu trả lời này để giải thích những gì bạn đã làm và cách thư viện này trả lời câu hỏi ban đầu (bởi vì nó đúng!), Và bao gồm các liên kết tương ứng tới GitHub và NuGet.
Arseni Mourzenko

13
Đây chỉ là một vài lớp học từ một dự án lớn hơn mà tôi có trên kho lưu trữ TFS cá nhân của mình (visualstudio.com). Tôi nghĩ rằng tôi chia sẻ nó như tất cả tôi thường thấy các vấn đề phần mềm thiếu hỗ trợ cho các đường dẫn dài (thậm chí TFS 2013 không thành công nếu bạn vượt quá 259 độ dài ...). Nhưng có. Có thể sẽ làm điều gì đó tương tự trong tương lai (ví dụ nếu bài đăng này nhận được nhiều lượt bình chọn :)).
Wolf 5

Giải pháp này phù hợp với tôi, nhưng trong hàm InternalGetDirectories , phép đệ quy không hoạt động nếu tùy chọn AllDirectories được chọn và khi không tìm thấy mẫu tìm kiếm trong danh sách thư mục con. Tôi đã phải thay thế dòng ThrowWin32Exception (); bằng cách tìm kiếm trước với "*" làm mẫu (mã để dài được đưa vào đây, nhưng rất giống với mã thực hiện trong hàm).
Alex

Đã thử dự án nuget / github của ZetaLongPaths, việc sao chép tệp đang tạo ra tệp bị hỏng vì một số lý do, hãy thử cách này ngay bây giờ. Tôi nghĩ rằng nó cũng sử dụng các mẫu pinvoke này, nhưng bạn không bao giờ biết!
xì hơi vào

Cảm ơn vì điều này thưa ông, nhưng chỉ FYI, nếu đường dẫn đích là tương đối (".. \ Fu \ Bar.txt") thì nó sẽ không hoạt động. Tôi đã sửa nó về phía mình bằng cách buộc nó như một con đường tuyệt đối.
Tipx

22

Bạn có thể thử thư viện Delimon, thư viện dựa trên .NET Framework 4 của nó trên Microsoft TechNet để khắc phục sự cố tên tệp dài:

Thư viện Delimon.Win32.I O (V4.0).

Nó có các phiên bản riêng của các phương thức chính từ System.IO. Ví dụ, bạn sẽ thay thế:

System.IO.Directory.GetFiles

với

Delimon.Win32.IO.Directory.GetFiles

điều này sẽ cho phép bạn xử lý các tệp và thư mục dài.

Từ trang web:

Delimon.Win32.IO thay thế các chức năng tệp cơ bản của System.IO và hỗ trợ tên tệp & thư mục lên đến 32.767 ký tự.

Thư viện này được viết trên .NET Framework 4.0 và có thể được sử dụng trên hệ thống x86 & x64. Các giới hạn về Tệp & Thư mục của không gian tên System.IO tiêu chuẩn có thể hoạt động với các tệp có 260 ký tự trong tên tệp và 240 ký tự trong tên thư mục (MAX_PATH thường được định cấu hình là 260 ký tự). Thông thường, bạn gặp lỗi System.IO.PathTooLongException với Thư viện .NET Chuẩn.


4
Ngoài ra còn có thư viện AlphaFS cho các đường dẫn vượt quá 260 ký tự.
Mark G


5

Tôi đã gặp sự cố này một lần với một ứng dụng mà tôi đang viết. Khi tôi sắp đạt đến giới hạn 260 ký tự, tôi sẽ lập tức ánh xạ ổ đĩa mạng tới một số đoạn của đường dẫn đầy đủ, do đó cắt giảm đáng kể độ dài của đường dẫn đầy đủ + tên tệp. Nó không thực sự là một giải pháp thanh lịch, nhưng nó đã hoàn thành công việc.



2

Các tài liệu tham khảo MSDN cho GetFileAttributesEx nói:

Trong phiên bản ANSI của hàm này, tên được giới hạn ở MAX_PATH ký tự. Để mở rộng giới hạn này lên 32.767 ký tự rộng, hãy gọi phiên bản Unicode của hàm và thêm "\\? \" Vào đường dẫn. Để biết thêm thông tin, hãy xem Đặt tên tệp .

Vì vậy, bạn muốn sử dụng GetFileAttributesExW và đặt tiền tố đường dẫn của bạn bằng "\\? \"


Trích dẫn của bạn là đúng nhưng hơi gây hiểu lầm: Hạn chế này không liên quan gì đến phiên bản ANSI (nó cũng bị giới hạn trong phiên bản Unicode).
user541686

1
nó nói rất rõ ràng rằng bạn cần sử dụng cả phiên bản Unicode và tiền tố để mở rộng giới hạn.
lunixbochs

2

Vui lòng cập nhật tệp cấu hình của bạn như sau:

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

0

Tạo một quy trình riêng sử dụng Robocopy cũng là một giải pháp như đã thảo luận ở đây: Làm thế nào để di chuyển thư mục / tệp có tên đường dẫn> 255 ký tự trong 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();
        }

Như đã thấy trong: Sao chép tệp bằng sao chép và xử lý rô bốt

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.