WindowsAppSDK: FileOpenPicker PickMultipleFilesAsync Break in WinUI3 Desktop

Describe the bug

The FileOpenPicker in WinUI3 Preview4 Desktop sample solution breaks and closes the application when trying to open multiple items. Exception details below in the last section.

Steps to reproduce the bug

  1. Clone the WinUI3 Preview4 Problems FilePicker repository.
  2. Go to the FilePickerWinUIPreview4 folder.
  3. Open the FilePickerWinUIPreview4 solution in Visual Studio 2019 Preview.
  4. Build and run with Debug x64.
  5. Click the second / bottom button “FileOpenPicker: Multiple Files”. (The first button should work as expected.)
  6. Select one or more files.
  7. Click “Open”. Crash should occur.

Expected behavior

We expect the application to display the path of the selected file(s) or folder.

We expect the FileOpenPicker to allow the user to select multiple files as it does in UWP.

Screenshots

ExpectedBehavior

CurrentBehavior2

Screenshot#1a and 1b - Current Behavior (Single File working and Crash Error Message)

CurrentBehavior1

Screenshot#2 - Expected Behavior (File paths of selected files)

Version Info

NuGet package version:

[Microsoft.WinUI 3.0.0-preview4.210210.4]

Targeting: Target: Universal Windows Target version: Windows 10, version 1809 (10.0; Build 17763) Min version: Windows 10, version 1803 (10.0; Build 17134)

Windows app type:

UWP Win32
Yes
Windows 10 version Saw the problem?
Insider Build (xxxxx)
May 2020 Update (19041)
November 2019 Update (18363)
May 2019 Update (18362)
October 2018 Update (17763) Yes
April 2018 Update (17134)
Fall Creators Update (16299)
Creators Update (15063)
Device form factor Saw the problem?
Desktop Yes
Xbox
Surface Hub
IoT

Additional context

Exception Details

