Untuk proses sadar jalur panjang pada Windows 10, saya mencoba memahami apa batasan argumen saat menggunakan metode shell windows PathRelativePathTo .
Dalam contoh saya di bawah ini, saya menggunakan C # via pinvoke untuk memanggil metode.
Saya telah memberikan beberapa contoh di bawah ini dan hasilnya. catatan:
- Semua contoh memberikan path direktori untuk "from" dan path file untuk "to" (tidak ada path ini yang benar-benar ada pada disk)
- Pengamatan saya adalah itu
- Jalur di bawah "pendek" MAX_PATH panjang (260) mengembalikan kesuksesan dengan hasil yang diharapkan.
- Beberapa jalur di atas "pendek" MAX_PATH mengembalikan kesuksesan dengan hasil yang benar.
- Beberapa jalur di atas "pendek" MAX_PATH mengembalikan kesuksesan dengan jawaban yang salah (ya!)
- Beberapa jalur yang jauh lebih lama mengembalikan Kesalahan. Namun, itu tidak pada beberapa panjang maks tetap.
Sumber:
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}");
}
}
}
Keluaran:
#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
Pertanyaan:
- Apa perilaku yang diharapkan
PathRelativePathTo
sehubungan dengan hal di atas? - Apakah hanya diharapkan berfungsi dengan baik dengan jalur di bawah batas MAX_PATH "pendek" (dan perilaku lainnya tidak ditentukan)?
- Apakah ada sesuatu yang lain dalam kerangka .net yang dapat saya gunakan sebagai gantinya (Catatan: Saya melihat bahwa .NET Core memiliki Path.GetRelativePath , tapi saya belum (belum) dapat menggunakannya)?
PathRelativePathTo
, itu tidak dimaksudkan untuk jalan panjang. Sebenarnya tidak aman untuk menggunakannya, karena Anda tidak dapat menyatakan ukuran buffer tujuan, dokumentasi hanya mengatakan itu "harus berukuran setidaknya MAX_PATH karakter."Jawaban:
Dari tampilannya, sepertinya PathRelativePathTo API aman hanya untuk jalur hingga MAX_LENGTH. Atleast dari dokumentasi Wine, kita melihat bahwa API telah bermasalah dalam implementasi Win32.
Dan dari dokumentasi PathCommonPrefix,
Informasi ini dan asumsi implementasi shlwapi bekerja dengan buffer dengan panjang MAX_SIZE dan mirip dengan apa yang ada di Wine atau ReactOS ( https://doxygen.reactos.org/de/dff/dll_2win32_2shlwapi_2path_8c_source.html ) agaknya menjelaskan jenis yang tidak terdefinisi. perilaku yang Anda lihat dalam pengujian.
Adapun solusi. NET, cara termudah (mungkin bukan yang terbaik) yang bisa saya pikirkan adalah menggunakan
System.Uri
Atau tentu saja Anda dapat mengimplementasikan sesuatu berdasarkan sumber .NET Core dari
Path.GetRelativePath
sumber
.NET 4.6.2 solusi
Gunakan
\\?\C:\Verrrrrrrrrrrry long path
sintaks seperti yang dijelaskan di sini .Ada juga posting blog yang bagus tentang ini
Secara umum masalah terbesar yang saya miliki adalah dengan folder bersama melalui web. Sisanya baik-baik saja.
Versi .NET yang lebih lama
Jika Anda menggunakan versi .NET yang lebih lama, Anda dapat memeriksa fungsi Win32 API ini , Anda perlu
P/Invoke
melakukannya.Windows API memiliki banyak fungsi yang juga memiliki versi Unicode untuk mengizinkan jalur panjang-panjang untuk panjang jalur total maksimum 32.767 karakter
Anda juga dapat memeriksa pertanyaan SO ini, yang sangat mirip dengan Anda.
Bagaimana cara menangani file dengan nama lebih dari 259 karakter?
sumber
PathRelativePathTo
PathRelativePathTo
tidak terpengaruh oleh awalan apa pun. ini adalah api penguraian leksikal murni, hardcoded hingga 260 karakter batas. juga bahkan \\ vs / berbeda - pecahkandi Bagaimana seseorang bisa mendapatkan jalur file absolut atau normal di .NET? saya melihat
jadi saya akan mulai dengan itu untuk menormalkan dua jalur (juga lihat https://blogs.msdn.microsoft.com/jeremykuhne/2016/04/21/path-normalization/ dalam kasus yang mencakup lebih banyak kasus)
maka saya akan membaginya menjadi array / daftar subpath (katakanlah dengan salah satu metode dari Bagaimana cara mengekstrak setiap nama folder dari path? )
dari sana saya akan menemukan max N bagian pertama yang umum.
maka saya akan mengurangi N dari hitungan jalur pertama dari bagian C, alias CN untuk mendapatkan berapa banyak .. \ Saya perlu menambahkan ke jalur pertama untuk kembali ke jalur umum.
akhirnya saya menambahkan sisa toPath setelah menghapus item N pertama darinya dan mengembalikan path yang dihasilkan
Kira Anda juga bisa melakukan itu (untuk menghindari penyimpanan tambahan) dengan penguraian string (tanpa membelah daftar) setelah Anda menemukan jalur yang dinormalisasi. Idenya adalah Anda akan menemukan awalan string umum dan kemudian memotong bagian terakhirnya jika bagian umum tidak berakhir dengan pemisah jalur (karena itu akan menjadi bagian umum tambahan yang kebetulan, misalnya c: \ a \ test1 dan c: \ a \ test2 memiliki jalur umum c: \ a \ dan bukan c: \ a \ test seperti yang Anda dapatkan dengan ekstraksi string awalan umum yang sederhana).
Sebagai alternatif, Anda dapat menggunakan algoritma yang mengembalikan indeks karakter untuk setiap \ mengerjakan dua jalur dinormalisasi pada saat yang sama dalam satu lingkaran (satu langkah pada masing-masing) sehingga Anda tidak perlu menyimpan sesuatu yang ekstra. Logikanya akan mirip dengan yang dijelaskan di atas.
sumber
Saya memutuskan untuk menggunakan port metode.
dotnet/corefx
Path.GetRelativePath
Kode berikut diadaptasi dari sumber-sumber berikut. Baca komentar dalam kode tempat saya mencantumkan penyesuaian atau penyelesaian yang saya gunakan:
Tujuan saya dalam mengadaptasi kode adalah untuk
GetRelativePath
Kode
sumber