diff --git a/AppControl Manager/Excluded Code/FileDialogHelper unmanaged version.cs b/AppControl Manager/Excluded Code/FileDialogHelper unmanaged version.cs deleted file mode 100644 index 7753f6a8c..000000000 --- a/AppControl Manager/Excluded Code/FileDialogHelper unmanaged version.cs +++ /dev/null @@ -1,532 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using Windows.Win32; -using Windows.Win32.Foundation; -using Windows.Win32.System.Com; -using Windows.Win32.UI.Shell; -using Windows.Win32.UI.Shell.Common; - -namespace WDACConfig -{ - /// - /// https://learn.microsoft.com/en-us/uwp/api/windows.storage.pickers.filesavepicker?view=winrt-26100 - /// This one currently has problems - /// - internal static class FileDialogHelper - { - /// - /// Opens a file picker dialog to select a single file. - /// - /// - /// - internal unsafe static string? ShowFilePickerDialog(string filter) - { - // Create an instance of the file open dialog - int hr = PInvoke.CoCreateInstance( - typeof(FileOpenDialog).GUID, // CLSID for FileOpenDialog - null, // No outer object for aggregation - CLSCTX.CLSCTX_INPROC_SERVER, // In-process server context - out IFileOpenDialog* fileOpenDialog // Explicitly specify the output type - ); - - // If creation fails, throw an exception with the corresponding HRESULT code. - if (hr < 0) - { - Marshal.ThrowExceptionForHR(hr); - } - - // Prepare the list of file type filters based on the input string. - List extensions = []; - - if (!string.IsNullOrEmpty(filter)) // Check if filter is provided. - { - // Split the filter into name and pattern pairs (e.g., "Text Files|*.txt"). - string[] tokens = filter.Split('|'); - - // Ensure the pairs are valid. - if (tokens.Length % 2 == 0) - { - for (int i = 0; i < tokens.Length; i += 2) - { - COMDLG_FILTERSPEC extension; - extension.pszName = (char*)Marshal.StringToHGlobalUni(tokens[i]); - extension.pszSpec = (char*)Marshal.StringToHGlobalUni(tokens[i + 1]); - extensions.Add(extension); - } - } - } - - // Apply the filters to the file open dialog. - fileOpenDialog->SetFileTypes(extensions.ToArray()); - - // Set the default folder to "My Documents". - hr = PInvoke.SHCreateItemFromParsingName( - Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), - null, - typeof(IShellItem).GUID, - out void* pDirectoryShellItem - ); - - if (hr >= 0) // Proceed only if the default folder creation succeeds. - { - IShellItem* directoryShellItem = (IShellItem*)pDirectoryShellItem; - fileOpenDialog->SetFolder(directoryShellItem); - fileOpenDialog->SetDefaultFolder(directoryShellItem); - - // Release the IShellItem after use - _ = directoryShellItem->Release(); - } - - try - { - - try - { - // Display the dialog to the user. - fileOpenDialog->Show(new HWND(GlobalVars.hWnd)); // Pass the parent window handle. - } - catch (Exception e) - { - if (e.HResult == -2147023673) // Specific HRESULT for "Operation Canceled". - { - return null; - } - - throw; // Re-throw unexpected exceptions. - } - finally - { - if (fileOpenDialog != null) - { - _ = fileOpenDialog->Release(); - } - } - - // Retrieve the result of the dialog (selected file). - IShellItem* ppsi = null; - fileOpenDialog->GetResult(&ppsi); - - // Retrieve the file path - PWSTR filename; - ppsi->GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &filename); - - // Convert to managed string - string selectedFilePath = new(filename); - - // Free the allocated memory for filename - if (filename.Value != null) - { - Marshal.FreeCoTaskMem((IntPtr)filename.Value); - } - - // Release COM objects - _ = ppsi->Release(); - - return selectedFilePath; - } - - finally - { - if (fileOpenDialog != null) - { - _ = fileOpenDialog->Release(); - } - - // Clean up extensions memory - foreach (var extension in extensions) - { - if (extension.pszName.Value != null) - { - Marshal.FreeHGlobal((IntPtr)extension.pszName.Value); - } - if (extension.pszSpec.Value != null) - { - Marshal.FreeHGlobal((IntPtr)extension.pszSpec.Value); - } - } - } - } - - - - /// - /// Opens a file picker dialog to select multiple files. - /// - /// A file filter string in the format "Description|Extension" pairs - /// (e.g., "Text Files|*.txt|All Files|*.*"). - /// A list of selected file paths or null if the operation is cancelled. - internal unsafe static List? ShowMultipleFilePickerDialog(string filter) - { - // Create the file open dialog - int hr = PInvoke.CoCreateInstance( - typeof(FileOpenDialog).GUID, // CLSID for FileOpenDialog - null, // No aggregation - CLSCTX.CLSCTX_INPROC_SERVER, // In-process COM server - out IFileOpenDialog* fileOpenDialog // Interface for the dialog - ); - - // If the HRESULT indicates failure, throw a corresponding .NET exception. - if (hr < 0) - { - Marshal.ThrowExceptionForHR(hr); - } - - // Initialize a list to store file type filters for the dialog. - List extensions = []; - - if (!string.IsNullOrEmpty(filter)) - { - // Split the filter string by '|' and process description-extension pairs. - string[] tokens = filter.Split('|'); - - // Ensure there is a valid description-extension pair for every two tokens. - if (tokens.Length % 2 == 0) - { - for (int i = 1; i < tokens.Length; i += 2) - { - COMDLG_FILTERSPEC extension; - extension.pszName = (char*)Marshal.StringToHGlobalUni(tokens[i - 1]); - extension.pszSpec = (char*)Marshal.StringToHGlobalUni(tokens[i]); - extensions.Add(extension); - } - } - } - - // Apply the file type filters to the dialog. - fileOpenDialog->SetFileTypes(extensions.ToArray()); - - // Set default folder to My Documents - hr = PInvoke.SHCreateItemFromParsingName( - Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), - null, - typeof(IShellItem).GUID, - out var directoryShellItem - ); - - // If the "My Documents" folder is successfully retrieved, set it as the default. - if (hr >= 0) - { - fileOpenDialog->SetFolder((IShellItem*)directoryShellItem); - fileOpenDialog->SetDefaultFolder((IShellItem*)directoryShellItem); - } - - // Configure dialog options for multiple selection - FILEOPENDIALOGOPTIONS options = FILEOPENDIALOGOPTIONS.FOS_ALLOWMULTISELECT; - fileOpenDialog->SetOptions(options); - - try - { - // Show the dialog - fileOpenDialog->Show(new HWND(GlobalVars.hWnd)); - } - catch (Exception e) - { - if (e.HResult == -2147023673) // Operation Canceled HRESULT - { - return null; - } - - throw; - } - finally - { - if (fileOpenDialog != null) - { - _ = fileOpenDialog->Release(); - } - } - - // Retrieve the collection of selected items - IShellItemArray* ppsiCollection = null; - - try - { - - fileOpenDialog->GetResults(&ppsiCollection); - - // Get the number of selected files - uint fileCount = 0; - ppsiCollection->GetCount(&fileCount); - - // Initialize the list to store file paths - List selectedFiles = []; - - // Iterate through selected items - for (uint i = 0; i < fileCount; i++) - { - IShellItem* ppsi = null; - ppsiCollection->GetItemAt(i, &ppsi); - - // Retrieve the file path - PWSTR filename; - ppsi->GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &filename); - - // Convert to managed string and add to the list - string filePath = new(filename.Value); - selectedFiles.Add(filePath); - - // Free the unmanaged memory allocated for the file path - if (filename.Value != null) - { - Marshal.FreeCoTaskMem((IntPtr)filename.Value); - } - - - // Release the IShellItem COM object - - _ = ppsi->Release(); - - } - - return selectedFiles; - } - finally - { - // Clean up extensions memory - foreach (var extension in extensions) - { - if (extension.pszName.Value != null) - { - Marshal.FreeHGlobal((IntPtr)extension.pszName.Value); - } - if (extension.pszSpec.Value != null) - { - Marshal.FreeHGlobal((IntPtr)extension.pszSpec.Value); - } - } - - // Release COM objects - _ = ppsiCollection->Release(); - if (fileOpenDialog != null) - { - _ = fileOpenDialog->Release(); - } - } - } - - - - - - /// - /// Opens a folder picker dialog to select a single folder. - /// - /// The selected directory path as a string, or null if the operation is cancelled. - internal unsafe static string? ShowDirectoryPickerDialog() - { - // Create the file open dialog - int hr = PInvoke.CoCreateInstance( - typeof(FileOpenDialog).GUID, // CLSID for FileOpenDialog - null, // No aggregation - CLSCTX.CLSCTX_INPROC_SERVER, // In-process COM server - out IFileOpenDialog* fileOpenDialog // Interface for the dialog - ); - - if (hr < 0) - { - Marshal.ThrowExceptionForHR(hr); - } - - // Configure dialog options to enable folder selection - FILEOPENDIALOGOPTIONS options = FILEOPENDIALOGOPTIONS.FOS_PICKFOLDERS; - fileOpenDialog->SetOptions(options); - - // Set default folder to "My Documents" - hr = PInvoke.SHCreateItemFromParsingName( - Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), - null, - typeof(IShellItem).GUID, - out var directoryShellItem - ); - - // If the "My Documents" folder is successfully retrieved, set it as the default. - if (hr >= 0) - { - fileOpenDialog->SetFolder((IShellItem*)directoryShellItem); - fileOpenDialog->SetDefaultFolder((IShellItem*)directoryShellItem); - } - - try - { - // Show the dialog - fileOpenDialog->Show(new HWND(GlobalVars.hWnd)); - } - catch (Exception e) - { - if (e.HResult == -2147023673) // Operation Canceled HRESULT - { - return null; - } - - throw; - } - finally - { - // Release the IFileOpenDialog COM object - if (fileOpenDialog != null) - { - _ = fileOpenDialog->Release(); - } - } - - // Retrieve the selected folder - IShellItem* ppsi = null; - - try - { - - fileOpenDialog->GetResult(&ppsi); - - // Get the file system path of the folder - PWSTR folderPath; - ppsi->GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &folderPath); - - // Convert to managed string - string selectedFolderPath = new(folderPath.Value); - - // Free unmanaged memory for the folder path - Marshal.FreeCoTaskMem((IntPtr)folderPath.Value); - - return selectedFolderPath; - - } - finally - { - // Clean up the IShellItem COM object - _ = ppsi->Release(); - - - // Release the IFileOpenDialog COM object - if (fileOpenDialog != null) - { - _ = fileOpenDialog->Release(); - } - } - } - - - - - - /// - /// Opens a folder picker dialog to select multiple folders. - /// - /// A list of selected directory paths or null if cancelled. - internal unsafe static List? ShowMultipleDirectoryPickerDialog() - { - // Create the file open dialog - int hr = PInvoke.CoCreateInstance( - typeof(FileOpenDialog).GUID, // CLSID for FileOpenDialog - null, // No aggregation - CLSCTX.CLSCTX_INPROC_SERVER, // In-process COM server - out IFileOpenDialog* fileOpenDialog // Interface for the dialog - ); - - if (hr < 0) - { - Marshal.ThrowExceptionForHR(hr); - } - - // Configure dialog options to enable folder picking and multiple selection - FILEOPENDIALOGOPTIONS options = FILEOPENDIALOGOPTIONS.FOS_PICKFOLDERS | FILEOPENDIALOGOPTIONS.FOS_ALLOWMULTISELECT; - fileOpenDialog->SetOptions(options); - - // Set default folder to "My Documents" - hr = PInvoke.SHCreateItemFromParsingName( - Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), - null, - typeof(IShellItem).GUID, - out var directoryShellItem - ); - - if (hr >= 0) - { - fileOpenDialog->SetFolder((IShellItem*)directoryShellItem); - fileOpenDialog->SetDefaultFolder((IShellItem*)directoryShellItem); - } - - - - try - { - // Show the dialog - fileOpenDialog->Show(new HWND(GlobalVars.hWnd)); - } - catch (Exception e) - { - if (e.HResult == -2147023673) // Operation Canceled HRESULT - { - return null; - } - - throw; - } - finally - { - // Release the IFileOpenDialog COM object - if (fileOpenDialog != null) - { - _ = fileOpenDialog->Release(); - } - } - - // Retrieve the collection of selected items - IShellItemArray* ppsiCollection = null; - - try - { - - fileOpenDialog->GetResults(&ppsiCollection); - - // Get the number of selected folders - uint folderCount; - ppsiCollection->GetCount(&folderCount); - - // Initialize the list to store selected folder paths - List selectedFolders = []; - - // Iterate through each selected folder - for (uint i = 0; i < folderCount; i++) - { - IShellItem* ppsi = null; - ppsiCollection->GetItemAt(i, &ppsi); // Retrieve IShellItem for the folder - - // Get the file system path of the folder - PWSTR folderPath; - ppsi->GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &folderPath); - - // Convert the unmanaged string (PWSTR) to a managed string and add to the list - string selectedFolderPath = new(folderPath.Value); - selectedFolders.Add(selectedFolderPath); - - // Free unmanaged memory for the folder path - Marshal.FreeCoTaskMem((IntPtr)folderPath.Value); - - // Clean up the IShellItem COM object - - _ = ppsi->Release(); - - } - - return selectedFolders; - } - finally - { - - // Clean up the IShellItemArray COM object - _ = ppsiCollection->Release(); - - // Release the IFileOpenDialog COM object - if (fileOpenDialog != null) - { - _ = fileOpenDialog->Release(); - } - - } - } - - - - } -} diff --git a/AppControl Manager/Excluded Code/FileDialogHelper.cs b/AppControl Manager/Excluded Code/FileDialogHelper.cs new file mode 100644 index 000000000..66329407a --- /dev/null +++ b/AppControl Manager/Excluded Code/FileDialogHelper.cs @@ -0,0 +1,392 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.System.Com; +using Windows.Win32.UI.Shell; +using Windows.Win32.UI.Shell.Common; + +namespace WDACConfig +{ + /// + /// https://learn.microsoft.com/en-us/uwp/api/windows.storage.pickers.filesavepicker?view=winrt-26100 + /// This class uses managed code, "allowMarshaling" should be "true" for CsWin32 JSON settings. + /// + internal static class FileDialogHelper + { + /// + /// Opens a file picker dialog to select a single file. + /// + /// + /// + internal unsafe static string? ShowFilePickerDialog(string filter) + { + // Create an instance of the file open dialog using COM interface. + int hr = PInvoke.CoCreateInstance( + typeof(FileOpenDialog).GUID, // GUID for FileOpenDialog COM object + null, // No outer object for aggregation + CLSCTX.CLSCTX_INPROC_SERVER, // Context specifies in-process server execution + out IFileOpenDialog fileOpenDialog // Output reference to the created IFileOpenDialog instance + ); + + // If creation fails, throw an exception with the corresponding HRESULT code. + if (hr < 0) + { + Marshal.ThrowExceptionForHR(hr); + } + + // Prepare the list of file type filters based on the input string. + List extensions = []; + + if (!string.IsNullOrEmpty(filter)) // Check if filter is provided. + { + // Split the filter into name and pattern pairs (e.g., "Text Files|*.txt"). + string[] tokens = filter.Split('|'); + if (tokens.Length % 2 == 0) // Ensure the pairs are valid. + { + for (int i = 1; i < tokens.Length; i += 2) + { + // Populate the filter specification structure for each pair. + COMDLG_FILTERSPEC extension; + extension.pszName = (char*)Marshal.StringToHGlobalUni(tokens[i - 1]); // Filter name. + extension.pszSpec = (char*)Marshal.StringToHGlobalUni(tokens[i]); // Filter pattern. + extensions.Add(extension); + } + } + } + + // Apply the filters to the file open dialog. + fileOpenDialog.SetFileTypes(extensions.ToArray()); + + // Set the default folder to "My Documents". + hr = PInvoke.SHCreateItemFromParsingName( + GlobalVars.UserConfigDir, // Path to the folder. + null, // No binding context needed. + typeof(IShellItem).GUID, // GUID for the IShellItem interface. + out var directoryShellItem // Output reference to the IShellItem instance. + ); + + if (hr >= 0) // Proceed only if the default folder creation succeeds. + { + // Set the initial and default folder for the dialog. + fileOpenDialog.SetFolder((IShellItem)directoryShellItem); + fileOpenDialog.SetDefaultFolder((IShellItem)directoryShellItem); + } + + try + { + // Display the dialog to the user. + fileOpenDialog.Show(new HWND(GlobalVars.hWnd)); // Pass the parent window handle. + } + catch (Exception e) + { + // Handle exceptions, such as when the user cancels the dialog. + if (e.HResult != -2147023673) // Specific HRESULT for "Operation Canceled". + { + throw; // Re-throw unexpected exceptions. + } + else + { + return null; // Return null when the dialog is canceled. + } + } + + // Retrieve the result of the dialog (selected file). + fileOpenDialog.GetResult(out IShellItem ppsi); // Get the IShellItem representing the selected file. + + // Get the file path as a PWSTR. + PWSTR filename; + ppsi.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &filename); // Retrieve the file's full path. + + // Convert the unmanaged PWSTR to a managed string and return it. + string selectedFilePath = filename.ToString(); + return selectedFilePath; + } + + + + /// + /// Opens a file picker dialog to select multiple files. + /// + /// A file filter string in the format "Description|Extension" pairs + /// (e.g., "Text Files|*.txt|All Files|*.*"). + /// A list of selected file paths or null if the operation is cancelled. + internal unsafe static List? ShowMultipleFilePickerDialog(string filter) + { + // Create the file open dialog using COM's CoCreateInstance method. + // CLSCTX.CLSCTX_INPROC_SERVER ensures the dialog runs in the same process. + int hr = PInvoke.CoCreateInstance( + typeof(FileOpenDialog).GUID, // GUID of the FileOpenDialog class. + null, // No aggregation. + CLSCTX.CLSCTX_INPROC_SERVER, // In-process COM server. + out IFileOpenDialog fileOpenDialog // Interface for the dialog. + ); + + // If the HRESULT indicates failure, throw a corresponding .NET exception. + if (hr < 0) + { + Marshal.ThrowExceptionForHR(hr); + } + + // Initialize a list to store file type filters for the dialog. + List extensions = []; + + if (!string.IsNullOrEmpty(filter)) + { + // Split the filter string by '|' and process description-extension pairs. + string[] tokens = filter.Split('|'); + + // Ensure there is a valid description-extension pair for every two tokens. + if (tokens.Length % 2 == 0) + { + for (int i = 1; i < tokens.Length; i += 2) + { + COMDLG_FILTERSPEC extension; + + // Marshal the description and extension strings to unmanaged memory. + extension.pszName = (char*)Marshal.StringToHGlobalUni(tokens[i - 1]); // Filter description. + extension.pszSpec = (char*)Marshal.StringToHGlobalUni(tokens[i]); // File extension(s). + + // Add the filter specification to the list. + extensions.Add(extension); + } + } + } + + // Apply the file type filters to the dialog. + fileOpenDialog.SetFileTypes(extensions.ToArray()); + + // Optionally set a default folder and starting directory. + // Retrieves a shell item representing the "My Documents" directory. + hr = PInvoke.SHCreateItemFromParsingName( + GlobalVars.UserConfigDir, // Path to the folder. + null, // No binding context. + typeof(IShellItem).GUID, // GUID for IShellItem interface. + out var directoryShellItem // Output shell item. + ); + + // If the "My Documents" folder is successfully retrieved, set it as the default. + if (hr >= 0) + { + fileOpenDialog.SetFolder((IShellItem)directoryShellItem); // Set starting folder. + fileOpenDialog.SetDefaultFolder((IShellItem)directoryShellItem); // Set default folder. + } + + // Configure dialog options to allow multiple file selection. + FILEOPENDIALOGOPTIONS options = FILEOPENDIALOGOPTIONS.FOS_ALLOWMULTISELECT; + fileOpenDialog.SetOptions(options); + + try + { + // Display the file picker dialog to the user. + fileOpenDialog.Show(new HWND(GlobalVars.hWnd)); // Owner window handle. + } + catch (Exception e) + { + // Handle user cancellation of the dialog (specific HRESULT -2147023673). + if (e.HResult != -2147023673) + { + throw; // Rethrow for unexpected errors. + } + else + { + return null; // User cancelled; return null. + } + } + + // Retrieve the collection of selected items from the dialog. + fileOpenDialog.GetResults(out IShellItemArray ppsiCollection); + + // Get the number of selected files in the collection. + ppsiCollection.GetCount(out uint fileCount); + + // Initialize the list to store the paths of the selected files. + List selectedFiles = []; + + // Iterate through each selected file. + for (uint i = 0; i < fileCount; i++) + { + ppsiCollection.GetItemAt(i, out IShellItem ppsi); // Get the IShellItem for the file. + + // Get the file system path of the file. + PWSTR filename; + ppsi.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &filename); // Retrieve the full filesystem path. + + // Convert the unmanaged string (PWSTR) to a managed string and add it to the list. + string selectedFilePath = filename.ToString(); + selectedFiles.Add(selectedFilePath); + } + + // Return the list of selected file paths. + return selectedFiles; + } + + + + + + /// + /// Opens a folder picker dialog to select a single folder. + /// + /// The selected directory path as a string, or null if the operation is cancelled. + internal unsafe static string? ShowDirectoryPickerDialog() + { + // Create the file open dialog using COM's CoCreateInstance method. + // CLSCTX.CLSCTX_INPROC_SERVER ensures the dialog runs in the same process. + int hr = PInvoke.CoCreateInstance( + typeof(FileOpenDialog).GUID, // GUID of the FileOpenDialog class. + null, // No aggregation. + CLSCTX.CLSCTX_INPROC_SERVER, // In-process COM server. + out IFileOpenDialog fileOpenDialog // Interface for the dialog. + ); + + // If the HRESULT indicates failure, throw a corresponding .NET exception. + if (hr < 0) + { + Marshal.ThrowExceptionForHR(hr); + } + + // Configure dialog options to enable folder selection. + FILEOPENDIALOGOPTIONS options = FILEOPENDIALOGOPTIONS.FOS_PICKFOLDERS; + fileOpenDialog.SetOptions(options); + + // Optionally set a default folder and starting directory. + // Retrieves a shell item representing the "My Documents" directory. + hr = PInvoke.SHCreateItemFromParsingName( + GlobalVars.UserConfigDir, // Path to the folder. + null, // No binding context. + typeof(IShellItem).GUID, // GUID for IShellItem interface. + out var directoryShellItem // Output shell item. + ); + + // If the "My Documents" folder is successfully retrieved, set it as the default. + if (hr >= 0) + { + fileOpenDialog.SetFolder((IShellItem)directoryShellItem); // Set starting folder. + fileOpenDialog.SetDefaultFolder((IShellItem)directoryShellItem); // Set default folder. + } + + try + { + // Display the folder picker dialog to the user. + fileOpenDialog.Show(new HWND(GlobalVars.hWnd)); // Owner window handle. + } + catch (Exception e) + { + // Handle user cancellation of the dialog (specific HRESULT -2147023673). + if (e.HResult != -2147023673) + { + throw; // Rethrow for unexpected errors. + } + else + { + return null; // User cancelled; return null. + } + } + + // Retrieve the selected folder from the dialog. + fileOpenDialog.GetResult(out IShellItem ppsi); + + // Get the file system path of the selected folder. + PWSTR folderPath; + ppsi.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &folderPath); // Retrieve the full filesystem path. + + // Convert the unmanaged string (PWSTR) to a managed string. + string selectedFolderPath = folderPath.ToString(); + + // Return the selected folder path as a managed string. + return selectedFolderPath; + } + + + + + /// + /// Opens a folder picker dialog to select multiple folders. + /// + /// A list of selected directory paths or null if cancelled. + internal unsafe static List? ShowMultipleDirectoryPickerDialog() + { + // Create the file open dialog using COM's CoCreateInstance method. + // CLSCTX.CLSCTX_INPROC_SERVER ensures the dialog runs in the same process. + int hr = PInvoke.CoCreateInstance( + typeof(FileOpenDialog).GUID, // GUID of the FileOpenDialog class. + null, // No aggregation. + CLSCTX.CLSCTX_INPROC_SERVER, // In-process COM server. + out IFileOpenDialog fileOpenDialog // Interface for the dialog. + ); + + // If the HRESULT indicates failure, throw a corresponding .NET exception. + if (hr < 0) + { + Marshal.ThrowExceptionForHR(hr); + } + + // Configure dialog options to enable folder picking and multiple selection. + FILEOPENDIALOGOPTIONS options = FILEOPENDIALOGOPTIONS.FOS_PICKFOLDERS | FILEOPENDIALOGOPTIONS.FOS_ALLOWMULTISELECT; + fileOpenDialog.SetOptions(options); + + // Optionally set a default folder and starting directory. + // Retrieves a shell item representing the "My Documents" directory. + hr = PInvoke.SHCreateItemFromParsingName( + GlobalVars.UserConfigDir, // Path to the folder. + null, // No binding context. + typeof(IShellItem).GUID, // GUID for IShellItem interface. + out var directoryShellItem // Output shell item. + ); + + // If the "My Documents" folder is successfully retrieved, set it as the default. + if (hr >= 0) + { + fileOpenDialog.SetFolder((IShellItem)directoryShellItem); // Set starting folder. + fileOpenDialog.SetDefaultFolder((IShellItem)directoryShellItem); // Set default folder. + } + + try + { + // Display the folder picker dialog to the user. + fileOpenDialog.Show(new HWND(GlobalVars.hWnd)); // Owner window handle. + } + catch (Exception e) + { + // Handle user cancellation of the dialog (specific HRESULT -2147023673). + if (e.HResult != -2147023673) + { + throw; // Rethrow for unexpected errors. + } + else + { + return null; // User cancelled; return null. + } + } + + // Retrieve the collection of selected items from the dialog. + fileOpenDialog.GetResults(out IShellItemArray ppsiCollection); + + // Get the number of selected folders in the collection. + ppsiCollection.GetCount(out uint folderCount); + + // Initialize the list to store the paths of the selected folders. + List selectedFolders = []; + + // Iterate through each selected folder. + for (uint i = 0; i < folderCount; i++) + { + ppsiCollection.GetItemAt(i, out IShellItem ppsi); // Get the IShellItem for the folder. + + // Get the file system path of the folder. + PWSTR folderPath; + ppsi.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &folderPath); // Retrieve the full filesystem path. + + // Convert the unmanaged string (PWSTR) to a managed string and add it to the list. + string selectedFolderPath = folderPath.ToString(); + selectedFolders.Add(selectedFolderPath); + } + + // Return the list of selected folder paths. + return selectedFolders; + } + + } +} diff --git a/AppControl Manager/Logic/FileDialogHelper.cs b/AppControl Manager/Logic/FileDialogHelper.cs index 6f924f7fb..8022ee20b 100644 --- a/AppControl Manager/Logic/FileDialogHelper.cs +++ b/AppControl Manager/Logic/FileDialogHelper.cs @@ -11,7 +11,7 @@ namespace WDACConfig { /// /// https://learn.microsoft.com/en-us/uwp/api/windows.storage.pickers.filesavepicker?view=winrt-26100 - /// This class uses managed code, "allowMarshaling" is "true" for CsWin32 JSON settings. + /// This class uses unmanaged code, "allowMarshaling" should be "false" for CsWin32 JSON settings. /// internal static class FileDialogHelper { @@ -22,12 +22,12 @@ internal static class FileDialogHelper /// internal unsafe static string? ShowFilePickerDialog(string filter) { - // Create an instance of the file open dialog using COM interface. + // Create an instance of the file open dialog int hr = PInvoke.CoCreateInstance( - typeof(FileOpenDialog).GUID, // GUID for FileOpenDialog COM object + typeof(FileOpenDialog).GUID, // CLSID for FileOpenDialog null, // No outer object for aggregation - CLSCTX.CLSCTX_INPROC_SERVER, // Context specifies in-process server execution - out IFileOpenDialog fileOpenDialog // Output reference to the created IFileOpenDialog instance + CLSCTX.CLSCTX_INPROC_SERVER, // In-process server context + out IFileOpenDialog* fileOpenDialog // Explicitly specify the output type ); // If creation fails, throw an exception with the corresponding HRESULT code. @@ -43,65 +43,103 @@ out IFileOpenDialog fileOpenDialog // Output reference to the created IFileOpenD { // Split the filter into name and pattern pairs (e.g., "Text Files|*.txt"). string[] tokens = filter.Split('|'); - if (tokens.Length % 2 == 0) // Ensure the pairs are valid. + + // Ensure the pairs are valid. + if (tokens.Length % 2 == 0) { - for (int i = 1; i < tokens.Length; i += 2) + for (int i = 0; i < tokens.Length; i += 2) { - // Populate the filter specification structure for each pair. COMDLG_FILTERSPEC extension; - extension.pszName = (char*)Marshal.StringToHGlobalUni(tokens[i - 1]); // Filter name. - extension.pszSpec = (char*)Marshal.StringToHGlobalUni(tokens[i]); // Filter pattern. + extension.pszName = (char*)Marshal.StringToHGlobalUni(tokens[i]); + extension.pszSpec = (char*)Marshal.StringToHGlobalUni(tokens[i + 1]); extensions.Add(extension); } } } // Apply the filters to the file open dialog. - fileOpenDialog.SetFileTypes(extensions.ToArray()); + fileOpenDialog->SetFileTypes(extensions.ToArray()); // Set the default folder to "My Documents". hr = PInvoke.SHCreateItemFromParsingName( - GlobalVars.UserConfigDir, // Path to the folder. - null, // No binding context needed. - typeof(IShellItem).GUID, // GUID for the IShellItem interface. - out var directoryShellItem // Output reference to the IShellItem instance. + Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), + null, + typeof(IShellItem).GUID, + out void* pDirectoryShellItem ); if (hr >= 0) // Proceed only if the default folder creation succeeds. { - // Set the initial and default folder for the dialog. - fileOpenDialog.SetFolder((IShellItem)directoryShellItem); - fileOpenDialog.SetDefaultFolder((IShellItem)directoryShellItem); + IShellItem* directoryShellItem = (IShellItem*)pDirectoryShellItem; + fileOpenDialog->SetFolder(directoryShellItem); + fileOpenDialog->SetDefaultFolder(directoryShellItem); + + // Release the IShellItem after use + _ = directoryShellItem->Release(); } try { - // Display the dialog to the user. - fileOpenDialog.Show(new HWND(GlobalVars.hWnd)); // Pass the parent window handle. - } - catch (Exception e) - { - // Handle exceptions, such as when the user cancels the dialog. - if (e.HResult != -2147023673) // Specific HRESULT for "Operation Canceled". + + try + { + // Display the dialog to the user. + fileOpenDialog->Show(new HWND(GlobalVars.hWnd)); // Pass the parent window handle. + } + catch (Exception e) { + if (e.HResult == -2147023673) // Specific HRESULT for "Operation Canceled". + { + return null; + } + throw; // Re-throw unexpected exceptions. } - else + + + // Retrieve the result of the dialog (selected file). + IShellItem* ppsi = null; + fileOpenDialog->GetResult(&ppsi); + + // Retrieve the file path + PWSTR filename; + ppsi->GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &filename); + + // Convert to managed string + string selectedFilePath = new(filename); + + // Free the allocated memory for filename + if (filename.Value != null) { - return null; // Return null when the dialog is canceled. + Marshal.FreeCoTaskMem((IntPtr)filename.Value); } - } - // Retrieve the result of the dialog (selected file). - fileOpenDialog.GetResult(out IShellItem ppsi); // Get the IShellItem representing the selected file. + // Release COM objects + _ = ppsi->Release(); - // Get the file path as a PWSTR. - PWSTR filename; - ppsi.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &filename); // Retrieve the file's full path. + return selectedFilePath; + } - // Convert the unmanaged PWSTR to a managed string and return it. - string selectedFilePath = filename.ToString(); - return selectedFilePath; + finally + { + if (fileOpenDialog != null) + { + _ = fileOpenDialog->Release(); + } + + // Clean up extensions memory + foreach (COMDLG_FILTERSPEC extension in extensions) + { + if (extension.pszName.Value != null) + { + Marshal.FreeHGlobal((IntPtr)extension.pszName.Value); + } + if (extension.pszSpec.Value != null) + { + Marshal.FreeHGlobal((IntPtr)extension.pszSpec.Value); + } + } + } } @@ -114,13 +152,12 @@ out var directoryShellItem // Output reference to the IShellItem instance. /// A list of selected file paths or null if the operation is cancelled. internal unsafe static List? ShowMultipleFilePickerDialog(string filter) { - // Create the file open dialog using COM's CoCreateInstance method. - // CLSCTX.CLSCTX_INPROC_SERVER ensures the dialog runs in the same process. + // Create the file open dialog int hr = PInvoke.CoCreateInstance( - typeof(FileOpenDialog).GUID, // GUID of the FileOpenDialog class. - null, // No aggregation. - CLSCTX.CLSCTX_INPROC_SERVER, // In-process COM server. - out IFileOpenDialog fileOpenDialog // Interface for the dialog. + typeof(FileOpenDialog).GUID, // CLSID for FileOpenDialog + null, // No aggregation + CLSCTX.CLSCTX_INPROC_SERVER, // In-process COM server + out IFileOpenDialog* fileOpenDialog // Interface for the dialog ); // If the HRESULT indicates failure, throw a corresponding .NET exception. @@ -143,83 +180,115 @@ out var directoryShellItem // Output reference to the IShellItem instance. for (int i = 1; i < tokens.Length; i += 2) { COMDLG_FILTERSPEC extension; - - // Marshal the description and extension strings to unmanaged memory. - extension.pszName = (char*)Marshal.StringToHGlobalUni(tokens[i - 1]); // Filter description. - extension.pszSpec = (char*)Marshal.StringToHGlobalUni(tokens[i]); // File extension(s). - - // Add the filter specification to the list. + extension.pszName = (char*)Marshal.StringToHGlobalUni(tokens[i - 1]); + extension.pszSpec = (char*)Marshal.StringToHGlobalUni(tokens[i]); extensions.Add(extension); } } } // Apply the file type filters to the dialog. - fileOpenDialog.SetFileTypes(extensions.ToArray()); + fileOpenDialog->SetFileTypes(extensions.ToArray()); - // Optionally set a default folder and starting directory. - // Retrieves a shell item representing the "My Documents" directory. + // Set default folder to My Documents hr = PInvoke.SHCreateItemFromParsingName( - GlobalVars.UserConfigDir, // Path to the folder. - null, // No binding context. - typeof(IShellItem).GUID, // GUID for IShellItem interface. - out var directoryShellItem // Output shell item. + Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), + null, + typeof(IShellItem).GUID, + out void* directoryShellItem ); // If the "My Documents" folder is successfully retrieved, set it as the default. if (hr >= 0) { - fileOpenDialog.SetFolder((IShellItem)directoryShellItem); // Set starting folder. - fileOpenDialog.SetDefaultFolder((IShellItem)directoryShellItem); // Set default folder. + fileOpenDialog->SetFolder((IShellItem*)directoryShellItem); + fileOpenDialog->SetDefaultFolder((IShellItem*)directoryShellItem); } - // Configure dialog options to allow multiple file selection. + // Configure dialog options for multiple selection FILEOPENDIALOGOPTIONS options = FILEOPENDIALOGOPTIONS.FOS_ALLOWMULTISELECT; - fileOpenDialog.SetOptions(options); + fileOpenDialog->SetOptions(options); try { - // Display the file picker dialog to the user. - fileOpenDialog.Show(new HWND(GlobalVars.hWnd)); // Owner window handle. + // Show the dialog + fileOpenDialog->Show(new HWND(GlobalVars.hWnd)); } catch (Exception e) { - // Handle user cancellation of the dialog (specific HRESULT -2147023673). - if (e.HResult != -2147023673) + if (e.HResult == -2147023673) // Operation Canceled HRESULT { - throw; // Rethrow for unexpected errors. - } - else - { - return null; // User cancelled; return null. + return null; } + + throw; } - // Retrieve the collection of selected items from the dialog. - fileOpenDialog.GetResults(out IShellItemArray ppsiCollection); + // Retrieve the collection of selected items + IShellItemArray* ppsiCollection = null; - // Get the number of selected files in the collection. - ppsiCollection.GetCount(out uint fileCount); + try + { - // Initialize the list to store the paths of the selected files. - List selectedFiles = []; + fileOpenDialog->GetResults(&ppsiCollection); - // Iterate through each selected file. - for (uint i = 0; i < fileCount; i++) - { - ppsiCollection.GetItemAt(i, out IShellItem ppsi); // Get the IShellItem for the file. + // Get the number of selected files + uint fileCount = 0; + ppsiCollection->GetCount(&fileCount); - // Get the file system path of the file. - PWSTR filename; - ppsi.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &filename); // Retrieve the full filesystem path. + // Initialize the list to store file paths + List selectedFiles = []; + + // Iterate through selected items + for (uint i = 0; i < fileCount; i++) + { + IShellItem* ppsi = null; + ppsiCollection->GetItemAt(i, &ppsi); + + // Retrieve the file path + PWSTR filename; + ppsi->GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &filename); - // Convert the unmanaged string (PWSTR) to a managed string and add it to the list. - string selectedFilePath = filename.ToString(); - selectedFiles.Add(selectedFilePath); + // Convert to managed string and add to the list + string filePath = new(filename.Value); + selectedFiles.Add(filePath); + + // Free the unmanaged memory allocated for the file path + if (filename.Value != null) + { + Marshal.FreeCoTaskMem((IntPtr)filename.Value); + } + + + // Release the IShellItem COM object + _ = ppsi->Release(); + + } + + return selectedFiles; } + finally + { + // Clean up extensions memory + foreach (COMDLG_FILTERSPEC extension in extensions) + { + if (extension.pszName.Value != null) + { + Marshal.FreeHGlobal((IntPtr)extension.pszName.Value); + } + if (extension.pszSpec.Value != null) + { + Marshal.FreeHGlobal((IntPtr)extension.pszSpec.Value); + } + } - // Return the list of selected file paths. - return selectedFiles; + // Release COM objects + _ = ppsiCollection->Release(); + if (fileOpenDialog != null) + { + _ = fileOpenDialog->Release(); + } + } } @@ -232,160 +301,198 @@ out var directoryShellItem // Output shell item. /// The selected directory path as a string, or null if the operation is cancelled. internal unsafe static string? ShowDirectoryPickerDialog() { - // Create the file open dialog using COM's CoCreateInstance method. - // CLSCTX.CLSCTX_INPROC_SERVER ensures the dialog runs in the same process. + // Create the file open dialog int hr = PInvoke.CoCreateInstance( - typeof(FileOpenDialog).GUID, // GUID of the FileOpenDialog class. - null, // No aggregation. - CLSCTX.CLSCTX_INPROC_SERVER, // In-process COM server. - out IFileOpenDialog fileOpenDialog // Interface for the dialog. + typeof(FileOpenDialog).GUID, // CLSID for FileOpenDialog + null, // No aggregation + CLSCTX.CLSCTX_INPROC_SERVER, // In-process COM server + out IFileOpenDialog* fileOpenDialog // Interface for the dialog ); - // If the HRESULT indicates failure, throw a corresponding .NET exception. if (hr < 0) { Marshal.ThrowExceptionForHR(hr); } - // Configure dialog options to enable folder selection. + // Configure dialog options to enable folder selection FILEOPENDIALOGOPTIONS options = FILEOPENDIALOGOPTIONS.FOS_PICKFOLDERS; - fileOpenDialog.SetOptions(options); + fileOpenDialog->SetOptions(options); - // Optionally set a default folder and starting directory. - // Retrieves a shell item representing the "My Documents" directory. + // Set default folder to "My Documents" hr = PInvoke.SHCreateItemFromParsingName( - GlobalVars.UserConfigDir, // Path to the folder. - null, // No binding context. - typeof(IShellItem).GUID, // GUID for IShellItem interface. - out var directoryShellItem // Output shell item. + Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), + null, + typeof(IShellItem).GUID, + out void* directoryShellItem ); // If the "My Documents" folder is successfully retrieved, set it as the default. if (hr >= 0) { - fileOpenDialog.SetFolder((IShellItem)directoryShellItem); // Set starting folder. - fileOpenDialog.SetDefaultFolder((IShellItem)directoryShellItem); // Set default folder. + fileOpenDialog->SetFolder((IShellItem*)directoryShellItem); + fileOpenDialog->SetDefaultFolder((IShellItem*)directoryShellItem); } try { - // Display the folder picker dialog to the user. - fileOpenDialog.Show(new HWND(GlobalVars.hWnd)); // Owner window handle. + // Show the dialog + fileOpenDialog->Show(new HWND(GlobalVars.hWnd)); } catch (Exception e) { - // Handle user cancellation of the dialog (specific HRESULT -2147023673). - if (e.HResult != -2147023673) + if (e.HResult == -2147023673) // Operation Canceled HRESULT { - throw; // Rethrow for unexpected errors. - } - else - { - return null; // User cancelled; return null. + return null; } + + throw; } - // Retrieve the selected folder from the dialog. - fileOpenDialog.GetResult(out IShellItem ppsi); - // Get the file system path of the selected folder. - PWSTR folderPath; - ppsi.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &folderPath); // Retrieve the full filesystem path. + // Retrieve the selected folder + IShellItem* ppsi = null; - // Convert the unmanaged string (PWSTR) to a managed string. - string selectedFolderPath = folderPath.ToString(); + try + { + + fileOpenDialog->GetResult(&ppsi); + + // Get the file system path of the folder + PWSTR folderPath; + ppsi->GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &folderPath); + + // Convert to managed string + string selectedFolderPath = new(folderPath.Value); + + // Free unmanaged memory for the folder path + Marshal.FreeCoTaskMem((IntPtr)folderPath.Value); + + return selectedFolderPath; - // Return the selected folder path as a managed string. - return selectedFolderPath; + } + finally + { + // Clean up the IShellItem COM object + _ = ppsi->Release(); + + + // Release the IFileOpenDialog COM object + if (fileOpenDialog != null) + { + _ = fileOpenDialog->Release(); + } + } } + /// /// Opens a folder picker dialog to select multiple folders. /// /// A list of selected directory paths or null if cancelled. internal unsafe static List? ShowMultipleDirectoryPickerDialog() { - // Create the file open dialog using COM's CoCreateInstance method. - // CLSCTX.CLSCTX_INPROC_SERVER ensures the dialog runs in the same process. + // Create the file open dialog int hr = PInvoke.CoCreateInstance( - typeof(FileOpenDialog).GUID, // GUID of the FileOpenDialog class. - null, // No aggregation. - CLSCTX.CLSCTX_INPROC_SERVER, // In-process COM server. - out IFileOpenDialog fileOpenDialog // Interface for the dialog. + typeof(FileOpenDialog).GUID, // CLSID for FileOpenDialog + null, // No aggregation + CLSCTX.CLSCTX_INPROC_SERVER, // In-process COM server + out IFileOpenDialog* fileOpenDialog // Interface for the dialog ); - // If the HRESULT indicates failure, throw a corresponding .NET exception. if (hr < 0) { Marshal.ThrowExceptionForHR(hr); } - // Configure dialog options to enable folder picking and multiple selection. + // Configure dialog options to enable folder picking and multiple selection FILEOPENDIALOGOPTIONS options = FILEOPENDIALOGOPTIONS.FOS_PICKFOLDERS | FILEOPENDIALOGOPTIONS.FOS_ALLOWMULTISELECT; - fileOpenDialog.SetOptions(options); + fileOpenDialog->SetOptions(options); - // Optionally set a default folder and starting directory. - // Retrieves a shell item representing the "My Documents" directory. + // Set default folder to "My Documents" hr = PInvoke.SHCreateItemFromParsingName( - GlobalVars.UserConfigDir, // Path to the folder. - null, // No binding context. - typeof(IShellItem).GUID, // GUID for IShellItem interface. - out var directoryShellItem // Output shell item. + Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), + null, + typeof(IShellItem).GUID, + out void* directoryShellItem ); - // If the "My Documents" folder is successfully retrieved, set it as the default. if (hr >= 0) { - fileOpenDialog.SetFolder((IShellItem)directoryShellItem); // Set starting folder. - fileOpenDialog.SetDefaultFolder((IShellItem)directoryShellItem); // Set default folder. + fileOpenDialog->SetFolder((IShellItem*)directoryShellItem); + fileOpenDialog->SetDefaultFolder((IShellItem*)directoryShellItem); } + try { - // Display the folder picker dialog to the user. - fileOpenDialog.Show(new HWND(GlobalVars.hWnd)); // Owner window handle. + // Show the dialog + fileOpenDialog->Show(new HWND(GlobalVars.hWnd)); } catch (Exception e) { - // Handle user cancellation of the dialog (specific HRESULT -2147023673). - if (e.HResult != -2147023673) - { - throw; // Rethrow for unexpected errors. - } - else + if (e.HResult == -2147023673) // Operation Canceled HRESULT { - return null; // User cancelled; return null. + return null; } + + throw; } - // Retrieve the collection of selected items from the dialog. - fileOpenDialog.GetResults(out IShellItemArray ppsiCollection); + // Retrieve the collection of selected items + IShellItemArray* ppsiCollection = null; + + try + { - // Get the number of selected folders in the collection. - ppsiCollection.GetCount(out uint folderCount); + fileOpenDialog->GetResults(&ppsiCollection); - // Initialize the list to store the paths of the selected folders. - List selectedFolders = []; + // Get the number of selected folders + uint folderCount; + ppsiCollection->GetCount(&folderCount); - // Iterate through each selected folder. - for (uint i = 0; i < folderCount; i++) - { - ppsiCollection.GetItemAt(i, out IShellItem ppsi); // Get the IShellItem for the folder. + // Initialize the list to store selected folder paths + List selectedFolders = []; - // Get the file system path of the folder. - PWSTR folderPath; - ppsi.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &folderPath); // Retrieve the full filesystem path. + // Iterate through each selected folder + for (uint i = 0; i < folderCount; i++) + { + IShellItem* ppsi = null; + ppsiCollection->GetItemAt(i, &ppsi); // Retrieve IShellItem for the folder + + // Get the file system path of the folder + PWSTR folderPath; + ppsi->GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &folderPath); + + // Convert the unmanaged string (PWSTR) to a managed string and add to the list + string selectedFolderPath = new(folderPath.Value); + selectedFolders.Add(selectedFolderPath); + + // Free unmanaged memory for the folder path + Marshal.FreeCoTaskMem((IntPtr)folderPath.Value); + + // Clean up the IShellItem COM object + _ = ppsi->Release(); + + } - // Convert the unmanaged string (PWSTR) to a managed string and add it to the list. - string selectedFolderPath = folderPath.ToString(); - selectedFolders.Add(selectedFolderPath); + return selectedFolders; } + finally + { + + // Clean up the IShellItemArray COM object + _ = ppsiCollection->Release(); + + // Release the IFileOpenDialog COM object + if (fileOpenDialog != null) + { + _ = fileOpenDialog->Release(); + } - // Return the list of selected folder paths. - return selectedFolders; + } } } diff --git a/AppControl Manager/NativeMethods.json b/AppControl Manager/NativeMethods.json index ad006028f..dacdfc705 100644 --- a/AppControl Manager/NativeMethods.json +++ b/AppControl Manager/NativeMethods.json @@ -1,4 +1,4 @@ { "$schema": "https://aka.ms/CsWin32.schema.json", - "allowMarshaling": true + "allowMarshaling": false } \ No newline at end of file