@@ -80,33 +80,35 @@ public static class OpenFolderDialog
8080
8181 // Create the FileOpenDialog object.
8282 hr = Windows . CoCreateInstance ( & CLSID_FileOpenDialog , null , ( uint ) CLSCTX . CLSCTX_INPROC_SERVER , & IID_IFileOpenDialog , ( void * * ) & pFileDialog ) ;
83- if ( Windows . SUCCEEDED ( hr ) )
83+ if ( ! Windows . SUCCEEDED ( hr ) )
8484 {
85- // Set the options on the dialog.
86- uint dwOptions ;
87- pFileDialog ->GetOptions ( & dwOptions ) ;
88- pFileDialog ->SetOptions ( dwOptions | ( uint ) FILEOPENDIALOGOPTIONS . FOS_PICKFOLDERS | ( uint ) FILEOPENDIALOGOPTIONS . FOS_FORCEFILESYSTEM ) ;
85+ return null ;
86+ }
87+
88+ // Set the options on the dialog.
89+ uint dwOptions ;
90+ pFileDialog ->GetOptions ( & dwOptions ) ;
91+ pFileDialog ->SetOptions ( dwOptions | ( uint ) FILEOPENDIALOGOPTIONS . FOS_PICKFOLDERS | ( uint ) FILEOPENDIALOGOPTIONS . FOS_FORCEFILESYSTEM ) ;
8992
90- // Show the dialog
91- hr = pFileDialog ->Show ( default ) ;
93+ // Show the dialog
94+ hr = pFileDialog ->Show ( default ) ;
95+ if ( Windows . SUCCEEDED ( hr ) )
96+ {
97+ IShellItem * pItem ;
98+ hr = pFileDialog ->GetResult ( & pItem ) ;
9299 if ( Windows . SUCCEEDED ( hr ) )
93100 {
94- IShellItem * pItem ;
95- hr = pFileDialog -> GetResult ( & pItem ) ;
101+ char * pszFilePath = null ;
102+ hr = pItem -> GetDisplayName ( SIGDN . SIGDN_FILESYSPATH , & pszFilePath ) ;
96103 if ( Windows . SUCCEEDED ( hr ) )
97104 {
98- char * pszFilePath = null ;
99- hr = pItem ->GetDisplayName ( SIGDN . SIGDN_FILESYSPATH , & pszFilePath ) ;
100- if ( Windows . SUCCEEDED ( hr ) )
101- {
102- result = new string ( pszFilePath ) ;
103- Windows . CoTaskMemFree ( pszFilePath ) ;
104- }
105- pItem ->Release ( ) ;
105+ result = new string ( pszFilePath ) ;
106+ Windows . CoTaskMemFree ( pszFilePath ) ;
106107 }
108+ pItem ->Release ( ) ;
107109 }
108- pFileDialog ->Release ( ) ;
109110 }
111+ pFileDialog ->Release ( ) ;
110112
111113 return result ;
112114 }
@@ -168,15 +170,105 @@ public static class OpenFolderDialog
168170 }
169171
170172 [ SupportedOSPlatform ( "windows" ) ]
171- private static async Task < string [ ] ? > OpenFoldersWindows ( )
173+ private static Task < string [ ] ? > OpenFoldersWindows ( )
172174 {
173- // Todo: proper Windows implementation
174- string ? path = await OpenFolder ( ) ;
175- if ( string . IsNullOrEmpty ( path ) )
175+ TaskCompletionSource < string [ ] ? > tcs = new ( ) ;
176+
177+ Thread thread = new ( ( ) =>
176178 {
177- return null ; // User canceled the dialog
179+ try
180+ {
181+ // Run the STA work
182+ Debug . Assert ( OperatingSystem . IsWindows ( ) ) ;
183+ string [ ] ? result = OpenFoldersWindowsInternal ( ) ;
184+
185+ // Mark task complete
186+ tcs . SetResult ( result ) ;
187+ }
188+ catch ( Exception ex )
189+ {
190+ tcs . SetException ( ex ) ;
191+ }
192+ } ) ;
193+ thread . SetApartmentState ( ApartmentState . STA ) ;
194+ thread . Start ( ) ;
195+
196+ return tcs . Task ;
197+ }
198+
199+ [ SupportedOSPlatform ( "windows" ) ]
200+ private unsafe static string [ ] ? OpenFoldersWindowsInternal ( )
201+ {
202+ HRESULT hr = Windows . CoInitializeEx ( null , ( uint ) COINIT . COINIT_APARTMENTTHREADED ) ;
203+ switch ( hr . Value )
204+ {
205+ case S . S_OK :
206+ Windows . CoUninitialize ( ) ;
207+ throw new InvalidOperationException ( "CoInitializeEx failed with S_OK, which should never happen because .NET is supposed to initialize the thread." ) ;
208+ case S . S_FALSE :
209+ // The thread is already initialized, which is expected.
210+ break ;
211+ case RPC . RPC_E_CHANGED_MODE :
212+ // The thread is already initialized with a different mode, which is unexpected.
213+ throw new InvalidOperationException ( "CoInitializeEx failed with RPC_E_CHANGED_MODE, which should never happen because we only call this method in STA threads." ) ;
214+ }
215+
216+ IFileOpenDialog * pFileDialog = null ;
217+
218+ // Assign the CLSID and IID for the FileOpenDialog.
219+ Guid CLSID_FileOpenDialog = new ( "DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7" ) ;
220+ Guid IID_IFileOpenDialog = new ( "D57C7288-D4AD-4768-BE02-9D969532D960" ) ;
221+
222+ // Create the FileOpenDialog object.
223+ hr = Windows . CoCreateInstance ( & CLSID_FileOpenDialog , null , ( uint ) CLSCTX . CLSCTX_INPROC_SERVER , & IID_IFileOpenDialog , ( void * * ) & pFileDialog ) ;
224+ if ( ! Windows . SUCCEEDED ( hr ) )
225+ {
226+ return null ;
178227 }
179- return [ path ] ;
228+
229+ // Set the options on the dialog.
230+ uint dwOptions = default ;
231+ pFileDialog ->GetOptions ( & dwOptions ) ;
232+ pFileDialog ->SetOptions ( dwOptions | ( uint ) FILEOPENDIALOGOPTIONS . FOS_PICKFOLDERS | ( uint ) FILEOPENDIALOGOPTIONS . FOS_ALLOWMULTISELECT | ( uint ) FILEOPENDIALOGOPTIONS . FOS_FORCEFILESYSTEM ) ;
233+
234+ string [ ] ? result = null ;
235+
236+ // Show the dialog
237+ hr = pFileDialog ->Show ( default ) ;
238+ if ( Windows . SUCCEEDED ( hr ) )
239+ {
240+ IShellItemArray * pItemArray ;
241+ hr = pFileDialog ->GetResults ( & pItemArray ) ;
242+ if ( Windows . SUCCEEDED ( hr ) )
243+ {
244+ uint itemCount = default ;
245+ pItemArray ->GetCount ( & itemCount ) ;
246+ if ( itemCount > 0 )
247+ {
248+ result = new string [ itemCount ] ;
249+ for ( uint i = 0 ; i < itemCount ; i ++ )
250+ {
251+ IShellItem * pItem ;
252+ hr = pItemArray ->GetItemAt ( i , & pItem ) ;
253+ if ( Windows . SUCCEEDED ( hr ) )
254+ {
255+ char * pszFilePath = null ;
256+ hr = pItem ->GetDisplayName ( SIGDN . SIGDN_FILESYSPATH , & pszFilePath ) ;
257+ if ( Windows . SUCCEEDED ( hr ) )
258+ {
259+ result [ i ] = new string ( pszFilePath ) ;
260+ Windows . CoTaskMemFree ( pszFilePath ) ;
261+ }
262+ pItem ->Release ( ) ;
263+ }
264+ }
265+ }
266+ pItemArray ->Release ( ) ;
267+ }
268+ }
269+ pFileDialog ->Release ( ) ;
270+
271+ return result ;
180272 }
181273
182274 [ SupportedOSPlatform ( "macos" ) ]
0 commit comments