对于Windows 10上的long path aware进程,我试图了解使用Windows Shell方法PathRelativePathTo时的参数限制。

在下面的示例中,我通过pinvoke使用C#来调用该方法。
我在下面给出了多个示例及其输出。笔记:

  • 所有这些示例都提供了“from”的目录路径和“to”的文件路径(这些路径实际上都不存在于磁盘上)
  • 我的观察是
  • “短” MAX_PATH长度(260)下的路径返回成功,并得到预期结果。
  • 在“短” MAX_PATH上的某些路径返回成功,并且结果正确。
  • “短” MAX_PATH上的某些路径以错误的答案返回成功(赞!)
  • 一些更长的路径返回错误。但是,它不是某个固定的最大长度。

  • 来源:
        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的预期行为是什么?
  • 它是否仅应与“short”下的路径一起正常使用
    MAX_PATH限制(其余行为未定义)?
  • .net框架中还有其他我可以使用的东西(注意:我看到.NET Core具有Path.GetRelativePath,但我还不能使用它)?
  • 最佳答案

    从外观上看,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/

    10-11 04:01