В процессе обработки длинных путей в Windows 10 я пытаюсь понять, каковы ограничения аргументов при использовании метода оболочки Windows PathRelativePathTo .
В моем примере ниже я использую C # через pinvoke для вызова метода.
Я дал несколько примеров ниже и их вывод. Замечания:
- Во всех примерах указаны пути к каталогам для «from» и пути к файлам для «to» (на самом деле ни один из этих путей не существует на диске)
- Мои наблюдения таковы, что
- Пути под «короткой» длиной MAX_PATH (260) возвращают успех с ожидаемым результатом.
- Некоторые пути по «короткому» MAX_PATH возвращают успех с правильным результатом.
- Некоторые пути по «короткому» MAX_PATH возвращают успех с неправильным ответом (yikes!)
- Некоторые более длинные пути возвращают ошибку. Однако он не имеет фиксированной максимальной длины.
Источник:
class Program
{
static class Native
{
// https://www.pinvoke.net/default.aspx/shlwapi.pathrelativepathto
// https://docs.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-pathrelativepathtoa
[DllImport("shlwapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool PathRelativePathTo([Out] StringBuilder pszPath, [In] string pszFrom, [In] int dwAttrFrom, [In] string pszTo, [In] int dwAttrTo);
}
static void Main(string[] args)
{
string pszFrom, pszTo;
int i = 0;
// #1 At "short" max path (259)
// Succeeds with right answer
pszFrom = @"c:\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCD123456789";
pszTo = @"c:\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCD123456789\abcdefghijklmnop.txt";
TestPathRelativePathTo(++i, pszFrom, pszTo);
// #2 One over "short" max path
// Succeeds with right answer
pszFrom = @"c:\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCD1234567890";
pszTo = @"c:\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCD1234567890\abcdefghijklmnop.txt";
TestPathRelativePathTo(++i, pszFrom, pszTo);
// #3 Shortest path (by experiment) that returned the wrong answer
pszFrom = @"c:\ABCDEFGHIJKLMNOPQRS\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCD1234567890";
pszTo = @"c:\ABCDEFGHIJKLMNOPQRS\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCD1234567890\b.txt";
TestPathRelativePathTo(++i, pszFrom, pszTo);
// #4: Long path that errors out
// Errors out
pszFrom = @"c:\ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGH\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
pszTo = @"c:\ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGH\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\b.txt";
TestPathRelativePathTo(++i, pszFrom, pszTo);
// #5: Same as previous except one character removed from beginning of first folder
// Succeeds, but wrong return result
pszFrom = @"c:\BCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGH\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
pszTo = @"c:\BCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGH\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\b.txt";
TestPathRelativePathTo(++i, pszFrom, pszTo);
// #6: Same as previous except 3 characters added to filename.
// Succeeds, but wrong return result
pszFrom = @"c:\BCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGH\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
pszTo = @"c:\BCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGH\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\b123.txt";
TestPathRelativePathTo(++i, pszFrom, pszTo);
}
static void TestPathRelativePathTo(int i, string pszFromDir, string pszToFile)
{
int maxResult = 10000;
StringBuilder result = new StringBuilder(maxResult);
Console.WriteLine($"#{i}: Calling PathRelativePathTo(...): pszFrom.Length: {pszFromDir.Length}; pszTo.Length {pszToFile.Length} ");
bool bRet = Native.PathRelativePathTo(result, pszFromDir, (int)FileAttributes.Directory, pszToFile, (int)FileAttributes.Normal);
if (!bRet)
{
// *Edit*: As pointed out in the comments, PathRelativePathTo does not set last error, so this part of the code is incorrect, it should really just print out that the method returned false.
// https://blogs.msdn.microsoft.com/shawnfa/2004/09/10/formatmessage-shortcut-for-win32-error-codes/
int currentError = Marshal.GetLastWin32Error();
var errorMessage = new Win32Exception(currentError).Message;
Console.WriteLine($" Error: {errorMessage}");
}
else
{
Console.WriteLine($" Result: {result}");
}
}
}
Вывод:
#1: Calling PathRelativePathTo(...): pszFrom.Length: 238; pszTo.Length 259
Result: .\abcdefghijklmnop.txt
#2: Calling PathRelativePathTo(...): pszFrom.Length: 239; pszTo.Length 260
Result: .\abcdefghijklmnop.txt
#3: Calling PathRelativePathTo(...): pszFrom.Length: 259; pszTo.Length 265
Result: ..\ABCD1234567890\b.txt
#4: Calling PathRelativePathTo(...): pszFrom.Length: 481; pszTo.Length 487
Error: The system cannot find the file specified
#5: Calling PathRelativePathTo(...): pszFrom.Length: 480; pszTo.Length 486
Result: .\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\b.txt
#6: Calling PathRelativePathTo(...): pszFrom.Length: 480; pszTo.Length 489
Result: .\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\b123.txt
Вопросов:
- Каково ожидаемое поведение по
PathRelativePathTo
отношению к вышесказанному? - Ожидается ли, что он будет правильно работать только с путями, находящимися под «коротким» пределом MAX_PATH (а остальная часть поведения не определена)?
- Есть ли что-то еще в .net Framework, которое я могу использовать вместо этого (Примечание: я вижу, что .NET Core имеет Path.GetRelativePath , но я не могу (пока) использовать это)?
PathRelativePathTo
, это не предназначено для длинных путей. На самом деле его использовать небезопасно, поскольку вы не можете указать размер буфера назначения, в документации сказано, что он «должен иметь размер не менее MAX_PATH».Ответы:
Судя по всему, API PathRelativePathTo безопасен только для путей до MAX_LENGTH. Из документации Wine мы видим, что API был проблематичным в реализации Win32.
И из документации PathCommonPrefix,
Эта информация и предполагается, что реализация shlwapi работает с буферами длины MAX_SIZE и похожа на ту, что есть в Wine или ReactOS ( https://doxygen.reactos.org/de/dff/dll_2win32_2shlwapi_2path_8c_source.html ), кажется, что-то вроде неопределенного поведение, которое вы видите в тестировании.
Что касается решения .NET, самый простой способ (возможно, не самый лучший), который я могу придумать, - это использовать
System.Uri
Или, конечно, вы можете реализовать что-то на основе источника .NET Core
Path.GetRelativePath
источник
Решение .NET 4.6.2
Используйте
\\?\C:\Verrrrrrrrrrrry long path
синтаксис, как описано здесь .Есть также отличный пост в блоге об этом
В целом, самая большая проблема, которую я имею, связана с общими папками в Интернете. Остальное в порядке.
Старые версии .NET
Если вы используете более старую версию .NET, вы можете проверить эту функцию Win32 API , она вам понадобится
P/Invoke
.Windows API имеет много функций, которые также имеют версии Unicode, чтобы разрешить путь расширенной длины для максимальной общей длины пути 32 767 символов
Также вы можете проверить этот вопрос, который очень похож на ваш.
Как работать с файлами с именем длиннее 259 символов?
источник
PathRelativePathTo
PathRelativePathTo
не затрагиваемой никаким префиксом. это чистый лексический синтаксический анализ API, жестко закодированный до ограничения в 260 символов. также даже \\ против / другой - сломай егона Как можно получить абсолютный или нормализованный путь к файлу в .NET? я вижу
поэтому я бы начал с этого, чтобы нормализовать два пути (также см. https://blogs.msdn.microsoft.com/jeremykuhne/2016/04/21/path-normalization/ в случае, который охватывает больше случаев)
затем я разделил бы их на массивы / списки подпутей (скажем, одним из методов из Как извлечь каждое имя папки из пути? )
оттуда я бы нашел макс N первых частей, которые являются общими.
затем я вычел бы N из числа частей C первого пути, иначе говоря CN, чтобы получить сколько .. \ Мне нужно добавить к первому пути, чтобы вернуться к общему пути.
наконец, я добавил бы остальную часть toPath после удаления первых N элементов из него и вернул бы полученный путь
Думаю, вы также можете сделать это (чтобы избежать дополнительной памяти) с помощью анализа строк (без разделения на списки), как только вы найдете нормализованные пути. Идея заключается в том, что вы найдете общий префикс строки и затем обрежете последнюю его часть, если общая часть не заканчивается разделителем пути (поскольку это будет случайная дополнительная общая часть, например, c: \ a \ test1 и c: \ a \ test2 имеют общий путь c: \ a \, а не c: \ a \ test, как вы могли бы получить простым извлечением строки с общим префиксом).
В качестве альтернативы вы можете использовать алгоритм, который возвращает символьные индексы для каждого \ обработки двух нормализованных путей одновременно в цикле (по одному шагу на каждом), чтобы вам не нужно было хранить что-то дополнительное. Логика будет похожа на ту, что описана выше.
источник
Я решил использовать порт метода.
dotnet/corefx
Path.GetRelativePath
Следующий код был адаптирован из следующих источников. Прочитайте комментарии в коде, где я перечисляю любые корректировки или обходные пути, которые я использовал:
Моей целью в адаптации кода было
GetRelativePath
Код
источник