committed by
GitHub
8 changed files with 431 additions and 368 deletions
@ -1,170 +1,229 @@ |
|||
#nullable enable |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel; |
|||
using System.Linq; |
|||
using System.Runtime.InteropServices; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Platform; |
|||
using Avalonia.MicroCom; |
|||
using Avalonia.Win32.Interop; |
|||
using Avalonia.Win32.Win32Com; |
|||
|
|||
namespace Avalonia.Win32 |
|||
{ |
|||
class SystemDialogImpl : ISystemDialogImpl |
|||
internal class SystemDialogImpl : ISystemDialogImpl |
|||
{ |
|||
private const UnmanagedMethods.FOS DefaultDialogOptions = UnmanagedMethods.FOS.FOS_FORCEFILESYSTEM | UnmanagedMethods.FOS.FOS_NOVALIDATE | |
|||
UnmanagedMethods.FOS.FOS_NOTESTFILECREATE | UnmanagedMethods.FOS.FOS_DONTADDTORECENT; |
|||
private const uint SIGDN_FILESYSPATH = 0x80058000; |
|||
|
|||
public unsafe Task<string[]> ShowFileDialogAsync(FileDialog dialog, Window parent) |
|||
private const FILEOPENDIALOGOPTIONS DefaultDialogOptions = FILEOPENDIALOGOPTIONS.FOS_FORCEFILESYSTEM | FILEOPENDIALOGOPTIONS.FOS_NOVALIDATE | |
|||
FILEOPENDIALOGOPTIONS.FOS_NOTESTFILECREATE | FILEOPENDIALOGOPTIONS.FOS_DONTADDTORECENT; |
|||
|
|||
public unsafe Task<string[]?> ShowFileDialogAsync(FileDialog dialog, Window parent) |
|||
{ |
|||
var hWnd = parent?.PlatformImpl?.Handle?.Handle ?? IntPtr.Zero; |
|||
return Task.Factory.StartNew(() => |
|||
return Task.Run(() => |
|||
{ |
|||
string[] result = default; |
|||
|
|||
Guid clsid = dialog is OpenFileDialog ? UnmanagedMethods.ShellIds.OpenFileDialog : UnmanagedMethods.ShellIds.SaveFileDialog; |
|||
Guid iid = UnmanagedMethods.ShellIds.IFileDialog; |
|||
UnmanagedMethods.CoCreateInstance(ref clsid, IntPtr.Zero, 1, ref iid, out object unk); |
|||
var frm = (UnmanagedMethods.IFileDialog)unk; |
|||
string[]? result = default; |
|||
try |
|||
{ |
|||
var clsid = dialog is OpenFileDialog ? UnmanagedMethods.ShellIds.OpenFileDialog : UnmanagedMethods.ShellIds.SaveFileDialog; |
|||
var iid = UnmanagedMethods.ShellIds.IFileDialog; |
|||
var frm = UnmanagedMethods.CreateInstance<IFileDialog>(ref clsid, ref iid); |
|||
|
|||
var openDialog = dialog as OpenFileDialog; |
|||
var openDialog = dialog as OpenFileDialog; |
|||
|
|||
uint options; |
|||
frm.GetOptions(out options); |
|||
options |= (uint)(DefaultDialogOptions); |
|||
if (openDialog?.AllowMultiple == true) |
|||
options |= (uint)UnmanagedMethods.FOS.FOS_ALLOWMULTISELECT; |
|||
frm.SetOptions(options); |
|||
var options = frm.Options; |
|||
options |= DefaultDialogOptions; |
|||
if (openDialog?.AllowMultiple == true) |
|||
{ |
|||
options |= FILEOPENDIALOGOPTIONS.FOS_ALLOWMULTISELECT; |
|||
} |
|||
frm.SetOptions(options); |
|||
|
|||
var defaultExtension = (dialog as SaveFileDialog)?.DefaultExtension ?? ""; |
|||
frm.SetDefaultExtension(defaultExtension); |
|||
frm.SetFileName(dialog.InitialFileName ?? ""); |
|||
frm.SetTitle(dialog.Title ?? ""); |
|||
var defaultExtension = (dialog as SaveFileDialog)?.DefaultExtension ?? ""; |
|||
fixed (char* pExt = defaultExtension) |
|||
{ |
|||
frm.SetDefaultExtension(pExt); |
|||
} |
|||
|
|||
var filters = new List<UnmanagedMethods.COMDLG_FILTERSPEC>(); |
|||
if (dialog.Filters != null) |
|||
{ |
|||
foreach (var filter in dialog.Filters) |
|||
var initialFileName = dialog.InitialFileName ?? ""; |
|||
fixed (char* fExt = initialFileName) |
|||
{ |
|||
var extMask = string.Join(";", filter.Extensions.Select(e => "*." + e)); |
|||
filters.Add(new UnmanagedMethods.COMDLG_FILTERSPEC { pszName = filter.Name, pszSpec = extMask }); |
|||
frm.SetFileName(fExt); |
|||
} |
|||
} |
|||
if (filters.Count == 0) |
|||
filters.Add(new UnmanagedMethods.COMDLG_FILTERSPEC { pszName = "All files", pszSpec = "*.*" }); |
|||
|
|||
frm.SetFileTypes((uint)filters.Count, filters.ToArray()); |
|||
frm.SetFileTypeIndex(0); |
|||
var title = dialog.Title ?? ""; |
|||
fixed (char* tExt = title) |
|||
{ |
|||
frm.SetTitle(tExt); |
|||
} |
|||
|
|||
if (dialog.Directory != null) |
|||
{ |
|||
UnmanagedMethods.IShellItem directoryShellItem; |
|||
Guid riid = UnmanagedMethods.ShellIds.IShellItem; |
|||
if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.Directory, IntPtr.Zero, ref riid, out directoryShellItem) == (uint)UnmanagedMethods.HRESULT.S_OK) |
|||
fixed (void* pFilters = FiltersToPointer(dialog.Filters, out var count)) |
|||
{ |
|||
frm.SetFolder(directoryShellItem); |
|||
frm.SetDefaultFolder(directoryShellItem); |
|||
frm.SetFileTypes((ushort)count, pFilters); |
|||
} |
|||
} |
|||
|
|||
if (frm.Show(hWnd) == (uint)UnmanagedMethods.HRESULT.S_OK) |
|||
{ |
|||
if (openDialog?.AllowMultiple == true) |
|||
frm.SetFileTypeIndex(0); |
|||
|
|||
if (dialog.Directory != null) |
|||
{ |
|||
UnmanagedMethods.IShellItemArray shellItemArray; |
|||
((UnmanagedMethods.IFileOpenDialog)frm).GetResults(out shellItemArray); |
|||
uint count; |
|||
shellItemArray.GetCount(out count); |
|||
result = new string[count]; |
|||
for (uint i = 0; i < count; i++) |
|||
var riid = UnmanagedMethods.ShellIds.IShellItem; |
|||
if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.Directory, IntPtr.Zero, ref riid, out var directoryShellItem) |
|||
== (uint)UnmanagedMethods.HRESULT.S_OK) |
|||
{ |
|||
UnmanagedMethods.IShellItem shellItem; |
|||
shellItemArray.GetItemAt(i, out shellItem); |
|||
result[i] = GetAbsoluteFilePath(shellItem); |
|||
var proxy = MicroComRuntime.CreateProxyFor<IShellItem>(directoryShellItem, true); |
|||
frm.SetFolder(proxy); |
|||
frm.SetDefaultFolder(proxy); |
|||
} |
|||
} |
|||
else |
|||
|
|||
frm.Show(hWnd); |
|||
|
|||
if (openDialog?.AllowMultiple == true) |
|||
{ |
|||
UnmanagedMethods.IShellItem shellItem; |
|||
if (frm.GetResult(out shellItem) == (uint)UnmanagedMethods.HRESULT.S_OK) |
|||
using var fileOpenDialog = frm.QueryInterface<IFileOpenDialog>(); |
|||
var shellItemArray = fileOpenDialog.Results; |
|||
var count = shellItemArray.Count; |
|||
|
|||
var results = new List<string>(); |
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
result = new string[] { GetAbsoluteFilePath(shellItem) }; |
|||
var shellItem = shellItemArray.GetItemAt(i); |
|||
if (GetAbsoluteFilePath(shellItem) is { } selected) |
|||
{ |
|||
results.Add(selected); |
|||
} |
|||
} |
|||
result = results.ToArray(); |
|||
} |
|||
else if (frm.Result is { } shellItem |
|||
&& GetAbsoluteFilePath(shellItem) is { } singleResult) |
|||
{ |
|||
result = new[] { singleResult }; |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
}); |
|||
return result; |
|||
} |
|||
catch (COMException ex) |
|||
{ |
|||
if ((uint)ex.HResult == (uint)UnmanagedMethods.HRESULT.E_CANCELLED) |
|||
{ |
|||
return result; |
|||
} |
|||
throw new Win32Exception(ex.HResult); |
|||
} |
|||
})!; |
|||
} |
|||
|
|||
public Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent) |
|||
public unsafe Task<string?> ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent) |
|||
{ |
|||
return Task.Factory.StartNew(() => |
|||
return Task.Run(() => |
|||
{ |
|||
string result = default; |
|||
string? result = default; |
|||
try |
|||
{ |
|||
var hWnd = parent?.PlatformImpl?.Handle?.Handle ?? IntPtr.Zero; |
|||
var clsid = UnmanagedMethods.ShellIds.OpenFileDialog; |
|||
var iid = UnmanagedMethods.ShellIds.IFileDialog; |
|||
var frm = UnmanagedMethods.CreateInstance<IFileDialog>(ref clsid, ref iid); |
|||
|
|||
var hWnd = parent?.PlatformImpl?.Handle?.Handle ?? IntPtr.Zero; |
|||
Guid clsid = UnmanagedMethods.ShellIds.OpenFileDialog; |
|||
Guid iid = UnmanagedMethods.ShellIds.IFileDialog; |
|||
var options = frm.Options; |
|||
options = FILEOPENDIALOGOPTIONS.FOS_PICKFOLDERS | DefaultDialogOptions; |
|||
frm.SetOptions(options); |
|||
|
|||
UnmanagedMethods.CoCreateInstance(ref clsid, IntPtr.Zero, 1, ref iid, out object unk); |
|||
var frm = (UnmanagedMethods.IFileDialog)unk; |
|||
uint options; |
|||
frm.GetOptions(out options); |
|||
options |= (uint)(UnmanagedMethods.FOS.FOS_PICKFOLDERS | DefaultDialogOptions); |
|||
frm.SetOptions(options); |
|||
frm.SetTitle(dialog.Title ?? ""); |
|||
var title = dialog.Title ?? ""; |
|||
fixed (char* tExt = title) |
|||
{ |
|||
frm.SetTitle(tExt); |
|||
} |
|||
|
|||
if (dialog.Directory != null) |
|||
{ |
|||
UnmanagedMethods.IShellItem directoryShellItem; |
|||
Guid riid = UnmanagedMethods.ShellIds.IShellItem; |
|||
if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.Directory, IntPtr.Zero, ref riid, out directoryShellItem) == (uint)UnmanagedMethods.HRESULT.S_OK) |
|||
if (dialog.Directory != null) |
|||
{ |
|||
frm.SetFolder(directoryShellItem); |
|||
var riid = UnmanagedMethods.ShellIds.IShellItem; |
|||
if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.Directory, IntPtr.Zero, ref riid, out var directoryShellItem) |
|||
== (uint)UnmanagedMethods.HRESULT.S_OK) |
|||
{ |
|||
var proxy = MicroComRuntime.CreateProxyFor<IShellItem>(directoryShellItem, true); |
|||
frm.SetFolder(proxy); |
|||
frm.SetDefaultFolder(proxy); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (dialog.Directory != null) |
|||
{ |
|||
UnmanagedMethods.IShellItem directoryShellItem; |
|||
Guid riid = UnmanagedMethods.ShellIds.IShellItem; |
|||
if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.Directory, IntPtr.Zero, ref riid, out directoryShellItem) == (uint)UnmanagedMethods.HRESULT.S_OK) |
|||
frm.Show(hWnd); |
|||
if (frm.Result is not null) |
|||
{ |
|||
frm.SetDefaultFolder(directoryShellItem); |
|||
result = GetAbsoluteFilePath(frm.Result); |
|||
} |
|||
} |
|||
|
|||
if (frm.Show(hWnd) == (uint)UnmanagedMethods.HRESULT.S_OK) |
|||
return result; |
|||
} |
|||
catch (COMException ex) |
|||
{ |
|||
UnmanagedMethods.IShellItem shellItem; |
|||
if (frm.GetResult(out shellItem) == (uint)UnmanagedMethods.HRESULT.S_OK) |
|||
if ((uint)ex.HResult == (uint)UnmanagedMethods.HRESULT.E_CANCELLED) |
|||
{ |
|||
result = GetAbsoluteFilePath(shellItem); |
|||
return result; |
|||
} |
|||
throw new Win32Exception(ex.HResult); |
|||
} |
|||
|
|||
return result; |
|||
}); |
|||
} |
|||
|
|||
private string GetAbsoluteFilePath(UnmanagedMethods.IShellItem shellItem) |
|||
private unsafe string? GetAbsoluteFilePath(IShellItem shellItem) |
|||
{ |
|||
IntPtr pszString; |
|||
if (shellItem.GetDisplayName(UnmanagedMethods.SIGDN_FILESYSPATH, out pszString) == (uint)UnmanagedMethods.HRESULT.S_OK) |
|||
var pszString = new IntPtr(shellItem.GetDisplayName(SIGDN_FILESYSPATH)); |
|||
if (pszString != IntPtr.Zero) |
|||
{ |
|||
if (pszString != IntPtr.Zero) |
|||
try |
|||
{ |
|||
try |
|||
{ |
|||
return Marshal.PtrToStringAuto(pszString); |
|||
} |
|||
finally |
|||
{ |
|||
Marshal.FreeCoTaskMem(pszString); |
|||
} |
|||
return Marshal.PtrToStringUni(pszString); |
|||
} |
|||
finally |
|||
{ |
|||
Marshal.FreeCoTaskMem(pszString); |
|||
} |
|||
} |
|||
return default; |
|||
} |
|||
|
|||
private unsafe byte[] FiltersToPointer(List<FileDialogFilter>? filters, out int lenght) |
|||
{ |
|||
if (filters == null || filters.Count == 0) |
|||
{ |
|||
filters = new List<FileDialogFilter> |
|||
{ |
|||
new FileDialogFilter { Name = "All files", Extensions = new List<string> { "*" } } |
|||
}; |
|||
} |
|||
|
|||
var size = Marshal.SizeOf<UnmanagedMethods.COMDLG_FILTERSPEC>(); |
|||
var arr = new byte[size]; |
|||
var resultArr = new byte[size * filters.Count]; |
|||
|
|||
for (int i = 0; i < filters.Count; i++) |
|||
{ |
|||
var filter = filters[i]; |
|||
var filterPtr = Marshal.AllocHGlobal(size); |
|||
try |
|||
{ |
|||
var filterStr = new UnmanagedMethods.COMDLG_FILTERSPEC |
|||
{ |
|||
pszName = filter.Name ?? string.Empty, |
|||
pszSpec = string.Join(";", filter.Extensions.Select(e => "*." + e)) |
|||
}; |
|||
|
|||
Marshal.StructureToPtr(filterStr, filterPtr, false); |
|||
Marshal.Copy(filterPtr, resultArr, i * size, size); |
|||
} |
|||
finally |
|||
{ |
|||
Marshal.FreeHGlobal(filterPtr); |
|||
} |
|||
} |
|||
|
|||
lenght = filters.Count; |
|||
return resultArr; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,191 @@ |
|||
@clr-namespace Avalonia.Win32.Win32Com |
|||
@clr-access internal |
|||
@clr-map FLOAT float |
|||
@clr-map HSTRING IntPtr |
|||
@clr-map Vector2 System.Numerics.Vector2 |
|||
@clr-map Vector3 System.Numerics.Vector3 |
|||
@clr-map Quaternion System.Numerics.Quaternion |
|||
@clr-map Matrix4x4 System.Numerics.Matrix4x4 |
|||
@clr-map RECT Avalonia.Win32.Interop.UnmanagedMethods.RECT |
|||
@clr-map SIZE Avalonia.Win32.Interop.UnmanagedMethods.SIZE |
|||
@clr-map POINT Avalonia.Win32.Interop.UnmanagedMethods.POINT |
|||
@clr-map HWND IntPtr |
|||
@clr-map BOOL int |
|||
@clr-map DWORD int |
|||
@clr-map boolean int |
|||
@clr-map BYTE byte |
|||
@clr-map INT16 short |
|||
@clr-map INT32 int |
|||
@clr-map INT64 long |
|||
@clr-map UINT ushort |
|||
@clr-map UINT16 ushort |
|||
@clr-map ULONG uint |
|||
@clr-map UINT32 uint |
|||
@clr-map UINT64 ulong |
|||
@clr-map DOUBLE double |
|||
@clr-map GUID System.Guid |
|||
@clr-map REFGUID System.Guid* |
|||
@clr-map REFIID System.Guid* |
|||
@clr-map WCHAR System.Char |
|||
|
|||
[flags] |
|||
enum FILEOPENDIALOGOPTIONS |
|||
{ |
|||
FOS_OVERWRITEPROMPT = 0x00000002, |
|||
FOS_STRICTFILETYPES = 0x00000004, |
|||
FOS_NOCHANGEDIR = 0x00000008, |
|||
FOS_PICKFOLDERS = 0x00000020, |
|||
FOS_FORCEFILESYSTEM = 0x00000040, // Ensure that items returned are filesystem items. |
|||
FOS_ALLNONSTORAGEITEMS = 0x00000080, // Allow choosing items that have no storage. |
|||
FOS_NOVALIDATE = 0x00000100, |
|||
FOS_ALLOWMULTISELECT = 0x00000200, |
|||
FOS_PATHMUSTEXIST = 0x00000800, |
|||
FOS_FILEMUSTEXIST = 0x00001000, |
|||
FOS_CREATEPROMPT = 0x00002000, |
|||
FOS_SHAREAWARE = 0x00004000, |
|||
FOS_NOREADONLYRETURN = 0x00008000, |
|||
FOS_NOTESTFILECREATE = 0x00010000, |
|||
FOS_HIDEMRUPLACES = 0x00020000, |
|||
FOS_HIDEPINNEDPLACES = 0x00040000, |
|||
FOS_NODEREFERENCELINKS = 0x00100000, |
|||
FOS_DONTADDTORECENT = 0x02000000, |
|||
FOS_FORCESHOWHIDDEN = 0x10000000, |
|||
FOS_DEFAULTNOMINIMODE = 0x20000000 |
|||
} |
|||
|
|||
[ |
|||
object, |
|||
uuid(43826d1e-e718-42ee-bc55-a1e261c37bfe), |
|||
pointer_default(unique) |
|||
] |
|||
interface IShellItem : IUnknown |
|||
{ |
|||
HRESULT BindToHandler( |
|||
[in, unique] void* pbc, |
|||
[in] REFGUID bhid, |
|||
[in] REFIID riid, |
|||
[out, iid_is(riid)] void** ppv); |
|||
|
|||
HRESULT GetParent([out] IShellItem** ppsi); |
|||
|
|||
HRESULT GetDisplayName( |
|||
[in] uint sigdnName, |
|||
[out, string, annotation("_Outptr_result_nullonfailure_")] WCHAR** ppszName); |
|||
|
|||
HRESULT GetAttributes( |
|||
[in] ULONG sfgaoMask, |
|||
[out] ULONG* psfgaoAttribs); |
|||
|
|||
HRESULT Compare( |
|||
[in] IShellItem* psi, |
|||
[in] ULONG hint, |
|||
[out] int* piOrder); |
|||
} |
|||
|
|||
[ |
|||
object, |
|||
uuid(B63EA76D-1F85-456F-A19C-48159EFA858B), |
|||
pointer_default(unique) |
|||
] |
|||
interface IShellItemArray : IUnknown |
|||
{ |
|||
HRESULT BindToHandler([in, unique] void* pbc, [in] REFGUID bhid, [in] REFIID riid, [out, iid_is(riid)] void** ppvOut); |
|||
|
|||
HRESULT GetPropertyStore([in] UINT flags, [in] REFIID riid, [out, iid_is(riid)] void** ppv); |
|||
|
|||
HRESULT GetPropertyDescriptionList([in] void* keyType, [in] REFIID riid, [out, iid_is(riid)] void** ppv); |
|||
|
|||
HRESULT GetAttributes([in] int AttribFlags, [in] UINT sfgaoMask, [out] UINT* psfgaoAttribs); |
|||
|
|||
HRESULT GetCount([out] DWORD* pdwNumItems); |
|||
|
|||
HRESULT GetItemAt([in] DWORD dwIndex, [out] IShellItem** ppsi); |
|||
|
|||
HRESULT EnumItems([out] void** ppenumShellItems); |
|||
} |
|||
|
|||
[ |
|||
object, |
|||
uuid(B4DB1657-70D7-485E-8E3E-6FCB5A5C1802), |
|||
pointer_default(unique) |
|||
] |
|||
interface IModalWindow : IUnknown |
|||
{ |
|||
[local] |
|||
HRESULT Show( |
|||
[in, unique] HWND hwndOwner); |
|||
} |
|||
|
|||
[ |
|||
object, |
|||
uuid(42F85136-DB7E-439C-85F1-E4075D135FC8), |
|||
pointer_default(unique) |
|||
] |
|||
interface IFileDialog : IModalWindow |
|||
{ |
|||
HRESULT SetFileTypes( |
|||
[in] UINT cFileTypes, |
|||
[in, size_is(cFileTypes)] void* rgFilterSpec); |
|||
|
|||
HRESULT SetFileTypeIndex([in] UINT iFileType); |
|||
|
|||
HRESULT GetFileTypeIndex([out] UINT* piFileType); |
|||
|
|||
HRESULT Advise( |
|||
[in] void* pfde, |
|||
[out] DWORD* pdwCookie); |
|||
|
|||
HRESULT Unadvise([in] DWORD dwCookie); |
|||
|
|||
HRESULT SetOptions([in] FILEOPENDIALOGOPTIONS fos); |
|||
|
|||
HRESULT GetOptions([out] FILEOPENDIALOGOPTIONS* pfos); |
|||
|
|||
HRESULT SetDefaultFolder([in] IShellItem* psi); |
|||
|
|||
HRESULT SetFolder([in] IShellItem* psi); |
|||
|
|||
HRESULT GetFolder([out] IShellItem** ppsi); |
|||
|
|||
HRESULT GetCurrentSelection([out] IShellItem** ppsi); |
|||
|
|||
HRESULT SetFileName([in, string] WCHAR* pszName); |
|||
|
|||
HRESULT GetFileName([out, string] WCHAR** pszName); |
|||
|
|||
HRESULT SetTitle([in, string] WCHAR* pszTitle); |
|||
|
|||
HRESULT SetOkButtonLabel([in, string] WCHAR* pszText); |
|||
|
|||
HRESULT SetFileNameLabel([in, string] WCHAR* pszLabel); |
|||
|
|||
HRESULT GetResult([out] IShellItem** ppsi); |
|||
|
|||
HRESULT AddPlace( |
|||
[in] IShellItem* psi, |
|||
[in] INT32 fdap); |
|||
|
|||
HRESULT SetDefaultExtension([in, string] WCHAR* pszDefaultExtension); |
|||
|
|||
HRESULT Close([in] HRESULT hr); |
|||
|
|||
HRESULT SetClientGuid([in] REFGUID guid); |
|||
|
|||
HRESULT ClearClientData(); |
|||
|
|||
HRESULT SetFilter([in] void* pFilter); |
|||
} |
|||
|
|||
[ |
|||
object, |
|||
uuid(D57C7288-D4AD-4768-BE02-9D969532D960), |
|||
pointer_default(unique) |
|||
] |
|||
interface IFileOpenDialog : IFileDialog |
|||
{ |
|||
HRESULT GetResults( |
|||
[out] IShellItemArray** ppenum); |
|||
|
|||
HRESULT GetSelectedItems( |
|||
[out] IShellItemArray** ppsai); |
|||
} |
|||
Loading…
Reference in new issue