diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/DataFormats.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/DataFormats.cs index 5ffca011d3c..fc58b3d5e11 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/DataFormats.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/DataFormats.cs @@ -2,124 +2,37 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -// -// -// Description: Manage the data formats. // // See spec at http://avalon/uis/Data%20Transfer%20clipboard%20dragdrop/Avalon%20Data%20Transfer%20Object.htm // -// -using MS.Win32; -using System.Collections; -using System.Globalization; -using System.Runtime.InteropServices; -using System.Security; -using System.Text; using MS.Internal.PresentationCore; -using SecurityHelper=MS.Internal.SecurityHelper; +using System.Collections.Generic; +using System.ComponentModel; +using System.Windows.Ink; +using System.Threading; +using System.Text; +using MS.Win32; namespace System.Windows { - #region DataFormats class - /// /// Translates between Windows Design text-based formats and /// 32-bit signed integer-based clipboard formats. /// public static class DataFormats { - //------------------------------------------------------ - // - // Public Methods - // - //------------------------------------------------------ - - #region Public Methods - /// - /// Gets an object with the Windows Clipboard numeric - /// ID and name for the specified ID. + /// Gets the data format with the Windows Clipboard numeric ID and name for the specified ID. /// - public static DataFormat GetDataFormat(int id) - { - return InternalGetDataFormat(id); - } + public static DataFormat GetDataFormat(int id) => DataFormatsImpl.GetDataFormat(id); /// /// Gets the data format with the Windows Clipboard numeric ID and name for the specified data format. /// - /// - /// Callers must have UnmanagedCode permission to call this API. - /// - public static DataFormat GetDataFormat(string format) - { - ArgumentNullException.ThrowIfNull(format); + public static DataFormat GetDataFormat(string format) => DataFormatsImpl.GetDataFormat(format); - if (format == string.Empty) - { - throw new ArgumentException(SR.DataObject_EmptyFormatNotAllowed); - } - - // Ensures the predefined Win32 data formats into our format list. - EnsurePredefined(); - - // Lock the data format list to obtains the mutual-exclusion. - lock (_formatListlock) - { - int formatId; - int index; - - // It is much faster to do a case sensitive search here. So do - // the case sensitive compare first, then the expensive one. - // - for (int n = 0; n < _formatList.Count; n++) - { - DataFormat formatItem; - - formatItem = (DataFormat)_formatList[n]; - - if (formatItem.Name.Equals(format)) - { - return formatItem; - } - } - - for (int n = 0; n < _formatList.Count; n++) - { - DataFormat formatItem; - - formatItem = (DataFormat)_formatList[n]; - - if (string.Equals(formatItem.Name, format, StringComparison.OrdinalIgnoreCase)) - { - return formatItem; - } - } - - // Reigster the this format string. - formatId = UnsafeNativeMethods.RegisterClipboardFormat(format); - - if (formatId == 0) - { - throw new System.ComponentModel.Win32Exception(); - } - - index = _formatList.Add(new DataFormat(format, formatId)); - - return (DataFormat)_formatList[index]; - } - } - - #endregion Public Methods - - //------------------------------------------------------ - // - // Public Fields - // - //------------------------------------------------------ - - #region Public Fields +#pragma warning disable IDE1006 // Naming rule violation (Static fields without s_* prefix) /// /// Specifies the standard ANSI text format. This field is read-only. @@ -246,7 +159,6 @@ public static DataFormat GetDataFormat(string format) /// public static readonly string Serializable = "PersistentObject"; - /// /// Specifies a data format as Xaml. This field is read-only. /// @@ -256,15 +168,9 @@ public static DataFormat GetDataFormat(string format) /// Specifies a data format as Xaml Package. This field is read-only. /// public static readonly string XamlPackage = "XamlPackage"; - #endregion Public Fields - //------------------------------------------------------ - // - // Internal Fields - // - //------------------------------------------------------ +#pragma warning restore IDE1006 // Naming rule violation (Static fields without s_* prefix) - #region Internal Fields /// /// Specifies a data format as ApplicationTrust which is used to block /// paste from partial trust to full trust applications. The intent of this @@ -276,201 +182,174 @@ public static DataFormat GetDataFormat(string format) internal const string FileName = "FileName"; internal const string FileNameW = "FileNameW"; - #endregion Internal Fields - - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods - /// /// Convert TextDataFormat to Dataformats. /// - internal static string ConvertToDataFormats(TextDataFormat textDataformat) + internal static string ConvertToDataFormats(TextDataFormat textDataformat) => textDataformat switch { - string dataFormat = DataFormats.UnicodeText; - - switch (textDataformat) - { - case TextDataFormat.Text: - dataFormat = DataFormats.Text; - break; - - case TextDataFormat.UnicodeText: - dataFormat = DataFormats.UnicodeText; - break; - - case TextDataFormat.Rtf: - dataFormat = DataFormats.Rtf; - break; - - case TextDataFormat.Html: - dataFormat = DataFormats.Html; - break; - - case TextDataFormat.CommaSeparatedValue: - dataFormat = DataFormats.CommaSeparatedValue; - break; - - case TextDataFormat.Xaml: - dataFormat = DataFormats.Xaml; - break; - } - - return dataFormat; - } + TextDataFormat.Text => DataFormats.Text, + TextDataFormat.UnicodeText => DataFormats.UnicodeText, + TextDataFormat.Rtf => DataFormats.Rtf, + TextDataFormat.Html => DataFormats.Html, + TextDataFormat.CommaSeparatedValue => DataFormats.CommaSeparatedValue, + TextDataFormat.Xaml => DataFormats.Xaml, + _ => DataFormats.UnicodeText + }; /// /// Validate the text data format. /// - internal static bool IsValidTextDataFormat(TextDataFormat textDataFormat) + /// + internal static bool IsValidTextDataFormat(TextDataFormat textDataFormat) => textDataFormat switch { - if (textDataFormat == TextDataFormat.Text || - textDataFormat == TextDataFormat.UnicodeText || - textDataFormat == TextDataFormat.Rtf || - textDataFormat == TextDataFormat.Html || - textDataFormat == TextDataFormat.CommaSeparatedValue || - textDataFormat == TextDataFormat.Xaml) - { - return true; - } - else - { - return false; - } - } - - #endregion Internal Methods - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods + TextDataFormat.Text => true, + TextDataFormat.UnicodeText => true, + TextDataFormat.Rtf => true, + TextDataFormat.Html => true, + TextDataFormat.CommaSeparatedValue => true, + TextDataFormat.Xaml => true, + _ => false + }; /// - /// Allows a the new format name to be specified if the requested format is not - /// in the list + /// Static class containing the internal format list and associated lookup methods. /// - private static DataFormat InternalGetDataFormat(int id) + private static class DataFormatsImpl { - // Ensures the predefined Win32 data formats into our format list. - EnsurePredefined(); - - // Lock the data format list to obtains the mutual-exclusion. - lock (_formatListlock) + /// + /// Ensures that the Win32 predefined formats are setup in our format list. + /// This is called anytime we need to search the list. + /// + static DataFormatsImpl() { - DataFormat formatItem; - StringBuilder sb; - int index; - - for (int n = 0; n < _formatList.Count; n++) + // Create format list for the default formats. + s_formatList = new List(19) { - formatItem = (DataFormat)_formatList[n]; + new(DataFormats.UnicodeText, NativeMethods.CF_UNICODETEXT), + new(DataFormats.Text, NativeMethods.CF_TEXT), + new(DataFormats.Bitmap, NativeMethods.CF_BITMAP), + new(DataFormats.MetafilePicture, NativeMethods.CF_METAFILEPICT), + new(DataFormats.EnhancedMetafile, NativeMethods.CF_ENHMETAFILE), + new(DataFormats.Dif, NativeMethods.CF_DIF), + new(DataFormats.Tiff, NativeMethods.CF_TIFF), + new(DataFormats.OemText, NativeMethods.CF_OEMTEXT), + new(DataFormats.Dib, NativeMethods.CF_DIB), + new(DataFormats.Palette, NativeMethods.CF_PALETTE), + new(DataFormats.PenData, NativeMethods.CF_PENDATA), + new(DataFormats.Riff, NativeMethods.CF_RIFF), + new(DataFormats.WaveAudio, NativeMethods.CF_WAVE), + new(DataFormats.SymbolicLink, NativeMethods.CF_SYLK), + new(DataFormats.FileDrop, NativeMethods.CF_HDROP), + new(DataFormats.Locale, NativeMethods.CF_LOCALE) + }; + + int xamlFormatId = UnsafeNativeMethods.RegisterClipboardFormat(DataFormats.Xaml); + if (xamlFormatId != 0) + s_formatList.Add(new DataFormat(DataFormats.Xaml, xamlFormatId)); + + // This is the format to store trust boundary information. Essentially this is accompalished by storing + // the permission set of the source application where the content comes from. During paste we compare this to + // the permission set of the target application. + int applicationTrustFormatId = UnsafeNativeMethods.RegisterClipboardFormat(DataFormats.ApplicationTrust); + if (applicationTrustFormatId != 0) + s_formatList.Add(new DataFormat(DataFormats.ApplicationTrust, applicationTrustFormatId)); + + // RegisterClipboardFormat returns 0 on failure + int inkServicesFrameworkFormatId = UnsafeNativeMethods.RegisterClipboardFormat(StrokeCollection.InkSerializedFormat); + if (inkServicesFrameworkFormatId != 0) + s_formatList.Add(new DataFormat(StrokeCollection.InkSerializedFormat, inkServicesFrameworkFormatId)); + } - // OLE FORMATETC defined CLIPFORMAT as the unsigned short, so we should ignore - // high 2bytes to find the matched CLIPFORMAT ID. - if ((formatItem.Id & 0x0000ffff) == (id & 0x0000ffff)) + /// + /// Allows a new format name to be specified if the requested format is not in the list. + /// + internal static unsafe DataFormat GetDataFormat(int id) + { + // Lock the data format list to obtain the mutual-exclusion. + lock (s_formatListlock) + { + DataFormat formatItem; + for (int i = 0; i < s_formatList.Count; i++) { - return formatItem; + formatItem = s_formatList[i]; + + // OLE FORMATETC defined CLIPFORMAT as the unsigned short, so we should ignore + // high 2bytes to find the matched CLIPFORMAT ID. + if ((formatItem.Id & 0x0000ffff) == (id & 0x0000ffff)) + return formatItem; } - } - sb = new StringBuilder(NativeMethods.MAX_PATH); + const string Format = "Format"; - // This can happen if windows adds a standard format that we don't know about, - // so we should play it safe. - if (UnsafeNativeMethods.GetClipboardFormatName(id, sb, sb.Capacity) == 0) - { - sb.Length = 0; - sb.Append("Format").Append(id); - } + // This can happen if windows adds a standard format that we don't know about, so we should play it safe. + // Maximum length can be up to 255: https://learn.microsoft.com/en-us/windows/win32/dataxchg/about-atom-tables + Span formatName = stackalloc char[256]; + int atomLength = 0; + fixed (char* ptrFormatName = formatName) + { + // If the return value is zero, the ID was not found, hence we will just create a placeholder name "Format{id}" + if ((atomLength = UnsafeNativeMethods.GetClipboardFormatName(id, ptrFormatName, formatName.Length)) == 0) + { + Format.CopyTo(formatName); + id.TryFormat(formatName.Slice(Format.Length), out int charsWritten); + + atomLength = Format.Length + charsWritten; + } + } - index = _formatList.Add(new DataFormat(sb.ToString(), id)); + // Create a new format and store it + formatItem = new DataFormat(new string(formatName.Slice(0, atomLength)), id); + s_formatList.Add(formatItem); - return (DataFormat)_formatList[index]; + return formatItem; + } } - } - /// - /// Ensures that the Win32 predefined formats are setup in our format list. This - /// is called anytime we need to search the list - /// - private static void EnsurePredefined() - { - // Lock the data format list to obtains the mutual-exclusion. - lock (_formatListlock) + /// + /// Retrieves a data format using its name or attempts to register a new one if it doesn't exist. + /// + internal static DataFormat GetDataFormat(string format) { - if (_formatList == null) + ArgumentNullException.ThrowIfNull(format); + + if (format == string.Empty) + throw new ArgumentException(SR.DataObject_EmptyFormatNotAllowed); + + // Lock the data format list to obtain the mutual-exclusion. + lock (s_formatListlock) { - // Create format list for the default formats. - _formatList = new ArrayList(19); - - _formatList.Add(new DataFormat(UnicodeText, NativeMethods.CF_UNICODETEXT)); - _formatList.Add(new DataFormat(Text, NativeMethods.CF_TEXT)); - _formatList.Add(new DataFormat(Bitmap, NativeMethods.CF_BITMAP)); - _formatList.Add(new DataFormat(MetafilePicture, NativeMethods.CF_METAFILEPICT)); - _formatList.Add(new DataFormat(EnhancedMetafile, NativeMethods.CF_ENHMETAFILE)); - _formatList.Add(new DataFormat(Dif, NativeMethods.CF_DIF)); - _formatList.Add(new DataFormat(Tiff, NativeMethods.CF_TIFF)); - _formatList.Add(new DataFormat(OemText, NativeMethods.CF_OEMTEXT)); - _formatList.Add(new DataFormat(Dib, NativeMethods.CF_DIB)); - _formatList.Add(new DataFormat(Palette, NativeMethods.CF_PALETTE)); - _formatList.Add(new DataFormat(PenData, NativeMethods.CF_PENDATA)); - _formatList.Add(new DataFormat(Riff, NativeMethods.CF_RIFF)); - _formatList.Add(new DataFormat(WaveAudio, NativeMethods.CF_WAVE)); - _formatList.Add(new DataFormat(SymbolicLink, NativeMethods.CF_SYLK)); - _formatList.Add(new DataFormat(FileDrop, NativeMethods.CF_HDROP)); - _formatList.Add(new DataFormat(Locale, NativeMethods.CF_LOCALE)); - int xamlFormatId = UnsafeNativeMethods.RegisterClipboardFormat(Xaml); - if (xamlFormatId != 0) + for (int i = 0; i < s_formatList.Count; i++) { - _formatList.Add(new DataFormat(Xaml,xamlFormatId)); - } + DataFormat formatItem = s_formatList[i]; - // This is the format to store trust boundary information. Essentially this is accompalished by storing - // the permission set of the source application where the content comes from. During paste we compare this to - // the permissio set of the target application. - int applicationTrustFormatId = UnsafeNativeMethods.RegisterClipboardFormat(DataFormats.ApplicationTrust); - if (applicationTrustFormatId != 0) - { - _formatList.Add(new DataFormat(ApplicationTrust, applicationTrustFormatId)); + if (formatItem.Name.Equals(format, StringComparison.OrdinalIgnoreCase)) + return formatItem; } - // RegisterClipboardFormat returns 0 on failure - int inkServicesFrameworkFormatId = UnsafeNativeMethods.RegisterClipboardFormat(System.Windows.Ink.StrokeCollection.InkSerializedFormat); - if (inkServicesFrameworkFormatId != 0) - { - _formatList.Add(new DataFormat(System.Windows.Ink.StrokeCollection.InkSerializedFormat, - inkServicesFrameworkFormatId)); - } -} - } - } + // Register this format string + int formatId = UnsafeNativeMethods.RegisterClipboardFormat(format); - #endregion Private Methods + if (formatId == 0) + throw new Win32Exception(); - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ + // Create a new format and store it + DataFormat newFormat = new(format, formatId); + s_formatList.Add(newFormat); - #region Private Fields + return newFormat; + } + } - // The registered data format list. - private static ArrayList _formatList; + /// + /// List of all registered that we're aware of. + /// + private static readonly List s_formatList; - // This object is for locking the _formatList to access safe in the multi-thread. - private static readonly Object _formatListlock = new Object(); + /// + /// Lock specially used for access to field. + /// + private static readonly Lock s_formatListlock = new(); - #endregion Private Fields + } } - - #endregion DataFormats class } diff --git a/src/Microsoft.DotNet.Wpf/src/Shared/MS/Win32/UnsafeNativeMethodsCLR.cs b/src/Microsoft.DotNet.Wpf/src/Shared/MS/Win32/UnsafeNativeMethodsCLR.cs index 3f7a7a66582..e1483f9717a 100644 --- a/src/Microsoft.DotNet.Wpf/src/Shared/MS/Win32/UnsafeNativeMethodsCLR.cs +++ b/src/Microsoft.DotNet.Wpf/src/Shared/MS/Win32/UnsafeNativeMethodsCLR.cs @@ -153,10 +153,10 @@ public static bool DeleteObjectNoThrow(HandleRef hObject) [DllImport(ExternDll.Gdi32, EntryPoint="SelectObject", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)] public static extern IntPtr CriticalSelectObject(HandleRef hdc, IntPtr obj); - [DllImport(ExternDll.User32, CharSet = System.Runtime.InteropServices.CharSet.Auto, BestFitMapping = false, SetLastError = true)] - public static extern int GetClipboardFormatName(int format, StringBuilder lpString, int cchMax); + [DllImport(ExternDll.User32, CharSet = CharSet.Auto, BestFitMapping = false, SetLastError = true)] + public static unsafe extern int GetClipboardFormatName(int format, char* lpString, int cchMax); - [DllImport(ExternDll.User32, SetLastError = true, CharSet = System.Runtime.InteropServices.CharSet.Auto, BestFitMapping = false)] + [DllImport(ExternDll.User32, SetLastError = true, CharSet = CharSet.Auto, BestFitMapping = false)] public static extern int RegisterClipboardFormat(string format); [DllImport(ExternDll.Gdi32, SetLastError = true, ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]