对于Windows 10上的long path aware进程,我试图了解使用Windows Shell方法PathRelativePathTo时的参数限制。
在下面的示例中,我通过pinvoke使用C#来调用该方法。
我在下面给出了多个示例及其输出。笔记:
来源:
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限制(其余行为未定义)?
最佳答案
从外观上看,PathRelativePathTo API似乎仅对不超过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
Uri path1 = new Uri(@"c:\lvl1\lvl2\");
Uri path2 = new Uri(@"c:\lvl1\lvl3\file1.txt");
Uri diff = path1.MakeRelativeUri(path2);
// Uri will switch to forward slashes, so to fix that...
string relPath =
Uri.UnescapeDataString(diff.OriginalString).Replace("/",@"\");
或者当然,您可以基于
Path.GetRelativePath
的.NET Core源实现某些功能关于c# - 在 "long path aware"环境中对PathRelativePathTo的参数的限制,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/58774168/