Как избежать исключения System.IO.PathTooLongException?

Мы постоянно сталкиваемся с этой проблемой...

Пример:

если у меня есть файл, который я хочу скопировать в другой каталог или в общий каталог UNC, и если длина пути превышает 248 (если я не ошибаюсь), он вызывает исключение PathTooLongException. Есть ли решение этой проблемы?

PS: есть ли настройки реестра, чтобы установить этот путь для более длинного набора символов?

10 ответов

Решение

Как описано в блоге Джереми Куна,.NET Framework 4.6.2 удаляет MAX_PATH ограничение там, где это возможно, без нарушения обратной совместимости.

Попробуйте это: Delimon.Win32.I O Library (V4.0) Это библиотека написана на.NET Framework 4.0

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

https://gallery.technet.microsoft.com/DelimonWin32IO-Library-V40-7ff6b16c

Эта библиотека написана специально для преодоления ограничения.NET Framework на использование длинных путей и имен файлов. С помощью этой библиотеки вы можете программно просматривать, получать доступ, записывать, удалять и т. Д. Файлы и папки, которые не доступны в пространстве имен System.IO.Library

использование

  1. Сначала добавьте ссылку на файл Delimon.Win32.IO.dll в свой проект (перейдите к файлу Delimon.Win32.IO.dll).

  2. В вашем файле кода добавьте "используя Delimon.Win32.IO"

  3. Используйте обычные объекты File & Directory, как если бы вы работали с System.IO

Это было подробно обсуждено командой BCL, см. Записи в блоге.

По сути, нет способа сделать это в коде.Net и придерживаться BCL. Слишком много функций полагаются на возможность канонизировать имя пути (что сразу вызывает использование функций, ожидающих выполнения MAX_PATH).

Вы можете обернуть все функции win32, которые поддерживают синтаксис "\\?\", С их помощью вы сможете реализовать набор функций с поддержкой длинных путей, но это будет громоздко.

Поскольку огромное количество инструментов (включая explorer[1]) не могут обрабатывать длинные имена путей, не рекомендуется идти по этому пути, если вы не удовлетворены тем, что все взаимодействие с полученной файловой системой происходит через вашу библиотеку (или ограниченное количество инструментов, которые построены, чтобы справиться с этим, как Robocopy)

В ответ на ваши конкретные потребности я бы выяснил, будет ли использование робокопии напрямую для выполнения этой задачи.

[1] В Vista есть способы смягчить проблему с помощью некоторого причудливого переименования, но в лучшем случае это хрупко)

Только один обходной путь, который я видел на этом... это может быть полезно

http://www.codeproject.com/KB/files/LongFileNames.aspx

Проблема в ANSI-версиях Windows API. Одним из решений, которое необходимо тщательно протестировать, является принудительное использование Unicode-версий Windows API. Это можно сделать, предварив "\\?\"к пути, который запрашивается.

Отличная информация, включая обходные пути, может быть найдена в следующих сообщениях в блоге от Microsoft Base Base Library (BCL) Team под названием "Длинные пути в.NET":

Я использовал команду "subst", чтобы обойти проблему... http://www.techrepublic.com/article/mapping-drive-letters-to-local-folders-in-windows-xp/5975262

Мое решение Drive-Mapping работает нормально и стабильно, используя "NetWorkDrive.cs" и "NetWorkUNCPath.cs", которые перечислены ниже.

Тестовый пример:

if (srcFileName.Length > 260)
{
   string directoryName = srcFileName.Substring(0, srcFileName.LastIndexOf('\\'));

   var uncName = GetUNCPath(srcFileName.Substring(0, 2)) + directoryName.Substring(2);

   using (NetWorkDrive nDrive = new NetWorkDrive(uncName))
   {
     drvFileName = nDrive.FullDriveLetter + Path.GetFileName(sourceFileName)
     File.Copy(drvFileName, destinationFileName, true);
   }
}
else
{
   File.Copy(srcFileName, destinationFileName, true);
}