System.Reflection.TargetInvocationException HResult=0x80131604 Message=Exception has been thrown by the target of an invocation. Source=System.Private.CoreLib StackTrace: at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters) at WinRT.SingleInterfaceOptimizedObject…ctor(Type type, IObjectReference objRef) at Windows.Storage.Pickers.FilePickerSelectedFilesArray.<.ctor>b__8_1() at System.Lazy1.ViaFactory(LazyThreadSafetyMode mode) at System.Lazy1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor) at System.Lazy1.CreateValue() at System.Lazy1.get_Value() at Windows.Storage.Pickers.FilePickerSelectedFilesArray.AsInternal(InterfaceTag`1 _) at Windows.Storage.Pickers.FilePickerSelectedFilesArray.GetEnumerator() at FileOpenPickerWinUIPreview4.MainWindow.<fileOpenPickerMultipleButton_Click>d__2.MoveNext() in \FileOpenPickerWinUIPreview4\FileOpenPickerWinUIPreview4\MainWindow.xaml.cs:line 60

This exception was originally thrown at this call stack: [External Code]

Inner Exception 1: COMException: The application called an interface that was marshalled for a different thread. (0x8001010E (RPC_E_WRONG_THREAD))

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 52 (8 by maintainers)

Most upvoted comments

I’d like to share the workaround of using Interop GetOpenFileName API call.

By researching several links and verifying some experiments:

https://docs.microsoft.com/en-us/windows/win32/dlgbox/open-and-save-as-dialog-boxes https://stackoverflow.com/questions/9088227/using-getopenfilename-instead-of-openfiledialog https://social.msdn.microsoft.com/forums/vstudio/en-US/2f4dd95e-5c7b-4f48-adfc-44956b350f38/getopenfilename-for-multiple-files

I can workaround this issue as below:

Notes: This works in Project Reunion 0.8 Preview. If you are using WinUI3 Preview or Project Reunion 0.5, may hit this bug:

https://github.com/microsoft/microsoft-ui-xaml/issues/4952

And need to upgrade the project to 0.8 by going through: https://docs.microsoft.com/en-us/windows/apps/project-reunion/update-existing-projects-to-the-latest-release

The helper class is as attached: Libwrap.txt

The calling sample code snippet: ` string[] files = LibWrap.ShowOpenFileDialog(“Select Files”,“c:\users\freistli\documents”, “All files\0 .\0”, true, true);

        if (files != null)
        {
            FPS.Text = "";
            foreach (var file in files)
            {
                Debug.WriteLine("Selected file with full path: {0}", file);
                FPS.Text += file + "\r\n";
            }
        }

`

Hope this helps.

@harvinders you’ll have to statically store the current Window somewhere you can access it globally. If your app only has one single window, the easiest thing to do is, in your App.xaml.cs, refactor…

private Window m_window;

to…

public static readonly Window MainWindow;

And then when you need to reference it, you can do…

var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow);

Yeah, I used the IInitializeWithWindow interface. The issue was that I didn’t specify a file type filter. Looking through the docs, I discovered that not specifying it generates an exception. It’s all resolved now. Thanks for the swift response!

@eleanorleffler thanks opening the bug and share the code. I could repro this.

I noticed that the doc of this API has the UWP label. I don’t know if this means that this API won’t work in Win32 apps.

Shows the file picker so that the user can pick multiple files. (UWP app)

I need help from the Project Reunion folks, this API is out of the WinUI domain. @jonwis, can you help us to know what is happening here?

We get:

COMException: The application called an interface that was marshalled for a different thread. (0x8001010E (RPC_E_WRONG_THREAD))

Below you can find the code snipped:

 public sealed partial class MainWindow : Window
    {
        public MainWindow()
        {
            this.InitializeComponent();
        }

        private async void fileOpenPickerMultipleButton_Click(object sender, RoutedEventArgs e)
        {
            FileOpenPicker picker = new FileOpenPicker();

            IInitializeWithWindow initializeWithWindowWrapper = picker.As<IInitializeWithWindow>();
            var hwnd = this.As<IWindowNative>().WindowHandle;
            initializeWithWindowWrapper.Initialize(hwnd);

            picker.ViewMode = PickerViewMode.List;
            picker.SuggestedStartLocation = PickerLocationId.Desktop;
            picker.FileTypeFilter.Add(".txt");

            var filesToOpen = await picker.PickMultipleFilesAsync();
            string text = string.Empty;
            foreach (StorageFile storageFile in filesToOpen)
            {
                text += storageFile.Path + Environment.NewLine;
            }
            fileOpenPickerMultipleTextBlock.Text = text;
        }
        public static void InitializeWithWindow(Object obj, Window window)
        {
            // When running on win32, FileOpenPicker needs to know the top-level hwnd via IInitializeWithWindow::Initialize.
            if (Window.Current == null)
            {
                IInitializeWithWindow initializeWithWindowWrapper = obj.As<IInitializeWithWindow>();
                var hwnd = window.As<IWindowNative>().WindowHandle;
                initializeWithWindowWrapper.Initialize(hwnd);
            }
        }

    }

    [ComImport]
    [Guid("3E68D4BD-7135-4D10-8018-9FB6D9F33FA1")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IInitializeWithWindow
    {
        void Initialize([In] IntPtr hwnd);
    }
    [ComImport]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("EECDBF0E-BAE9-4CB6-A68E-9598E1CB57BB")]
    internal interface IWindowNative
    {
        IntPtr WindowHandle { get; }
    }

Is m_hWndOwner correct at : (the window handle passed as 1st parameter of ShowDialog)

        public void OnButtonClicked([In][MarshalAs(UnmanagedType.Interface)] IFileDialogCustomize pfdc, [In] int dwIDCtl)
        {
            IntPtr pClassPtr = Marshal.GetIUnknownForObject(this);
            SendMessage(parentCFD.m_hWndOwner, WM_APP_FILEDIALOG, (IntPtr)dwIDCtl, (IntPtr)pClassPtr);
            return;
        }

PickMultipleFilesAsync is unfortunately not supported right now. You can instead use the Win32 API GetOpenFileName, we’re working on posting a workaround sample for that.

@Aloento make sure when you’re calling this.As<IWindowNative>(), the this object is a Window object. If you’re still seeing problems, you can open a new issue, and please provide a full example of the code you’re calling, what specifically is failing, etc. Thanks!

I got the same issue on PickMultipleFilesAsync, which crashes WinUI/Reunion WPF desktop app. But PickSingleFolderAsync works well. Do we have workaround or fix now? Thanks in advance.

See microsoft/ProjectReunion#466 for more information on why this doesn’t work and the workaround.