Skip to content

Commit eb7b6fe

Browse files
authored
Win32 file picker fixes (#13625)
* Use SIGDN_DESKTOPABSOLUTEPARSING instead of SIGDN_FILESYSPATH, and avoid crashing on GetDisplayName * Don't forget fking .ConfigureAwait(false) * Fix wrong method call * Avoid System.Linq and double iterations over results array
1 parent 0567a6d commit eb7b6fe

File tree

2 files changed

+41
-29
lines changed

2 files changed

+41
-29
lines changed

src/Windows/Avalonia.Win32/Win32Com/win32.idl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ interface IShellItem : IUnknown
8080

8181
HRESULT GetParent([out] IShellItem** ppsi);
8282

83-
HRESULT GetDisplayName(
83+
int GetDisplayName(
8484
[in] uint sigdnName,
85-
[out, string, annotation("_Outptr_result_nullonfailure_")] WCHAR** ppszName);
85+
[string, annotation("_Outptr_result_nullonfailure_")] WCHAR** ppszName);
8686

8787
HRESULT GetAttributes(
8888
[in] ULONG sfgaoMask,

src/Windows/Avalonia.Win32/Win32StorageProvider.cs

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Linq;
32
using System.Collections.Generic;
43
using System.IO;
54
using System.ComponentModel;
@@ -15,10 +14,12 @@ namespace Avalonia.Win32
1514
{
1615
internal class Win32StorageProvider : BclStorageProvider
1716
{
18-
private const uint SIGDN_FILESYSPATH = 0x80058000;
17+
private const uint SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000;
1918

20-
private const FILEOPENDIALOGOPTIONS DefaultDialogOptions = FILEOPENDIALOGOPTIONS.FOS_FORCEFILESYSTEM | FILEOPENDIALOGOPTIONS.FOS_NOVALIDATE |
21-
FILEOPENDIALOGOPTIONS.FOS_NOTESTFILECREATE | FILEOPENDIALOGOPTIONS.FOS_DONTADDTORECENT;
19+
private const FILEOPENDIALOGOPTIONS DefaultDialogOptions =
20+
FILEOPENDIALOGOPTIONS.FOS_PATHMUSTEXIST | FILEOPENDIALOGOPTIONS.FOS_FORCEFILESYSTEM |
21+
FILEOPENDIALOGOPTIONS.FOS_NOVALIDATE | FILEOPENDIALOGOPTIONS.FOS_NOTESTFILECREATE |
22+
FILEOPENDIALOGOPTIONS.FOS_DONTADDTORECENT;
2223

2324
private readonly WindowImpl _windowImpl;
2425

@@ -35,21 +36,23 @@ public Win32StorageProvider(WindowImpl windowImpl)
3536

3637
public override async Task<IReadOnlyList<IStorageFolder>> OpenFolderPickerAsync(FolderPickerOpenOptions options)
3738
{
38-
var files = await ShowFilePicker(
39+
return await ShowFilePicker(
3940
true, true,
4041
options.AllowMultiple, false,
41-
options.Title, null, options.SuggestedStartLocation, null, null);
42-
return files.Select(f => new BclStorageFolder(new DirectoryInfo(f))).ToArray();
42+
options.Title, null, options.SuggestedStartLocation, null, null,
43+
f => new BclStorageFolder(new DirectoryInfo(f)))
44+
.ConfigureAwait(false);
4345
}
4446

4547
public override async Task<IReadOnlyList<IStorageFile>> OpenFilePickerAsync(FilePickerOpenOptions options)
4648
{
47-
var files = await ShowFilePicker(
49+
return await ShowFilePicker(
4850
true, false,
4951
options.AllowMultiple, false,
5052
options.Title, null, options.SuggestedStartLocation,
51-
null, options.FileTypeFilter);
52-
return files.Select(f => new BclStorageFile(new FileInfo(f))).ToArray();
53+
null, options.FileTypeFilter,
54+
f => new BclStorageFile(new FileInfo(f)))
55+
.ConfigureAwait(false);
5356
}
5457

5558
public override async Task<IStorageFile?> SaveFilePickerAsync(FilePickerSaveOptions options)
@@ -58,11 +61,13 @@ public override async Task<IReadOnlyList<IStorageFile>> OpenFilePickerAsync(File
5861
false, false,
5962
false, options.ShowOverwritePrompt,
6063
options.Title, options.SuggestedFileName, options.SuggestedStartLocation,
61-
options.DefaultExtension, options.FileTypeChoices);
62-
return files.Select(f => new BclStorageFile(new FileInfo(f))).FirstOrDefault();
64+
options.DefaultExtension, options.FileTypeChoices,
65+
f => new BclStorageFile(new FileInfo(f)))
66+
.ConfigureAwait(false);
67+
return files.Count > 0 ? files[0] : null;
6368
}
6469

65-
private unsafe Task<IEnumerable<string>> ShowFilePicker(
70+
private unsafe Task<IReadOnlyList<TStorageItem>> ShowFilePicker<TStorageItem>(
6671
bool isOpenFile,
6772
bool openFolder,
6873
bool allowMultiple,
@@ -71,11 +76,13 @@ private unsafe Task<IEnumerable<string>> ShowFilePicker(
7176
string? suggestedFileName,
7277
IStorageFolder? folder,
7378
string? defaultExtension,
74-
IReadOnlyList<FilePickerFileType>? filters)
79+
IReadOnlyList<FilePickerFileType>? filters,
80+
Func<string, TStorageItem> convert)
81+
where TStorageItem : IStorageItem
7582
{
7683
return Task.Run(() =>
7784
{
78-
IEnumerable<string> result = Array.Empty<string>();
85+
IReadOnlyList<TStorageItem> result = Array.Empty<TStorageItem>();
7986
try
8087
{
8188
var clsid = isOpenFile ? UnmanagedMethods.ShellIds.OpenFileDialog : UnmanagedMethods.ShellIds.SaveFileDialog;
@@ -101,7 +108,7 @@ private unsafe Task<IEnumerable<string>> ShowFilePicker(
101108

102109
if (defaultExtension is null)
103110
{
104-
defaultExtension = String.Empty;
111+
defaultExtension = string.Empty;
105112
}
106113

107114
fixed (char* pExt = defaultExtension)
@@ -162,22 +169,22 @@ private unsafe Task<IEnumerable<string>> ShowFilePicker(
162169
var shellItemArray = fileOpenDialog.Results;
163170
var count = shellItemArray.Count;
164171

165-
var results = new List<string>();
172+
var results = new List<TStorageItem>();
166173
for (int i = 0; i < count; i++)
167174
{
168175
var shellItem = shellItemArray.GetItemAt(i);
169-
if (GetAbsoluteFilePath(shellItem) is { } selected)
176+
if (GetParsingName(shellItem) is { } selected)
170177
{
171-
results.Add(selected);
178+
results.Add(convert(selected));
172179
}
173180
}
174181

175182
result = results;
176183
}
177184
else if (frm.Result is { } shellItem
178-
&& GetAbsoluteFilePath(shellItem) is { } singleResult)
185+
&& GetParsingName(shellItem) is { } singleResult)
179186
{
180-
result = new[] { singleResult };
187+
result = new[] { convert(singleResult) };
181188
}
182189

183190
return result;
@@ -191,18 +198,23 @@ private unsafe Task<IEnumerable<string>> ShowFilePicker(
191198
}
192199

193200

194-
private static unsafe string? GetAbsoluteFilePath(IShellItem shellItem)
201+
private static string? GetParsingName(IShellItem shellItem)
202+
{
203+
return GetDisplayName(shellItem, SIGDN_DESKTOPABSOLUTEPARSING);
204+
}
205+
206+
private static unsafe string? GetDisplayName(IShellItem shellItem, uint sigdnName)
195207
{
196-
var pszString = new IntPtr(shellItem.GetDisplayName(SIGDN_FILESYSPATH));
197-
if (pszString != IntPtr.Zero)
208+
char* pszString = null;
209+
if (shellItem.GetDisplayName(sigdnName, &pszString) == 0)
198210
{
199211
try
200212
{
201-
return Marshal.PtrToStringUni(pszString);
213+
return Marshal.PtrToStringUni((IntPtr)pszString);
202214
}
203215
finally
204216
{
205-
Marshal.FreeCoTaskMem(pszString);
217+
Marshal.FreeCoTaskMem((IntPtr)pszString);
206218
}
207219
}
208220
return default;
@@ -224,7 +236,7 @@ private static byte[] FiltersToPointer(IReadOnlyList<FilePickerFileType>? filter
224236
for (int i = 0; i < filters.Count; i++)
225237
{
226238
var filter = filters[i];
227-
if (filter.Patterns is null || !filter.Patterns.Any())
239+
if (filter.Patterns is null || filter.Patterns.Count == 0)
228240
{
229241
continue;
230242
}

0 commit comments

Comments
 (0)