Код источникаNetWorkDrive.cs:

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.ComponentModel;
namespace SeekCopySupportTool.Business
{

public class NetWorkDrive : IDisposable
{
    #region private fields
    private string m_DriveLetter = string.Empty;
    private string m_FullDriveLetter = string.Empty;
    private bool m_Disposed = false;
    //this list specifies the drive-letters, whitch will be used to map networkfolders
    private string[] possibleDriveLetters = new string[] 
    { 
        "G:\\", 
        "H:\\", 
        "I:\\", 
        "J:\\", 
        "K:\\", 
        "L:\\", 
        "M:\\", 
        "N:\\", 
        "O:\\", 
        "P:\\", 
        "Q:\\", 
        "R:\\", 
        "S:\\", 
        "T:\\", 
        "U:\\", 
        "V:\\", 
        "W:\\", 
        "X:\\", 
        "Y:\\", 
        "Z:\\" 
    };
    #endregion

    #region public properties
    public string DriveLetter
    {
        get { return m_DriveLetter; }
        set { m_DriveLetter = value; }
    }
    public string FullDriveLetter
    {
        get { return m_FullDriveLetter; }
        set { m_FullDriveLetter = value; }
    }
    #endregion

    #region .ctor
    public NetWorkDrive(string folderPath)
    {
        m_FullDriveLetter = MapFolderAsNetworkDrive(folderPath);
        if (string.IsNullOrEmpty(m_FullDriveLetter))
        {
            throw new Exception("no free valid drive-letter found");
        }
        m_DriveLetter = m_FullDriveLetter.Substring(0,2);
    }
    #endregion

    #region private methods
    /// maps a given folder to a free drive-letter (f:\)
    /// <param name="folderPath">the folder to map</param>
    /// <returns>the drive letter in this format: "(letter):\" -> "f:\"</returns>
    /// <exception cref="Win32Exception">if the connect returns an error</exception>
    private string MapFolderAsNetworkDrive(string folderPath)
    {
        string result = GetFreeDriveLetter();
        NETRESOURCE myNetResource = new NETRESOURCE();
        myNetResource.dwScope = ResourceScope.RESOURCE_GLOBALNET;
        myNetResource.dwType = ResourceType.RESOURCETYPE_ANY;
        myNetResource.dwDisplayType = ResourceDisplayType.RESOURCEDISPLAYTYPE_SERVER;
        myNetResource.dwUsage = ResourceUsage.RESOURCEUSAGE_CONNECTABLE;
        myNetResource.lpLocalName = result.Substring(0,2);
        myNetResource.lpRemoteName = folderPath;
        myNetResource.lpProvider = null;
        int errorcode = WNetAddConnection2(myNetResource, null, null, 0);
        if(errorcode != 0)
        {
            throw new Win32Exception(errorcode);
        }
        return result;
    }
    private void DisconnectNetworkDrive()
    {
        int CONNECT_UPDATE_PROFILE = 0x1;
        int errorcode = WNetCancelConnection2(m_DriveLetter, CONNECT_UPDATE_PROFILE, true);
        if (errorcode != 0)
        {
            throw new Win32Exception(errorcode);
        }
    }

    private string GetFreeDriveLetter()
    {
        //first get the existing driveletters
        const int size = 512;
        char[] buffer = new char[size];
        uint code = GetLogicalDriveStrings(size, buffer);
        if (code == 0)
        {                
            return "";
        }
        List<string> list = new List<string>();
        int start = 0;
        for (int i = 0; i < code; ++i)
        {
            if (buffer[i] == 0)
            {
                string s = new string(buffer, start, i - start);
                list.Add(s);
                start = i + 1;
            }
        }            
        foreach (string s in possibleDriveLetters)
        {                
            if (!list.Contains(s))
            {
                return s;
            }
        }
        return null;
    }        
    #endregion

