Bei einem langen Pfadprozess unter Windows 10 versuche ich zu verstehen, welche Argumentbeschränkungen bei Verwendung der Windows-Shell-Methode PathRelativePathTo bestehen .
In meinem Beispiel unten verwende ich C # über Pinvoke, um die Methode aufzurufen.
Ich habe unten mehrere Beispiele und deren Ausgabe gegeben. Hinweis:
- Alle Beispiele geben Verzeichnispfade für "von" und Dateipfade für "bis" an (keiner dieser Pfade ist tatsächlich auf der Festplatte vorhanden).
- Meine Beobachtungen sind das
- Pfade unter der "kurzen" MAX_PATH-Länge (260) geben Erfolg mit dem erwarteten Ergebnis zurück.
- Einige Pfade über den "kurzen" MAX_PATH geben Erfolg mit dem richtigen Ergebnis zurück.
- Einige Pfade über den "kurzen" MAX_PATH geben Erfolg mit der falschen Antwort zurück (Huch!)
- Einige viel längere Pfade geben einen Fehler zurück. Es hat jedoch keine feste maximale Länge.
Quelle:
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}");
}
}
}
Ausgabe:
#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
Fragen:
- Was ist das erwartete Verhalten in
PathRelativePathTo
Bezug auf die oben genannten? - Wird nur erwartet, dass es ordnungsgemäß mit Pfaden unter dem "kurzen" MAX_PATH-Limit funktioniert (und der Rest des Verhaltens ist undefiniert)?
- Gibt es noch etwas im .net-Framework, das ich stattdessen verwenden kann (Hinweis: Ich sehe, dass .NET Core Path.GetRelativePath hat , aber ich kann das (noch) nicht verwenden)?
PathRelativePathTo
, es ist nicht für lange Wege gedacht. Es ist eigentlich nicht sicher, es zu verwenden, da Sie die Größe des Zielpuffers nicht angeben können. In der Dokumentation heißt es nur, dass "mindestens MAX_PATH-Zeichen groß sein müssen".Antworten:
Wie es aussieht, scheint die PathRelativePathTo-API nur für Pfade bis MAX_LENGTH sicher zu sein. Zumindest aus der Wine-Dokumentation geht hervor, dass die API in der Win32-Implementierung problematisch war.
Und aus der PathCommonPrefix-Dokumentation:
Diese Informationen und die Annahme, dass die shlwapi-Implementierung mit Puffern der Länge MAX_SIZE funktioniert, ähneln denen in Wine oder ReactOS ( https://doxygen.reactos.org/de/dff/dll_2win32_2shlwapi_2path_8c_source.html ) und scheinen das Undefinierte zu erklären Verhalten, das Sie beim Testen sehen.
Was eine .NET-Lösung betrifft, ist der einfachste (möglicherweise nicht der beste) Weg, den ich mir vorstellen kann, die Verwendung
System.Uri
Oder natürlich können Sie etwas implementieren, das auf der .NET Core-Quelle von basiert
Path.GetRelativePath
quelle
.NET 4.6.2 Lösung
Verwenden Sie die hier
\\?\C:\Verrrrrrrrrrrry long path
beschriebene Syntax .Es gibt auch einen großartigen Blog-Beitrag darüber
Im Allgemeinen besteht das größte Problem bei freigegebenen Ordnern über das Web. Der Rest ist in Ordnung.
Ältere .NET-Versionen
Wenn Sie eine ältere Version von .NET verwenden, können Sie diese Win32-API-Funktion überprüfen , die Sie dafür benötigen
P/Invoke
.Die Windows-API verfügt über viele Funktionen, die auch über Unicode-Versionen verfügen, um einen Pfad mit erweiterter Länge für eine maximale Gesamtpfadlänge von 32.767 Zeichen zuzulassen
Sie können sich auch diese SO-Frage ansehen, die Ihrer sehr ähnlich ist.
Wie gehe ich mit Dateien um, deren Name länger als 259 Zeichen ist?
quelle
PathRelativePathTo
PathRelativePathTo
nicht von einem Präfix betroffen. Dies ist eine reine lexikalische Parsing-API, die auf 260 Zeichen fest codiert ist. auch sogar \\ vs / different - break itat Wie kann man einen absoluten oder normalisierten Dateipfad in .NET erhalten? Aha
Ich würde also damit beginnen, um die beiden Pfade zu normalisieren (siehe auch https://blogs.msdn.microsoft.com/jeremykuhne/2016/04/21/path-normalization/). falls dies mehr Fälle abdeckt).
dann würde ich sie in Arrays / Listen von Unterpfaden aufteilen (etwa mit einer der Methoden aus Wie extrahiert man jeden Ordnernamen aus einem Pfad? )
von dort würde ich die max N ersten Teile finden, die üblich sind.
dann würde ich N von der Anzahl der Teile C, auch bekannt als CN, des ersten Pfads subtrahieren, um zu ermitteln, wie viele. \ Ich muss zum ersten Pfad hinzufügen, um zum gemeinsamen Pfad zurückzukehren.
Schließlich würde ich den Rest des toPath hinzufügen, nachdem ich die ersten N Elemente daraus entfernt und den resultierenden Pfad zurückgegeben habe
Vermutlich können Sie dies auch tun (um zusätzlichen Speicherplatz zu vermeiden), indem Sie Zeichenfolgen analysieren (ohne in Listen aufzuteilen), sobald Sie die normalisierten Pfade gefunden haben. Die Idee wäre, dass Sie das gemeinsame Zeichenfolgenpräfix finden und dann den letzten Teil davon kürzen, wenn der gemeinsame Teil nicht mit dem Pfadtrennzeichen endet (da dies ein zufälliger zusätzlicher gemeinsamer Teil wäre, z. B. c: \ a \ test1 und c: \ a \ test2 haben den gemeinsamen Pfad c: \ a \ und nicht c: \ a \ test, wie Sie es mit einer einfachen gemeinsamen Präfix-Zeichenfolgenextraktion erhalten würden.
Alternativ können Sie einen Algorithmus verwenden, der Zeichenindizes für jede Aufarbeitung der beiden normalisierten Pfade gleichzeitig in einer Schleife zurückgibt (jeweils einen Schritt), sodass Sie nichts extra speichern müssen. Die Logik wäre ähnlich der oben beschriebenen.
quelle
Ich entschied mich für einen Port der
dotnet/corefx
Path.GetRelativePath
Methode.Der folgende Code wurde aus den folgenden Quellen angepasst. Lesen Sie die Kommentare im Code, in dem ich alle von mir verwendeten Anpassungen oder Problemumgehungen aufführe:
Mein Ziel bei der Anpassung des Codes war es
GetRelativePath
Code
quelle