    #region dll imports
    /// <summary>
    /// to connect to a networksource
    /// </summary>
    /// <param name="netResource"></param>
    /// <param name="password">null the function uses the current default password associated with the user specified by the username parameter ("" the function does not use a password)</param>
    /// <param name="username">null the function uses the default user name (The user context for the process provides the default user name)</param>
    /// <param name="flags"></param>
    /// <returns></returns>
    [DllImport("mpr.dll")]
    //public static extern int WNetAddConnection2(ref NETRESOURCE netResource, string password, string username, int flags);
    private static extern int WNetAddConnection2([In] NETRESOURCE netResource, string password, string username, int flags);
    /// <summary>
    /// to disconnect the networksource
    /// </summary>
    /// <param name="lpName"></param>
    /// <param name="dwFlags"></param>
    /// <param name="bForce"></param>
    /// <returns></returns>
    [DllImport("mpr.dll")]
    private static extern int WNetCancelConnection2(string lpName, Int32 dwFlags, bool bForce);
    /// <param name="nBufferLength"></param>
    /// <param name="lpBuffer"></param>
    /// <returns></returns>
    [DllImport("kernel32.dll")]
    private static extern uint GetLogicalDriveStrings(uint nBufferLength, [Out] char[] lpBuffer);
    #endregion

    #region enums/structs
    /// <example>
    /// NETRESOURCE myNetResource = new NETRESOURCE();
    /// myNetResource.dwScope = 2;
    /// myNetResource.dwType = 1;
    /// myNetResource.dwDisplayType = 3;
    /// myNetResource.dwUsage = 1;
    /// myNetResource.LocalName = "z:";
    /// myNetResource.RemoteName = @"\servername\sharename";
    /// myNetResource.Provider = null;
    /// </example>
    [StructLayout(LayoutKind.Sequential)]
    public class NETRESOURCE
    {
        public ResourceScope dwScope = 0;
        public ResourceType dwType = 0;
        public ResourceDisplayType dwDisplayType = 0;
        public ResourceUsage dwUsage = 0;
        public string lpLocalName = null;
        public string lpRemoteName = null;
        public string lpComment = null;
        public string lpProvider = null;
    };
    public enum ResourceScope : int
    {
        RESOURCE_CONNECTED = 1,
        RESOURCE_GLOBALNET,
        RESOURCE_REMEMBERED,
        RESOURCE_RECENT,
        RESOURCE_CONTEXT
    };
    public enum ResourceType : int
    {
        RESOURCETYPE_ANY,
        RESOURCETYPE_DISK,
        RESOURCETYPE_PRINT,
        RESOURCETYPE_RESERVED
    };
    public enum ResourceUsage
    {
        RESOURCEUSAGE_CONNECTABLE = 0x00000001,
        RESOURCEUSAGE_CONTAINER = 0x00000002,
        RESOURCEUSAGE_NOLOCALDEVICE = 0x00000004,
        RESOURCEUSAGE_SIBLING = 0x00000008,
        RESOURCEUSAGE_ATTACHED = 0x00000010,
        RESOURCEUSAGE_ALL = (RESOURCEUSAGE_CONNECTABLE | RESOURCEUSAGE_CONTAINER | RESOURCEUSAGE_ATTACHED),
    };
    public enum ResourceDisplayType
    {
        RESOURCEDISPLAYTYPE_GENERIC,
        RESOURCEDISPLAYTYPE_DOMAIN,
        RESOURCEDISPLAYTYPE_SERVER,
        RESOURCEDISPLAYTYPE_SHARE,
        RESOURCEDISPLAYTYPE_FILE,
        RESOURCEDISPLAYTYPE_GROUP,
        RESOURCEDISPLAYTYPE_NETWORK,
        RESOURCEDISPLAYTYPE_ROOT,
        RESOURCEDISPLAYTYPE_SHAREADMIN,
        RESOURCEDISPLAYTYPE_DIRECTORY,
        RESOURCEDISPLAYTYPE_TREE,
        RESOURCEDISPLAYTYPE_NDSCONTAINER
    };
    #endregion

    #region IDisposable Members
    public void Dispose()
    {
        Dispose(true);
    }
    #endregion

    #region overrides/virtuals
    public override string ToString()
    {
        return m_FullDriveLetter;
    }
    /// <param name="disposing"></param>
    protected virtual void Dispose(bool disposing)
    {
        if (!m_Disposed)
        {
            if (disposing)
            {
                DisconnectNetworkDrive();
            }
            m_Disposed = true;
        }
    }
    #endregion
}

}

Исходный кодNetWorkUNCPath.cs:

using System;
using System.Management;
namespace SeekCopySupportTool.Business
{

public class NetWorkUNCPath
{
    // get UNC path
    public static string GetUNCPath(string path)
    {
        if (path.StartsWith(@"\\"))
        {
            return path;
        }

        ManagementObject mo = new ManagementObject();
        mo.Path = new ManagementPath(String.Format("Win32_LogicalDisk='{0}'", path));

        // DriveType 4 = Network Drive
        if (Convert.ToUInt32(mo["DriveType"]) == 4)
        {
            return Convert.ToString(mo["ProviderName"]);
        }
        // DriveType 3 = Local Drive
        else if (Convert.ToUInt32(mo["DriveType"]) == 3)
        {
            return "\\\\" + Environment.MachineName + "\\" + path.Substring(0,1) + "$";
        }
        else
        {
            return path;
        }
    }
}

}

Эта библиотека может быть полезна: Zeta Long Paths

Я был удивлен, обнаружив, что решение Джейсона отлично работает на моем ПК с использованием Visual Studio 2019 и .Net Framework 4.7.2.

Изначально я написал что-то похожее на этот код

      Dim sBaseDir = "D:\Documents\+Informatique\Application\@Visual Basic.NET\GetTestAchatsPosts\Site\communication-multimedia\ordinateurs"
Dim sLastDir = "2638.pc-acer-ne-charge-plus-malgre-un-retour-en-garantie-reviens-charge-mais-ne-charge-toujours-pas-garantie-passee-plus-dintervention-gra"

System.IO.Directory.CreateDirectory(sBaseDir & "\" & sLastDir)

Этот код генерирует ошибку 429-PathTooLongException


Затем я протестировал создание только последнего каталога

      System.IO.Directory.SetCurrentDirectory(sBaseDir)
System.IO.Directory.CreateDirectory(sLastDir)

Этот код возвращает ту же ошибку 429-PathTooLongException


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

      System.IO.Directory.SetCurrentDirectory("D:\Documents")
System.IO.Directory.CreateDirectory(sLastDir)

Этот код работает. Но каталог создается в плохом месте.


Затем я протестировал уменьшение длины имени последнего каталога.

      System.IO.Directory.SetCurrentDirectory(sBaseDir)
System.IO.Directory.CreateDirectory(sLastDir.Substring(0, 100))

Этот код работает, но последнее имя каталога уменьшено!


Затем я протестировал использование предложения Джейсона в 2 этапа.

      System.IO.Directory.SetCurrentDirectory(sBaseDir)
System.IO.Directory.CreateDirectory("\\?\" & sLastDir)

Этот код дает сбой, указывая на то, что синтаксис каталога неверен!


Затем я также проверил тот же синтаксис при добавлении "." нить

      System.IO.Directory.SetCurrentDirectory(sBaseDir)
System.IO.Directory.CreateDirectory("\\?\.\" & sLastDir)

Этот код дает сбой, указывая на то, что синтаксис каталога неверен!


Затем я протестировал, используя простую команду, префикс полного каталога с "\\?"

      System.IO.Directory.CreateDirectory("\\?\" & sBaseDir & "\" & sLastDir)

Этот код работает отлично, и это лучшее решение для меня.

Единственным неудобством является то, что каталог должен быть полным каталогом.

Предупреждение ! Нельзя смешивать символы «\» и «/»!

Я написал следующий код


и программа вылетает при выполнении CreateDirectory() function !

Правильный код, который работает

      sQuestionDir = "\\?\" & sArticleDir & "/" & sQuestionDir
If Not System.IO.Directory.Exists(sQuestionDir) Then
    System.IO.Directory.CreateDirectory(sQuestionDir)
End If

Я надеюсь, что все эти тесты могут помочь другим решить их проблему.

В C# для меня это обходной путь:

/*make long path short by setting it to like cd*/
string path = @"\\godDamnLong\Path\";
Directory.SetCurrentDirectory(path);
Другие вопросы по тегам