From 3c3acc10d995e8a2491b385051064d7296caf8c1 Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Wed, 29 Jan 2025 19:31:09 -0800 Subject: [PATCH] Refactor OLE code to be shareable (#12857) Refactor OLE code to be shareable To do this, this change introduces platform agnostic static interfaces. - `INrbfSerializer` (`CoreNrbfSerializer` implements common functionality, `WinFormsNrbfSerializer` composes and adds WinForms specific functionality) - `IOleServices` (`WinFormsOleServices`, no common class) - `IRuntime` to encapsulate everything `DragDropHelper` was made explicit to `DataObject` (via `IComVisibleDataObject`) as we wouldn't really handle arbitrary `IDataObjects`. Also encapsulates data requests into a `DataRequest` struct to simplify call sites and documentation of arguments. Namespaces were changed to facilitate this. Files will be moved and renamed in follow-up PRs to maintain history. Also: - Add `IBitmap` for abstract bitmap usage - Add `IDragEvent` and `IGiveFeedbackEvent`to abstract drag event info - Change `ITypeResolver.GetType` to `BindToType` to match Binder terminology - Move `TryWriteJsonData` and `TryGetObjectFromJson` to Core - Move `RestrictedTypeDeserializationException` to Core - Move more format definitions to DataFormatNames as well as "restricted" helpers - Add `FullyQualifiedTypeName` struct for caching `TypeName` generation - Add `IComVisibleDataObject` to represent a type that implements both our managed interface and COM methods - Move `ConvertToSerializationException` helper to ExceptionExtensions - Use same NRBF deserialization logic for Resx as OLE - Use CsWin32 definitions to define enums --- .../src/System/Drawing/Bitmap.cs | 5 +- .../src/NativeMethods.txt | 3 + .../src/Resources/SR.resx | 3 + .../src/System/ExceptionExtensions.cs | 24 +- .../BinaryFormat/BinaryFormatWriter.cs | 21 ++ .../Deserializer/ArrayRecordDeserializer.cs | 4 +- .../Deserializer/ClassRecordDeserializer.cs | 2 +- .../Deserializer/DefaultTypeResolver.cs | 4 +- .../BinaryFormat/Deserializer/Deserializer.cs | 2 +- .../Windows/BinaryFormat/ITypeResolver.cs | 2 +- .../Private/Windows/Graphics/IBitmap.cs | 22 ++ .../src/System/Private/Windows/IRuntime.cs | 17 + .../Windows/Nrbf/CoreNrbfSerializer.cs | 173 ++++++++++ .../Windows/Nrbf/FullyQualifiedTypeName.cs | 36 +++ .../Private/Windows/Nrbf/INrbfSerializer.cs | 30 ++ .../Nrbf/SerializationRecordExtensions.cs | 89 ++++-- .../Private/Windows/Ole/DataFormatNames.cs | 86 ++++- .../System/Private/Windows/Ole/DataRequest.cs | 35 +++ .../Windows/Ole/IComVisibleDataObject.cs | 13 + .../System/Private/Windows/Ole/IDragEvent.cs | 57 ++++ .../Private/Windows/Ole/IGiveFeedbackEvent.cs | 28 ++ .../Private/Windows/Ole/IOleServices.cs | 21 ++ .../RestrictedTypeDeserializationException.cs | 2 +- .../src/System/Private/Windows/Runtime.cs | 35 +++ ...DataObject.Composition.TypeNameComparer.cs | 4 +- .../BinaryFormatWriterTests.cs | 2 +- .../FormattedObject/HashTableTests.cs | 2 +- .../FormatTests/FormattedObject/ListTests.cs | 2 +- .../FormattedObject/PrimitiveTypeTests.cs | 12 +- .../SerializationRecordExtensionsTests.cs | 1 + .../src/GlobalSuppressions.cs | 5 +- src/System.Windows.Forms/src/GlobalUsings.cs | 1 + .../src/Resources/SR.resx | 3 - .../src/Resources/xlf/SR.cs.xlf | 5 - .../src/Resources/xlf/SR.de.xlf | 5 - .../src/Resources/xlf/SR.es.xlf | 5 - .../src/Resources/xlf/SR.fr.xlf | 5 - .../src/Resources/xlf/SR.it.xlf | 5 - .../src/Resources/xlf/SR.ja.xlf | 5 - .../src/Resources/xlf/SR.ko.xlf | 5 - .../src/Resources/xlf/SR.pl.xlf | 5 - .../src/Resources/xlf/SR.pt-BR.xlf | 5 - .../src/Resources/xlf/SR.ru.xlf | 5 - .../src/Resources/xlf/SR.tr.xlf | 5 - .../src/Resources/xlf/SR.zh-Hans.xlf | 5 - .../src/Resources/xlf/SR.zh-Hant.xlf | 5 - .../src/System/Resources/ResXDataNode.cs | 5 +- .../Forms/ActiveX/AxHost.PropertyBagStream.cs | 4 +- .../Control.ActiveXImpl.PropertyBagStream.cs | 4 +- .../Forms/ActiveX/Control.ActiveXImpl.cs | 5 +- .../WinFormsBinaryFormatWriter.cs | 28 -- .../src/System/Windows/Forms/Control.cs | 3 +- .../RichTextBox/RichTextBox.OleCallback.cs | 26 +- .../ToolStrips/ToolStripDropTargetManager.cs | 3 +- .../Controls/ToolStrips/ToolStripItem.cs | 6 +- .../WebBrowser/WebBrowser.WebBrowserSite.cs | 4 +- .../Windows/Forms/GiveFeedbackEventArgs.cs | 6 +- .../Forms/Nrbf/WinFormsNrbfSerializer.cs | 64 ++++ .../WinFormsSerializationRecordExtensions.cs | 75 +---- ...bject.Composition.BinaryFormatUtilities.cs | 52 ++- .../OLE/DataObject.Composition.Binder.cs | 295 ++++-------------- ...ject.Composition.NativeToRuntimeAdapter.cs | 9 +- ...ect.Composition.NativeToWinFormsAdapter.cs | 139 +++++---- ...ject.Composition.RuntimeToNativeAdapter.cs | 13 +- ...ect.Composition.WinFormsToNativeAdapter.cs | 88 ++---- .../Forms/OLE/DataObject.Composition.cs | 65 ++-- .../Forms/OLE/DataObject.FormatEnumerator.cs | 19 +- .../System/Windows/Forms/OLE/DataObject.cs | 65 +--- .../Windows/Forms/OLE/DragDropEffects.cs | 12 +- .../Windows/Forms/OLE/DragDropFormat.cs | 2 +- .../Windows/Forms/OLE/DragDropHelper.cs | 238 +++++--------- .../System/Windows/Forms/OLE/DragEventArgs.cs | 33 +- .../System/Windows/Forms/OLE/DropImageType.cs | 16 +- .../System/Windows/Forms/OLE/DropSource.cs | 8 +- .../System/Windows/Forms/OLE/DropTarget.cs | 53 ++-- .../Windows/Forms/OLE/WinFormsOleServices.cs | 79 +++++ .../System/Windows/Forms/WinFormsRuntime.cs | 16 + .../ComDisabledTests/DataObjectComTests.cs | 2 +- .../WinFormsBinaryFormattedObjectTests.cs | 25 +- .../Forms/BinaryFormatUtilitiesTests.cs | 75 ++++- .../Windows/Forms/DataObjectComTests.cs | 2 +- .../System/Windows/Forms/DataObjectTests.cs | 12 +- .../Windows/Forms/DragDropFormatTests.cs | 1 + .../Windows/Forms/DragDropHelperTests.cs | 65 ++-- 84 files changed, 1416 insertions(+), 1012 deletions(-) create mode 100644 src/System.Private.Windows.Core/src/System/Private/Windows/Graphics/IBitmap.cs create mode 100644 src/System.Private.Windows.Core/src/System/Private/Windows/IRuntime.cs create mode 100644 src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/CoreNrbfSerializer.cs create mode 100644 src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/FullyQualifiedTypeName.cs create mode 100644 src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/INrbfSerializer.cs create mode 100644 src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataRequest.cs create mode 100644 src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IComVisibleDataObject.cs create mode 100644 src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IDragEvent.cs create mode 100644 src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IGiveFeedbackEvent.cs create mode 100644 src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IOleServices.cs create mode 100644 src/System.Private.Windows.Core/src/System/Private/Windows/Runtime.cs rename src/{System.Windows.Forms/src/System/Windows/Forms/OLE => System.Private.Windows.Core/src/System/Reflection/Metadata}/DataObject.Composition.TypeNameComparer.cs (96%) create mode 100644 src/System.Windows.Forms/src/System/Windows/Forms/Nrbf/WinFormsNrbfSerializer.cs create mode 100644 src/System.Windows.Forms/src/System/Windows/Forms/OLE/WinFormsOleServices.cs create mode 100644 src/System.Windows.Forms/src/System/Windows/Forms/WinFormsRuntime.cs diff --git a/src/System.Drawing.Common/src/System/Drawing/Bitmap.cs b/src/System.Drawing.Common/src/System/Drawing/Bitmap.cs index ca3fd84a2bf..cbecd64dc1c 100644 --- a/src/System.Drawing.Common/src/System/Drawing/Bitmap.cs +++ b/src/System.Drawing.Common/src/System/Drawing/Bitmap.cs @@ -7,6 +7,7 @@ using System.Drawing.Imaging.Effects; #endif using System.IO; +using System.Private.Windows.Graphics; using System.Runtime.CompilerServices; using System.Runtime.Serialization; @@ -16,7 +17,7 @@ namespace System.Drawing; $"System.Drawing.Design.UITypeEditor, {AssemblyRef.SystemDrawing}")] [Serializable] [TypeForwardedFrom(AssemblyRef.SystemDrawing)] -public sealed unsafe class Bitmap : Image, IPointer +public sealed unsafe class Bitmap : Image, IPointer, IBitmap { private static readonly Color s_defaultTransparentColor = Color.LightGray; @@ -152,6 +153,8 @@ public static Bitmap FromResource(IntPtr hinstance, string bitmapName) [EditorBrowsable(EditorBrowsableState.Advanced)] public IntPtr GetHbitmap() => GetHbitmap(Color.LightGray); + HBITMAP IBitmap.GetHbitmap() => (HBITMAP)GetHbitmap(); + [EditorBrowsable(EditorBrowsableState.Advanced)] public IntPtr GetHbitmap(Color background) { diff --git a/src/System.Private.Windows.Core/src/NativeMethods.txt b/src/System.Private.Windows.Core/src/NativeMethods.txt index b3fedfbb96b..95ee7d88f19 100644 --- a/src/System.Private.Windows.Core/src/NativeMethods.txt +++ b/src/System.Private.Windows.Core/src/NativeMethods.txt @@ -3,6 +3,8 @@ BI_COMPRESSION BitBlt BOOL CallWindowProc +CFSTR_FILENAME +CFSTR_FILENAMEA CFSTR_DROPDESCRIPTION CFSTR_INDRAGLOOP CLIPBOARD_FORMAT @@ -140,6 +142,7 @@ IGlobalInterfaceTable ImageLockMode INPLACE_E_NOTOOLSPACE IntersectClipRect +INK_SERIALIZED_FORMAT IPicture IPictureDisp IStream diff --git a/src/System.Private.Windows.Core/src/Resources/SR.resx b/src/System.Private.Windows.Core/src/Resources/SR.resx index c7ca1ce9428..f1f61b3355d 100644 --- a/src/System.Private.Windows.Core/src/Resources/SR.resx +++ b/src/System.Private.Windows.Core/src/Resources/SR.resx @@ -128,4 +128,7 @@ Clipboard format registration did not succeed. + + Failed to deserialize JSON data. + diff --git a/src/System.Private.Windows.Core/src/System/ExceptionExtensions.cs b/src/System.Private.Windows.Core/src/System/ExceptionExtensions.cs index 3c94f041371..cf333b2a465 100644 --- a/src/System.Private.Windows.Core/src/System/ExceptionExtensions.cs +++ b/src/System.Private.Windows.Core/src/System/ExceptionExtensions.cs @@ -1,6 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Reflection; +using System.Runtime.ExceptionServices; +using System.Runtime.Serialization; + namespace System; internal static class ExceptionExtensions @@ -9,11 +13,29 @@ internal static class ExceptionExtensions /// Returns if the exception is an exception that isn't recoverable and/or a likely /// bug in our implementation. /// - public static bool IsCriticalException(this Exception ex) + internal static bool IsCriticalException(this Exception ex) => ex is NullReferenceException or StackOverflowException or OutOfMemoryException or ThreadAbortException or IndexOutOfRangeException or AccessViolationException; + + /// + /// Converts the given exception to a if needed, nesting the original exception + /// and assigning the original stack trace. + /// + internal static SerializationException ConvertToSerializationException(this Exception ex) + { + if (ex is TargetInvocationException targetException) + { + ex = targetException.InnerException ?? ex; + } + + return ex is SerializationException serializationException + ? serializationException + : (SerializationException)ExceptionDispatchInfo.SetRemoteStackTrace( + new SerializationException(ex.Message, ex), + ex.StackTrace ?? string.Empty); + } } diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/BinaryFormatWriter.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/BinaryFormatWriter.cs index 08ee18b01c7..9cf789c6c11 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/BinaryFormatWriter.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/BinaryFormatWriter.cs @@ -753,6 +753,27 @@ static bool Write(Stream stream, object value) } } + public static bool TryWriteJsonData(Stream stream, object value) + { + if (value is not IJsonData jsonData) + { + return false; + } + + using BinaryFormatWriterScope writer = new(stream); + new BinaryLibrary(libraryId: 2, IJsonData.CustomAssemblyName).Write(writer); + new ClassWithMembersAndTypes( + new ClassInfo(1, $"{typeof(IJsonData).Namespace}.JsonData", [$"<{nameof(jsonData.JsonBytes)}>k__BackingField", $"<{nameof(jsonData.InnerTypeAssemblyQualifiedName)}>k__BackingField"]), + libraryId: 2, + new MemberTypeInfo[] { new(BinaryType.PrimitiveArray, PrimitiveType.Byte), new(BinaryType.String, null) }, + new MemberReference(idRef: 3), + new BinaryObjectString(objectId: 4, jsonData.InnerTypeAssemblyQualifiedName)).Write(writer); + + new ArraySinglePrimitive(objectId: 3, jsonData.JsonBytes).Write(writer); + + return true; + } + /// /// Simple wrapper to ensure the is reset to its original position if the /// throws. diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Deserializer/ArrayRecordDeserializer.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Deserializer/ArrayRecordDeserializer.cs index 91a8571faec..548960d134e 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Deserializer/ArrayRecordDeserializer.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Deserializer/ArrayRecordDeserializer.cs @@ -22,7 +22,7 @@ internal ArrayRecordDeserializer(ArrayRecord arrayRecord, IDeserializer deserial Debug.Assert(arrayRecord.RecordType is not (SerializationRecordType.ArraySingleString or SerializationRecordType.ArraySinglePrimitive)); _arrayRecord = arrayRecord; - _elementType = deserializer.TypeResolver.GetType(arrayRecord.TypeName.GetElementType()); + _elementType = deserializer.TypeResolver.BindToType(arrayRecord.TypeName.GetElementType()); Type expectedArrayType = arrayRecord.Rank switch { 1 => _elementType.MakeArrayType(), @@ -120,7 +120,7 @@ internal override SerializationRecordId Continue() [RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.BinaryFormattedObject.TypeResolver.GetType(TypeName)")] internal static Array? GetSimpleBinaryArray(ArrayRecord arrayRecord, ITypeResolver typeResolver) { - Type arrayRecordElementType = typeResolver.GetType(arrayRecord.TypeName.GetElementType()); + Type arrayRecordElementType = typeResolver.BindToType(arrayRecord.TypeName.GetElementType()); Type elementType = arrayRecordElementType; while (elementType.IsArray) { diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Deserializer/ClassRecordDeserializer.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Deserializer/ClassRecordDeserializer.cs index e02af637478..58832db42b0 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Deserializer/ClassRecordDeserializer.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Deserializer/ClassRecordDeserializer.cs @@ -29,7 +29,7 @@ private protected ClassRecordDeserializer(ClassRecord classRecord, object @objec [RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.BinaryFormattedObject.TypeResolver.GetType(TypeName)")] internal static ObjectRecordDeserializer Create(ClassRecord classRecord, IDeserializer deserializer) { - Type type = deserializer.TypeResolver.GetType(classRecord.TypeName); + Type type = deserializer.TypeResolver.BindToType(classRecord.TypeName); SerializationRecordId id = classRecord.Id; ISerializationSurrogate? surrogate = deserializer.GetSurrogate(type); diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Deserializer/DefaultTypeResolver.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Deserializer/DefaultTypeResolver.cs index f629d5987f0..236bdf6f4cf 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Deserializer/DefaultTypeResolver.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Deserializer/DefaultTypeResolver.cs @@ -26,7 +26,7 @@ internal DefaultTypeResolver(DeserializationOptions options) /// [RequiresUnreferencedCode("Calls System.Reflection.Assembly.GetType(String)")] [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] - Type ITypeResolver.GetType(TypeName typeName) + Type ITypeResolver.BindToType(TypeName typeName) { Debug.Assert(typeName.AssemblyName is not null); @@ -35,7 +35,7 @@ Type ITypeResolver.GetType(TypeName typeName) return cachedType; } - if (_binder?.GetType(typeName) is Type binderType) + if (_binder?.BindToType(typeName) is Type binderType) { // BinaryFormatter is inconsistent about what caching behavior you get with binders. // It would always cache the last item from the binder, but wouldn't put the result diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Deserializer/Deserializer.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Deserializer/Deserializer.cs index 59f8e367b0e..96d00a5058d 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Deserializer/Deserializer.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/Deserializer/Deserializer.cs @@ -334,7 +334,7 @@ void IDeserializer.CompleteObject(SerializationRecordId id) // There are no remaining dependencies. Hook any finished events for this object. // Doing at the end of deserialization for simplicity. - Type type = _typeResolver.GetType(classRecord.TypeName); + Type type = _typeResolver.BindToType(classRecord.TypeName); object @object = _deserializedObjects[completedId]; OnDeserialized += SerializationEvents.GetOnDeserializedForType(type, @object); diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/ITypeResolver.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/ITypeResolver.cs index d47a7871438..4db3f477c6d 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/ITypeResolver.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/BinaryFormat/ITypeResolver.cs @@ -15,5 +15,5 @@ internal interface ITypeResolver /// [RequiresUnreferencedCode("Calls System.Reflection.Assembly.GetType(String)")] [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] - Type GetType(TypeName typeName); + Type BindToType(TypeName typeName); } diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Graphics/IBitmap.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Graphics/IBitmap.cs new file mode 100644 index 00000000000..f753eb46cc7 --- /dev/null +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Graphics/IBitmap.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Drawing; + +namespace System.Private.Windows.Graphics; + +/// +/// Platform agnostic interface for a bitmap. +/// +internal interface IBitmap +{ + /// + /// Gets a Win32 HBITMAP handle for this bitmap. + /// + HBITMAP GetHbitmap(); + + /// + /// Size of the bitmap in pixels. + /// + Size Size { get; } +} diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/IRuntime.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/IRuntime.cs new file mode 100644 index 00000000000..b7da540e914 --- /dev/null +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/IRuntime.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Private.Windows.Nrbf; +using System.Private.Windows.Ole; + +namespace System.Private.Windows; + +/// +/// Unifying interface for runtime specific services and types. +/// +/// The platform specific DataFormat struct type. +internal interface IRuntime + : INrbfSerializer, IOleServices + where TDataFormat : IDataFormat +{ +} diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/CoreNrbfSerializer.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/CoreNrbfSerializer.cs new file mode 100644 index 00000000000..11a303e926c --- /dev/null +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/CoreNrbfSerializer.cs @@ -0,0 +1,173 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Drawing; +using System.Formats.Nrbf; +using System.Private.Windows.BinaryFormat; +using System.Reflection.Metadata; + +namespace System.Private.Windows.Nrbf; + +/// +/// Core NRBF serializer. Supports common .NET types. +/// +internal class CoreNrbfSerializer : INrbfSerializer +{ + private static Dictionary? s_knownTypes; + + // These types are read from and written to serialized stream manually, accessing record field by field. + // Thus they are re-hydrated with no formatters and are safe. The default resolver should recognize them + // to resolve primitive types or fields of the specified type T. + private static readonly Type[] s_intrinsicTypes = + [ + // Primitive types. + typeof(byte), + typeof(sbyte), + typeof(short), + typeof(ushort), + typeof(int), + typeof(uint), + typeof(long), + typeof(ulong), + typeof(double), + typeof(float), + typeof(char), + typeof(bool), + typeof(string), + typeof(decimal), + typeof(DateTime), + typeof(TimeSpan), + typeof(IntPtr), + typeof(UIntPtr), + // Special type we use to report that binary formatting is disabled. + typeof(NotSupportedException), + // Lists of primitive types + typeof(List), + typeof(List), + typeof(List), + typeof(List), + typeof(List), + typeof(List), + typeof(List), + typeof(List), + typeof(List), + typeof(List), + typeof(List), + typeof(List), + typeof(List), + typeof(List), + typeof(List), + typeof(List), + // Arrays of primitive types. + typeof(byte[]), + typeof(sbyte[]), + typeof(short[]), + typeof(ushort[]), + typeof(int[]), + typeof(uint[]), + typeof(long[]), + typeof(ulong[]), + typeof(float[]), + typeof(double[]), + typeof(char[]), + typeof(bool[]), + typeof(string[]), + typeof(decimal[]), + typeof(DateTime[]), + typeof(TimeSpan[]), + + // Exchange types, they are serialized with the .NET Framework assembly name. + // In .NET they are located in System.Drawing.Primitives. + typeof(RectangleF), + typeof(PointF), + typeof(SizeF), + typeof(Rectangle), + typeof(Point), + typeof(Size), + typeof(Color) + ]; + + public static bool TryWriteObject(Stream stream, object value) => + BinaryFormatWriter.TryWriteFrameworkObject(stream, value) + || BinaryFormatWriter.TryWriteJsonData(stream, value) + || BinaryFormatWriter.TryWriteDrawingPrimitivesObject(stream, value); + + public static bool TryGetObject(SerializationRecord record, [NotNullWhen(true)] out object? value) => + record.TryGetFrameworkObject(out value) + // While these shouldn't normally be in a ResX file, it doesn't hurt to read them and simplifies the code. + || record.TryGetDrawingPrimitivesObject(out value); + + public static bool TryBindToType(TypeName typeName, [NotNullWhen(true)] out Type? type) + { + if (s_knownTypes is null) + { + s_knownTypes = new(s_intrinsicTypes.Length, TypeNameComparer.Default); + foreach (Type intrinsic in s_intrinsicTypes) + { + s_knownTypes.Add(intrinsic.ToTypeName(), intrinsic); + } + } + + return s_knownTypes.TryGetValue(typeName, out type); + } + + public static bool IsSupportedType() => + typeof(T) == typeof(byte) + || typeof(T) == typeof(sbyte) + || typeof(T) == typeof(short) + || typeof(T) == typeof(ushort) + || typeof(T) == typeof(int) + || typeof(T) == typeof(uint) + || typeof(T) == typeof(long) + || typeof(T) == typeof(ulong) + || typeof(T) == typeof(double) + || typeof(T) == typeof(float) + || typeof(T) == typeof(char) + || typeof(T) == typeof(bool) + || typeof(T) == typeof(string) + || typeof(T) == typeof(decimal) + || typeof(T) == typeof(DateTime) + || typeof(T) == typeof(TimeSpan) + || typeof(T) == typeof(IntPtr) + || typeof(T) == typeof(UIntPtr) + || typeof(T) == typeof(NotSupportedException) + || typeof(T) == typeof(List) + || typeof(T) == typeof(List) + || typeof(T) == typeof(List) + || typeof(T) == typeof(List) + || typeof(T) == typeof(List) + || typeof(T) == typeof(List) + || typeof(T) == typeof(List) + || typeof(T) == typeof(List) + || typeof(T) == typeof(List) + || typeof(T) == typeof(List) + || typeof(T) == typeof(List) + || typeof(T) == typeof(List) + || typeof(T) == typeof(List) + || typeof(T) == typeof(List) + || typeof(T) == typeof(List) + || typeof(T) == typeof(List) + || typeof(T) == typeof(byte[]) + || typeof(T) == typeof(sbyte[]) + || typeof(T) == typeof(short[]) + || typeof(T) == typeof(ushort[]) + || typeof(T) == typeof(int[]) + || typeof(T) == typeof(uint[]) + || typeof(T) == typeof(long[]) + || typeof(T) == typeof(ulong[]) + || typeof(T) == typeof(float[]) + || typeof(T) == typeof(double[]) + || typeof(T) == typeof(char[]) + || typeof(T) == typeof(bool[]) + || typeof(T) == typeof(string[]) + || typeof(T) == typeof(decimal[]) + || typeof(T) == typeof(DateTime[]) + || typeof(T) == typeof(TimeSpan[]) + || typeof(T) == typeof(RectangleF) + || typeof(T) == typeof(PointF) + || typeof(T) == typeof(SizeF) + || typeof(T) == typeof(Rectangle) + || typeof(T) == typeof(Point) + || typeof(T) == typeof(Size) + || typeof(T) == typeof(Color); +} diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/FullyQualifiedTypeName.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/FullyQualifiedTypeName.cs new file mode 100644 index 00000000000..0dfe74cfacc --- /dev/null +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/FullyQualifiedTypeName.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel.DataAnnotations; + +namespace System.Private.Windows.Nrbf; + +/// +/// Represents a fully qualified type name split into assembly name and type name. +/// +internal readonly struct FullyQualifiedTypeName : IEquatable +{ + public FullyQualifiedTypeName(string fullTypeName, string assemblyName) + { + Debug.Assert(!string.IsNullOrWhiteSpace(assemblyName)); + Debug.Assert(!string.IsNullOrWhiteSpace(fullTypeName)); + + AssemblyName = assemblyName; + FullTypeName = fullTypeName; + } + + [Required] + public string AssemblyName { get; init; } + + [Required] + public string FullTypeName { get; init; } + + public bool Equals(FullyQualifiedTypeName other) => + other.AssemblyName == AssemblyName && other.FullTypeName == FullTypeName; + + public override bool Equals(object? obj) => obj is FullyQualifiedTypeName other && Equals(other); + + public override int GetHashCode() => HashCode.Combine(AssemblyName, FullTypeName); + + public override string ToString() => $"{FullTypeName}, {AssemblyName}"; +} diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/INrbfSerializer.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/INrbfSerializer.cs new file mode 100644 index 00000000000..0984482151b --- /dev/null +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/INrbfSerializer.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Formats.Nrbf; +using System.Reflection.Metadata; + +namespace System.Private.Windows.Nrbf; + +internal interface INrbfSerializer +{ + /// + /// Tries to write supported objects to a stream. + /// + static abstract bool TryWriteObject(Stream stream, object value); + + /// + /// Tries to read supported objects from a . + /// + static abstract bool TryGetObject(SerializationRecord record, [NotNullWhen(true)] out object? value); + + /// + /// Tries to bind the given type name to a supported type. + /// + static abstract bool TryBindToType(TypeName typeName, [NotNullWhen(true)] out Type? type); + + /// + /// Returns if the type is supported by the serializer. + /// + static abstract bool IsSupportedType(); +} diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/SerializationRecordExtensions.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/SerializationRecordExtensions.cs index c313ae0017f..7300b9ad273 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/SerializationRecordExtensions.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Nrbf/SerializationRecordExtensions.cs @@ -7,56 +7,32 @@ using System.Formats.Nrbf; using System.Private.Windows.BinaryFormat; using System.Reflection; +using System.Reflection.Metadata; using System.Runtime.ExceptionServices; using System.Runtime.Serialization; +using System.Text.Json; -namespace System.Windows.Forms.Nrbf; +namespace System.Private.Windows.Nrbf; internal static class SerializationRecordExtensions { + /// + internal static SerializationRecord DecodeNrbf(this Stream stream) => stream.DecodeNrbf(out _); + /// - /// Converts the given exception to a if needed, nesting the original exception - /// and assigning the original stack trace. + /// Decodes a NRBF stream, converting any exceptions to . /// - private static SerializationException ConvertToSerializationException(this Exception ex) - => ex is SerializationException serializationException - ? serializationException - : (SerializationException)ExceptionDispatchInfo.SetRemoteStackTrace( - new SerializationException(ex.Message, ex), - ex.StackTrace ?? string.Empty); - - internal static SerializationRecord Decode(this Stream stream) - { - try - { - return NrbfDecoder.Decode(stream, leaveOpen: true); - } - catch (Exception ex) when (ex is ArgumentException or InvalidCastException or ArithmeticException or IOException) - { - // Make the exception easier to catch, but retain the original stack trace. - throw ex.ConvertToSerializationException(); - } - catch (TargetInvocationException ex) - { - throw ExceptionDispatchInfo.Capture(ex.InnerException!).SourceException.ConvertToSerializationException(); - } - } - - internal static SerializationRecord Decode(this Stream stream, out IReadOnlyDictionary recordMap) + internal static SerializationRecord DecodeNrbf(this Stream stream, out IReadOnlyDictionary recordMap) { try { return NrbfDecoder.Decode(stream, out recordMap, leaveOpen: true); } - catch (Exception ex) when (ex is ArgumentException or InvalidCastException or ArithmeticException or IOException) + catch (Exception ex) when (ex is ArgumentException or InvalidCastException or ArithmeticException or IOException or TargetInvocationException) { // Make the exception easier to catch, but retain the original stack trace. throw ex.ConvertToSerializationException(); } - catch (TargetInvocationException ex) - { - throw ExceptionDispatchInfo.Capture(ex.InnerException!).SourceException.ConvertToSerializationException(); - } } /// @@ -592,4 +568,51 @@ public static bool TryGetDrawingPrimitivesObject( private static bool IsPrimitiveArrayRecord(SerializationRecord serializationRecord) => serializationRecord.RecordType is SerializationRecordType.ArraySingleString or SerializationRecordType.ArraySinglePrimitive; + + /// + /// Tries to deserialize this object if it was serialized as JSON. + /// + /// + /// if the data was serialized as JSON. Otherwise, . + /// + /// If the data was supposed to be our , but was serialized incorrectly./> + /// If an exception occurred while JSON deserializing. + [RequiresUnreferencedCode("Calls System.Private.Windows.BinaryFormat.ITypeResolver.GetType(TypeName)")] + public static bool TryGetObjectFromJson(this SerializationRecord record, ITypeResolver resolver, out object? @object) + { + @object = null; + + if (record.TypeName.AssemblyName?.FullName != IJsonData.CustomAssemblyName) + { + // The data was not serialized as JSON. + return false; + } + + if (record is not ClassRecord types + || types.GetRawValue("k__BackingField") is not SZArrayRecord byteData + || types.GetRawValue("k__BackingField") is not string innerTypeFullName + || !TypeName.TryParse(innerTypeFullName, out TypeName? serializedTypeName)) + { + // This is supposed to be JsonData, but somehow the binary formatted data is corrupt. + throw new SerializationException(); + } + + Type serializedType = resolver.BindToType(serializedTypeName); + if (!serializedType.IsAssignableTo(typeof(T))) + { + // Not the type the caller asked for so @object remains null. + return true; + } + + try + { + @object = JsonSerializer.Deserialize(byteData.GetArray()); + } + catch (Exception ex) + { + throw new NotSupportedException(SR.ClipboardOrDragDrop_JsonDeserializationFailed, ex); + } + + return true; + } } diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataFormatNames.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataFormatNames.cs index 74b1e6eb55f..daeeadfd04b 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataFormatNames.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataFormatNames.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Windows.Win32.UI.Shell; + namespace System.Private.Windows.Ole; internal static partial class DataFormatNames @@ -28,11 +30,47 @@ internal static partial class DataFormatNames internal const string Serializable = "WindowsForms10PersistentObject"; internal const string Xaml = "Xaml"; internal const string XamlPackage = "XamlPackage"; - internal const string InkSerializedFormat = "Ink Serialized Format"; - internal const string FileNameAnsi = "FileName"; - internal const string FileNameUnicode = "FileNameW"; + internal const string InkSerializedFormat = PInvokeCore.INK_SERIALIZED_FORMAT; + internal const string FileNameAnsi = PInvokeCore.CFSTR_FILENAMEA; + internal const string FileNameUnicode = PInvokeCore.CFSTR_FILENAME; internal const string BinaryFormatBitmap = "System.Drawing.Bitmap"; + /// + /// A format used internally by the drag image manager. + /// + internal const string DragContext = "DragContext"; + + /// + /// A format that contains the drag image bottom-up device-independent bitmap bits. + /// + internal const string DragImageBits = "DragImageBits"; + + /// + /// A format that contains the value passed to + /// and controls whether to allow text specified in to be displayed on the drag image. + /// + internal const string DragSourceHelperFlags = "DragSourceHelperFlags"; + + /// + /// A format used to identify an object's drag image window so that it's visual information can be updated dynamically. + /// + internal const string DragWindow = "DragWindow"; + + /// + /// A format that is non-zero if the drop target supports drag images. + /// + internal const string IsShowingLayered = "IsShowingLayered"; + + /// + /// A format that is non-zero if the drop target supports drop description text. + /// + internal const string IsShowingText = "IsShowingText"; + + /// + /// A format that is non-zero if the drag image is a layered window with a size of 96x96. + /// + internal const string UsingDefaultDragImage = "UsingDefaultDragImage"; + /// /// Adds all the "synonyms" for the specified format. /// @@ -73,4 +111,46 @@ internal static void AddMappedFormats(string format, T formats) break; } } + + /// + /// Check if the is one of the restricted formats, which formats that + /// correspond to primitives or are pre-defined in the OS such as strings, bitmaps, and OLE types. + /// + internal static bool IsRestrictedFormat(string format) => RestrictDeserializationToSafeTypes(format) + || format is Text + or UnicodeText + or Rtf + or Html + or OemText + or FileDrop + or FileNameAnsi + or FileNameUnicode; + + /// + /// We are restricting binary serialization and deserialization of formats that represent strings, bitmaps or OLE types. + /// + /// format name + /// - serialize only safe types, strings or bitmaps. + /// + /// + /// These formats are also restricted in WPF + /// https://github.com/dotnet/wpf/blob/db1ae73aae0e043326e2303b0820d361de04e751/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/dataobject.cs#L2801 + /// + /// + internal static bool RestrictDeserializationToSafeTypes(string format) => + format is String + or BinaryFormatBitmap + or Csv + or Dib + or Dif + or Locale + or PenData + or Riff + or SymbolicLink + or Tiff + or WaveAudio + or Bitmap + or Emf + or Palette + or Wmf; } diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataRequest.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataRequest.cs new file mode 100644 index 00000000000..86cf39c578d --- /dev/null +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/DataRequest.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel.DataAnnotations; +using System.Reflection.Metadata; + +namespace System.Private.Windows.Ole; + +/// +/// Encapsulates state of a data request. +/// +internal readonly struct DataRequest +{ + public DataRequest(string format) => Format = format; + + [Required] + public string Format { get; init; } + + public bool AutoConvert { get; init; } = true; + + /// + /// Provides the list of custom allowed types that user considers safe to deserialize from the payload. + /// Resolver should recognize the closure of all non-primitive and not known types in the payload, + /// such as field types and types in the inheritance hierarchy and the code to match these types to the + /// s read from the deserialized stream. + /// + public Func? Resolver { get; init; } = null; + + /// + /// if the user had not requested any specific type, i.e. the call originates from + /// API family, that returns an . + /// if the user had requested a specific type by calling API family. + /// + public bool UntypedRequest { get; init; } = false; +} diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IComVisibleDataObject.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IComVisibleDataObject.cs new file mode 100644 index 00000000000..901c6824003 --- /dev/null +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IComVisibleDataObject.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Windows.Win32.System.Com; + +namespace System.Private.Windows.Ole; + +/// +/// Used to filter to fully COM visible data objects. Notably the platform specific DataObject class. +/// +internal interface IComVisibleDataObject : IDataObject.Interface, IManagedWrapper, IDataObjectInternal +{ +} diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IDragEvent.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IDragEvent.cs new file mode 100644 index 00000000000..16840179107 --- /dev/null +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IDragEvent.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Windows.Win32.System.Ole; +using Windows.Win32.UI.Shell; + +namespace System.Private.Windows.Ole; + +internal interface IDragEvent +{ + /// + /// Gets the x-coordinate of the mouse pointer. + /// + int X { get; } + + /// + /// Gets the y-coordinate of the mouse pointer. + /// + int Y { get; } + + /// + /// Gets which drag-and-drop operations are allowed by the target of the drag event. + /// + DROPEFFECT Effect { get; } + + /// + /// Gets the drop description image type. + /// + DROPIMAGETYPE DropImageType { get; } + + /// + /// Data object, if any, associated with the drag event. + /// + IComVisibleDataObject? DataObject { get; } + + /// + /// Gets or sets the drop description text such as "Move to %1". + /// + /// + /// + /// UI coloring is applied to the text in + /// if used by specifying %1 in . + /// + /// + string? Message { get; set; } + + /// + /// Gets or sets the drop description text such as "Documents" when %1 is specified in . + /// + /// + /// + /// UI coloring is applied to the text in + /// if used by specifying %1 in . + /// + /// + string? MessageReplacementToken { get; set; } +} diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IGiveFeedbackEvent.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IGiveFeedbackEvent.cs new file mode 100644 index 00000000000..86ce9d77393 --- /dev/null +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IGiveFeedbackEvent.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Drawing; +using System.Private.Windows.Graphics; + +namespace System.Private.Windows.Ole; + +/// +/// Runtime agnostic interface for the GiveFeedback event. +/// +internal interface IGiveFeedbackEvent +{ + /// + /// Gets the drag image bitmap. + /// + IBitmap? DragImage { get; } + + /// + /// Gets the drag image cursor offset. + /// + Point CursorOffset { get; set; } + + /// + /// Gets a value indicating whether a layered window drag image is used. + /// + bool UseDefaultDragImage { get; set; } +} diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IOleServices.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IOleServices.cs new file mode 100644 index 00000000000..65558714efa --- /dev/null +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/IOleServices.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Windows.Win32.System.Com; + +namespace System.Private.Windows.Ole; + +internal unsafe interface IOleServices +{ + /// + /// This method is called to validate that OLE is initialized and + /// that the current thread is a single-threaded apartment (STA). + /// + /// Current thread is not in the right state for OLE. + static abstract void EnsureThreadState(); + + /// + /// Called after unsuccessfully performing clipboard serialization. + /// + static abstract HRESULT GetDataHere(string format, object data, FORMATETC* pformatetc, STGMEDIUM* pmedium); +} diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/RestrictedTypeDeserializationException.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/RestrictedTypeDeserializationException.cs index 601429d131e..869023a93a8 100644 --- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/RestrictedTypeDeserializationException.cs +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/RestrictedTypeDeserializationException.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace System.Windows.Forms; +namespace System.Private.Windows.Ole; /// /// This exception is used to indicate that clipboard contains a serialized diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Runtime.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Runtime.cs new file mode 100644 index 00000000000..feac7f3985c --- /dev/null +++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Runtime.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Formats.Nrbf; +using System.Private.Windows.Nrbf; +using System.Private.Windows.Ole; +using System.Reflection.Metadata; +using Windows.Win32.System.Com; + +namespace System.Private.Windows; + +/// +/// Abstract runtime implementation base class that provides the necessary services for the platform. +/// +/// The platform specific DataFormat struct type. +/// The platform specific NrbfSerializer type. +/// The platform specific OLE services type. +internal abstract class Runtime + : IRuntime + where TDataFormat : IDataFormat + where TNrbfSerializer : INrbfSerializer + where TOleServices : IOleServices +{ + static void IOleServices.EnsureThreadState() => TOleServices.EnsureThreadState(); + static unsafe HRESULT IOleServices.GetDataHere(string format, object data, FORMATETC* pformatetc, STGMEDIUM* pmedium) => + TOleServices.GetDataHere(format, data, pformatetc, pmedium); + static bool INrbfSerializer.IsSupportedType() => + TNrbfSerializer.IsSupportedType(); + static bool INrbfSerializer.TryBindToType(TypeName typeName, [NotNullWhen(true)] out Type? type) => + TNrbfSerializer.TryBindToType(typeName, out type); + static bool INrbfSerializer.TryGetObject(SerializationRecord record, [NotNullWhen(true)] out object? value) => + TNrbfSerializer.TryGetObject(record, out value); + static bool INrbfSerializer.TryWriteObject(Stream stream, object value) => + TNrbfSerializer.TryWriteObject(stream, value); +} diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.TypeNameComparer.cs b/src/System.Private.Windows.Core/src/System/Reflection/Metadata/DataObject.Composition.TypeNameComparer.cs similarity index 96% rename from src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.TypeNameComparer.cs rename to src/System.Private.Windows.Core/src/System/Reflection/Metadata/DataObject.Composition.TypeNameComparer.cs index 79b3617692a..da83c336aa5 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.TypeNameComparer.cs +++ b/src/System.Private.Windows.Core/src/System/Reflection/Metadata/DataObject.Composition.TypeNameComparer.cs @@ -1,9 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Reflection.Metadata; - -namespace System.Windows.Forms; +namespace System.Reflection.Metadata; /// /// Match s by matching full namespace-qualified type names and full assembly names, diff --git a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/BinaryFormatWriterTests.cs b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/BinaryFormatWriterTests.cs index 9199fe48c32..a4142ebdf64 100644 --- a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/BinaryFormatWriterTests.cs +++ b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/BinaryFormatWriterTests.cs @@ -5,9 +5,9 @@ using System.Runtime.Serialization.Formatters.Binary; using System.Private.Windows.BinaryFormat; using System.Formats.Nrbf; -using System.Windows.Forms.Nrbf; using System.Drawing; using System.Diagnostics; +using System.Private.Windows.Nrbf; namespace FormatTests.FormattedObject; diff --git a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/HashTableTests.cs b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/HashTableTests.cs index fd539a97998..8dd090e647d 100644 --- a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/HashTableTests.cs +++ b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/HashTableTests.cs @@ -8,7 +8,7 @@ using System.Private.Windows.BinaryFormat; using FormatTests.Common; using System.Formats.Nrbf; -using System.Windows.Forms.Nrbf; +using System.Private.Windows.Nrbf; namespace FormatTests.FormattedObject; diff --git a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/ListTests.cs b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/ListTests.cs index 17588843c9e..c4609e1e59e 100644 --- a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/ListTests.cs +++ b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/ListTests.cs @@ -7,7 +7,7 @@ using System.Private.Windows.BinaryFormat; using System.Runtime.Serialization.Formatters.Binary; using FormatTests.Common; -using System.Windows.Forms.Nrbf; +using System.Private.Windows.Nrbf; namespace FormatTests.FormattedObject; diff --git a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/PrimitiveTypeTests.cs b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/PrimitiveTypeTests.cs index 35b79abcf3e..6568e4e27aa 100644 --- a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/PrimitiveTypeTests.cs +++ b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/PrimitiveTypeTests.cs @@ -1,16 +1,16 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Formats.Nrbf; +using System.Globalization; +using System.Private.Windows.BinaryFormat; +using System.Private.Windows.BinaryFormat.Serializer; +using System.Private.Windows.Nrbf; +using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.Text; -using System.Private.Windows.BinaryFormat; using FormatTests.Common; using Record = System.Private.Windows.BinaryFormat.Serializer.Record; -using System.Formats.Nrbf; -using System.Windows.Forms.Nrbf; -using System.Globalization; -using System.Runtime.Serialization; -using System.Private.Windows.BinaryFormat.Serializer; namespace FormatTests.FormattedObject; diff --git a/src/System.Private.Windows.Core/tests/BinaryFormatTests/SerializationRecordExtensionsTests.cs b/src/System.Private.Windows.Core/tests/BinaryFormatTests/SerializationRecordExtensionsTests.cs index ab9c11d1b9c..d56be1da4e1 100644 --- a/src/System.Private.Windows.Core/tests/BinaryFormatTests/SerializationRecordExtensionsTests.cs +++ b/src/System.Private.Windows.Core/tests/BinaryFormatTests/SerializationRecordExtensionsTests.cs @@ -4,6 +4,7 @@ using System.Collections; using System.Drawing; using System.Formats.Nrbf; +using System.Private.Windows.Nrbf; using System.Runtime.Serialization.Formatters.Binary; using FormatTests.FormattedObject; diff --git a/src/System.Windows.Forms/src/GlobalSuppressions.cs b/src/System.Windows.Forms/src/GlobalSuppressions.cs index 32179b3c386..704b25528ea 100644 --- a/src/System.Windows.Forms/src/GlobalSuppressions.cs +++ b/src/System.Windows.Forms/src/GlobalSuppressions.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // Publicly shipped API - [assembly: SuppressMessage("Naming", "CA1725:Parameter names should match base declaration", Justification = "Public API", Scope = "member", Target = "~M:System.Windows.Forms.ButtonBase.OnKeyDown(System.Windows.Forms.KeyEventArgs)")] [assembly: SuppressMessage("Naming", "CA1725:Parameter names should match base declaration", Justification = "Public API", Scope = "member", Target = "~M:System.Windows.Forms.ButtonBase.OnKeyUp(System.Windows.Forms.KeyEventArgs)")] [assembly: SuppressMessage("Naming", "CA1725:Parameter names should match base declaration", Justification = "Public API", Scope = "member", Target = "~M:System.Windows.Forms.ButtonBase.OnMouseDown(System.Windows.Forms.MouseEventArgs)")] @@ -232,8 +231,8 @@ [assembly: SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Designer", Scope = "member", Target = "~M:System.Windows.Forms.MdiClient.ShouldSerializeLocation~System.Boolean")] // Ideally these should be different exceptions, but leaving them as shipped for compatibility -[assembly: SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "Compat", Scope = "member", Target = "~M:System.Windows.Forms.Composition.NativeToWinFormsAdapter.ReadByteStreamFromHGLOBAL(Windows.Win32.Foundation.HGLOBAL,System.Boolean@)~System.IO.MemoryStream")] -[assembly: SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "Compat", Scope = "member", Target = "~M:System.Windows.Forms.Composition.NativeToRuntimeAdapter.EnumFormatEtc(System.Runtime.InteropServices.ComTypes.DATADIR)~System.Runtime.InteropServices.ComTypes.IEnumFORMATETC")] +[assembly: SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "Compat", Scope = "member", Target = "~M:System.Private.Windows.Ole.Composition`2.NativeToRuntimeAdapter.EnumFormatEtc(System.Runtime.InteropServices.ComTypes.DATADIR)~System.Runtime.InteropServices.ComTypes.IEnumFORMATETC")] +[assembly: SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "Compat", Scope = "member", Target = "~M:System.Private.Windows.Ole.Composition`2.NativeToManagedAdapter.ReadByteStreamFromHGLOBAL(Windows.Win32.Foundation.HGLOBAL,System.Boolean@)~System.IO.MemoryStream")] [assembly: SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "Compat", Scope = "member", Target = "~M:System.Windows.Forms.Clipboard.SetDataObject(System.Object,System.Boolean,System.Int32,System.Int32)")] [assembly: SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "Compat", Scope = "member", Target = "~M:System.Windows.Forms.ComponentModel.Com2Interop.Com2TypeInfoProcessor.ProcessFunctions(Windows.Win32.System.Com.ITypeInfo*,System.Collections.Generic.IDictionary{System.String,System.Windows.Forms.ComponentModel.Com2Interop.Com2TypeInfoProcessor.PropertyInfo},System.Int32,System.Int32,System.Boolean@)")] [assembly: SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "Compat", Scope = "member", Target = "~M:System.Windows.Forms.Control.ActiveXImpl.ThrowHr(Windows.Win32.Foundation.HRESULT)")] diff --git a/src/System.Windows.Forms/src/GlobalUsings.cs b/src/System.Windows.Forms/src/GlobalUsings.cs index fc95cd971ed..8166e23a3d1 100644 --- a/src/System.Windows.Forms/src/GlobalUsings.cs +++ b/src/System.Windows.Forms/src/GlobalUsings.cs @@ -6,6 +6,7 @@ global using System.Private.Windows; global using AppContextSwitches = System.Windows.Forms.Primitives.LocalAppContextSwitches; +global using DragDropHelper = System.Private.Windows.Ole.DragDropHelper; global using Windows.Win32; global using Windows.Win32.Foundation; diff --git a/src/System.Windows.Forms/src/Resources/SR.resx b/src/System.Windows.Forms/src/Resources/SR.resx index 7ec60605094..0d03cb6cc6f 100644 --- a/src/System.Windows.Forms/src/Resources/SR.resx +++ b/src/System.Windows.Forms/src/Resources/SR.resx @@ -7033,9 +7033,6 @@ Stack trace where the illegal operation occurred was: The specified type '{0}' is not compatible with the specified format '{1}'. - - Failed to deserialize JSON data. - An object of type 'DataObject' will serialize as empty. Use 'DataObject.SetDataAsJson' APIs on your object to JSON-serialize the data within your object, then use the '{0}' API with your object. diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.cs.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.cs.xlf index 8cb19e46818..c82e2b8e680 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.cs.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.cs.xlf @@ -982,11 +982,6 @@ '{0}' není konkrétní typ a může mít za následek nevázaný pokus o deserializaci. Použijte prosím konkrétní typ nebo alternativně definujte funkci resolver, která podporuje typy, které byste očekávali načíst ze schránky nebo použít při operacích přetažení myší. - - Failed to deserialize JSON data. - Nepovedlo se deserializovat data JSON. - - Type {0} is not found. If this type is allowed, please ensure that the 'resolver' function provided in 'TryGetData<T>' APIs supports it. Typ {0} nebyl nalezen. Pokud je tento typ povolený, ujistěte se prosím, že ji podporuje funkce resolver poskytnutá v rozhraních API TryGetData<T>. diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.de.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.de.xlf index 921bcebf274..d1c8cfb1e50 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.de.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.de.xlf @@ -982,11 +982,6 @@ '{0}' ist kein konkreter Typ und kann zu einem ungebundenen Deserialisierungsversuch führen. Verwenden Sie einen konkreten Typ, oder definieren Sie alternativ eine 'Resolver'-Funktion, die Typen unterstützt, die Sie aus der Zwischenablage abrufen oder in Drag &amp; Drop-Vorgängen verwenden möchten. - - Failed to deserialize JSON data. - Fehler beim Deserialisieren von JSON-Daten. - - Type {0} is not found. If this type is allowed, please ensure that the 'resolver' function provided in 'TryGetData<T>' APIs supports it. Der Typ {0} wurde nicht gefunden. Wenn dieser Typ zulässig ist, stellen Sie sicher, dass er von der in den APIs "TryGetData<T>" bereitgestellten Resolverfunktion unterstützt wird. diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.es.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.es.xlf index 5be47fd28bb..26fa6487d6f 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.es.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.es.xlf @@ -982,11 +982,6 @@ '{0}' no es un tipo concreto y podría dar lugar a un intento de deserialización no enlazado. Use un tipo concreto o bien defina una función "resolver" que admita los tipos que espera recuperar del Portapapeles o use en operaciones de arrastrar y colocar. - - Failed to deserialize JSON data. - No se pudieron deserializar los datos JSON. - - Type {0} is not found. If this type is allowed, please ensure that the 'resolver' function provided in 'TryGetData<T>' APIs supports it. No se encuentra el tipo {0}. Si se permite este tipo, asegúrese de que la función "resolver" proporcionada en las API "TryGetData<T>" la admite. diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.fr.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.fr.xlf index 179d4d72db7..03ca8d0c9b9 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.fr.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.fr.xlf @@ -982,11 +982,6 @@ '{0}' n’est pas un type concret et peut entraîner une tentative de désérialisation non liée. Utilisez un type concret ou définissez une fonction « resolver » qui prend en charge les types que vous voulez récupérer à partir du Presse-papiers ou utilisez-la dans les opérations de glisser-déplacer. - - Failed to deserialize JSON data. - Nous n’avons pas pu désérialiser les données JSON. - - Type {0} is not found. If this type is allowed, please ensure that the 'resolver' function provided in 'TryGetData<T>' APIs supports it. Type {0} introuvable. Si ce type est autorisé, vérifiez que la fonction « resolver » fournie dans les API « TryGetData<T> » le prend en charge. diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.it.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.it.xlf index 40482fde880..f3da0bf58be 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.it.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.it.xlf @@ -982,11 +982,6 @@ '{0}' non è un tipo concreto e potrebbe causare un tentativo di deserializzazione senza limitazioni. Usare un tipo concreto o in alternativa definire una funzione 'resolver' che supporta tipi che si prevede di recuperare dagli Appunti o da usare nelle operazioni di trascinamento della selezione. - - Failed to deserialize JSON data. - Non è possibile deserializzare i dati JSON. - - Type {0} is not found. If this type is allowed, please ensure that the 'resolver' function provided in 'TryGetData<T>' APIs supports it. Tipo {0} non trovato. Se questo tipo è consentito, assicurarsi che sia supportata dalla funzione 'resolver' fornita nelle API 'TryGetData<T>'. diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.ja.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.ja.xlf index 4cad5f58ac6..1cd2cbea5d0 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.ja.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.ja.xlf @@ -982,11 +982,6 @@ '{0}' は具象型ではないため、バインドされていない逆シリアル化が試行される可能性があります。具象型を使用するか、クリップボードから取得する必要のある型をサポートする 'resolver' 関数を定義するか、ドラッグ アンド ドロップ操作で使用してください。 - - Failed to deserialize JSON data. - トークン データを逆シリアル化できませんでした。 - - Type {0} is not found. If this type is allowed, please ensure that the 'resolver' function provided in 'TryGetData<T>' APIs supports it. 型 {0} が見つかりません。この型が許可されている場合は、'TryGetData<T>' API で指定された 'resolver' 関数でサポートされていることを確認してください。 diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.ko.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.ko.xlf index a94d599b469..50b153e5c3f 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.ko.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.ko.xlf @@ -982,11 +982,6 @@ '{0}' 구체적인 형식이 아니므로 바인딩되지 않은 역직렬화를 시도할 수 있습니다. 구체적인 형식을 사용하거나 클립보드에서 검색하거나 끌어서 놓기 작업에 사용할 형식을 지원하는 'resolver' 함수를 정의하십시오. - - Failed to deserialize JSON data. - JSON 데이터를 역직렬화하지 못했습니다. - - Type {0} is not found. If this type is allowed, please ensure that the 'resolver' function provided in 'TryGetData<T>' APIs supports it. {0} 형식을 찾을 수 없습니다. 이 형식이 허용되는 경우 'TryGetData<T>' API에 제공된 'resolver' 함수가 이를 지원하는지 확인하세요. diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.pl.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.pl.xlf index e3483c6c99d..58c31cedde9 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.pl.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.pl.xlf @@ -982,11 +982,6 @@ '{0}' nie jest konkretnym typem i może spowodować niepowiązaną próbę deserializacji. Użyj konkretnego typu lub alternatywnie zdefiniuj funkcję "resolver", która obsługuje typy oczekiwane do pobrania ze schowka, lub użyj jej w operacjach przeciągania i upuszczania. - - Failed to deserialize JSON data. - Nie można zdeserializować danych JSON. - - Type {0} is not found. If this type is allowed, please ensure that the 'resolver' function provided in 'TryGetData<T>' APIs supports it. Nie znaleziono {0} typu. Jeśli ten typ jest dozwolony, upewnij się, że funkcja "resolver" podana w interfejsach API "TryGetData<T>" obsługuje go. diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.pt-BR.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.pt-BR.xlf index 62ea41c30e8..4a7b07a8ed2 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.pt-BR.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.pt-BR.xlf @@ -982,11 +982,6 @@ '{0}' não é um tipo concreto e pode resultar em uma tentativa de desserialização não associado. Use um tipo concreto ou, como alternativa, defina uma função 'resolver' que dê suporte aos tipos que você está esperando recuperar da área de transferência ou use em operações de arrastar e soltar. - - Failed to deserialize JSON data. - Falha ao desserializar dados JSON. - - Type {0} is not found. If this type is allowed, please ensure that the 'resolver' function provided in 'TryGetData<T>' APIs supports it. Tipo {0} não encontrado. Se esse tipo for permitido, verifique se a função 'resolver' fornecida nas APIs 'TryGetData<T>' dá suporte a ela. diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.ru.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.ru.xlf index 5c492a52e50..26a257c69be 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.ru.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.ru.xlf @@ -982,11 +982,6 @@ '{0}' не является конкретным типом и может привести к нео привязанной попытке десериализации. Используйте конкретный тип или определите функцию "resolver", которая поддерживает типы, которые вы ожидаете получить из буфера обмена, или используйте в операциях перетаскивания. - - Failed to deserialize JSON data. - Сбой десериализации данных JSON. - - Type {0} is not found. If this type is allowed, please ensure that the 'resolver' function provided in 'TryGetData<T>' APIs supports it. Тип {0} не найден. Если этот тип разрешен, убедитесь, что функция "resolver", указанная в API "TryGetData<T>", поддерживает ее. diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.tr.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.tr.xlf index 143bd986883..3bedfdf81d2 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.tr.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.tr.xlf @@ -982,11 +982,6 @@ '{0}' somut bir tür değil ve bir unbounded deserialization girişimine neden olabilir. Lütfen somut bir tür kullanın veya alternatif olarak panodan almayı bekleyen türleri destekleyen bir 'çözümleyici' işlevi ya da sürükle ve bırak işlemleri içinde kullanın. - - Failed to deserialize JSON data. - JSON verileri seri durumdan çıkarılamadı. - - Type {0} is not found. If this type is allowed, please ensure that the 'resolver' function provided in 'TryGetData<T>' APIs supports it. Tür {0} bulunamadı. Bu türe izin verilirse lütfen 'TryGetData<T>' API'lerinde sağlanan 'resolver' işlevinin bunu desteklediğinden emin olun. diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hans.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hans.xlf index 4c3f3aa4ce2..1f1c96486e2 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hans.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hans.xlf @@ -982,11 +982,6 @@ '{0}' 不是具体类型,可能导致未绑定的反序列化尝试。请使用具体类型,或者定义支持希望从剪贴板检索的类型或用于拖放操作的“解析程序”函数。 - - Failed to deserialize JSON data. - 无法反序列化 JSON 数据。 - - Type {0} is not found. If this type is allowed, please ensure that the 'resolver' function provided in 'TryGetData<T>' APIs supports it. 找不到类型 {0}。如果允许此类型,请确保 “TryGetData<T>” API 中提供的 “resolver” 函数支持它。 diff --git a/src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hant.xlf b/src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hant.xlf index ac37e3b1ed3..1544ee0e3ed 100644 --- a/src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hant.xlf +++ b/src/System.Windows.Forms/src/Resources/xlf/SR.zh-Hant.xlf @@ -982,11 +982,6 @@ '{0}' 不是實體類型,可能會導致未繫結的還原串行化嘗試。請使用實體類型,或是定義 『resolver』 函式,以支援您預期要從剪貼簿擷取的類型,或是用於拖放操作。 - - Failed to deserialize JSON data. - 無法還原序列化 JSON 資料。 - - Type {0} is not found. If this type is allowed, please ensure that the 'resolver' function provided in 'TryGetData<T>' APIs supports it. 找不到類型 {0}。如果允許此類型,請確定 'TryGetData<T>' API 中提供的 'resolver' 函式支援。 diff --git a/src/System.Windows.Forms/src/System/Resources/ResXDataNode.cs b/src/System.Windows.Forms/src/System/Resources/ResXDataNode.cs index 87f6631f9c8..4aaf56023c9 100644 --- a/src/System.Windows.Forms/src/System/Resources/ResXDataNode.cs +++ b/src/System.Windows.Forms/src/System/Resources/ResXDataNode.cs @@ -12,6 +12,7 @@ using System.Xml; using System.Windows.Forms.Nrbf; using System.Windows.Forms.BinaryFormat; +using System.Private.Windows.Nrbf; namespace System.Resources; @@ -429,8 +430,8 @@ Type ResolveTypeName(string typeName) try { - SerializationRecord rootRecord = stream.Decode(); - if (rootRecord.TryGetResXObject(out object? value)) + SerializationRecord rootRecord = stream.DecodeNrbf(); + if (WinFormsNrbfSerializer.TryGetObject(rootRecord, out object? value)) { return value; } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ActiveX/AxHost.PropertyBagStream.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ActiveX/AxHost.PropertyBagStream.cs index 71e25eb46e2..1e9fae4521c 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/ActiveX/AxHost.PropertyBagStream.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ActiveX/AxHost.PropertyBagStream.cs @@ -8,7 +8,7 @@ using Windows.Win32.System.Com; using Windows.Win32.System.Com.StructuredStorage; using Windows.Win32.System.Variant; -using System.Windows.Forms.Nrbf; +using System.Private.Windows.Nrbf; namespace System.Windows.Forms; @@ -25,7 +25,7 @@ internal PropertyBagStream(Stream stream) long position = stream.Position; try { - SerializationRecord rootRecord = stream.Decode(); + SerializationRecord rootRecord = stream.DecodeNrbf(); if (rootRecord.TryGetPrimitiveHashtable(out _bag!)) { return; diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ActiveX/Control.ActiveXImpl.PropertyBagStream.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ActiveX/Control.ActiveXImpl.PropertyBagStream.cs index 6cd07240c48..923f7ec4780 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/ActiveX/Control.ActiveXImpl.PropertyBagStream.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ActiveX/Control.ActiveXImpl.PropertyBagStream.cs @@ -4,7 +4,7 @@ using System.Collections; using System.Formats.Nrbf; using System.Private.Windows.BinaryFormat; -using System.Windows.Forms.Nrbf; +using System.Private.Windows.Nrbf; using Windows.Win32.System.Com; using Windows.Win32.System.Com.StructuredStorage; using Windows.Win32.System.Variant; @@ -29,7 +29,7 @@ internal void Read(IStream* istream) try { - SerializationRecord rootRecord = stream.Decode(); + SerializationRecord rootRecord = stream.DecodeNrbf(); success = rootRecord.TryGetPrimitiveHashtable(out _bag!); } catch (Exception e) when (!e.IsCriticalException()) diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ActiveX/Control.ActiveXImpl.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ActiveX/Control.ActiveXImpl.cs index ad195354d99..823500a8a6c 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/ActiveX/Control.ActiveXImpl.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ActiveX/Control.ActiveXImpl.cs @@ -21,6 +21,7 @@ using System.Windows.Forms.Nrbf; using RECTL = Windows.Win32.Foundation.RECTL; using System.Windows.Forms.BinaryFormat; +using System.Private.Windows.Nrbf; namespace System.Windows.Forms; @@ -1130,8 +1131,8 @@ bool SetValue(PropertyDescriptor currentProperty, object data) object? deserialized = null; try { - SerializationRecord rootRecord = stream.Decode(); - success = rootRecord.TryGetResXObject(out deserialized); + SerializationRecord rootRecord = stream.DecodeNrbf(); + success = WinFormsNrbfSerializer.TryGetObject(rootRecord, out deserialized); } catch (Exception ex) when (!ex.IsCriticalException()) { diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/WinFormsBinaryFormatWriter.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/WinFormsBinaryFormatWriter.cs index 99d78ffe4f1..daa7b2cab15 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/WinFormsBinaryFormatWriter.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/WinFormsBinaryFormatWriter.cs @@ -51,20 +51,6 @@ public static void WriteImageListStreamer(Stream stream, ImageListStreamer strea new ArraySinglePrimitive(3, data).Write(writer); } - public static void WriteJsonData(Stream stream, IJsonData jsonData) - { - using BinaryFormatWriterScope writer = new(stream); - new BinaryLibrary(libraryId: 2, IJsonData.CustomAssemblyName).Write(writer); - new ClassWithMembersAndTypes( - new ClassInfo(1, $"{typeof(IJsonData).Namespace}.JsonData", [$"<{nameof(jsonData.JsonBytes)}>k__BackingField", $"<{nameof(jsonData.InnerTypeAssemblyQualifiedName)}>k__BackingField"]), - libraryId: 2, - new MemberTypeInfo[] { new(BinaryType.PrimitiveArray, PrimitiveType.Byte), new(BinaryType.String, null) }, - new MemberReference(idRef: 3), - new BinaryObjectString(objectId: 4, jsonData.InnerTypeAssemblyQualifiedName)).Write(writer); - - new ArraySinglePrimitive(objectId: 3, jsonData.JsonBytes).Write(writer); - } - /// /// Writes the given if supported. /// @@ -86,22 +72,8 @@ static bool Write(Stream stream, object value) WriteBitmap(stream, bitmap); return true; } - else if (value is IJsonData jsonData) - { - WriteJsonData(stream, jsonData); - return true; - } return false; } } - - /// - /// Writes the given Framework, WinForms or System.Drawing.Primitives if supported. - /// - public static bool TryWriteCommonObject(Stream stream, object value) - { - return TryWriteObject(stream, value) - || BinaryFormatWriter.TryWriteDrawingPrimitivesObject(stream, value); - } } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs index 12cdcc62739..c6d4cc5e131 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs @@ -16,7 +16,6 @@ using Windows.Win32.UI.Accessibility; using Windows.Win32.UI.Input.KeyboardAndMouse; using Com = Windows.Win32.System.Com; -using ComTypes = System.Runtime.InteropServices.ComTypes; using Encoding = System.Text.Encoding; namespace System.Windows.Forms; @@ -4865,7 +4864,7 @@ public DragDropEffects DoDragDrop( Point cursorOffset, bool useDefaultDragImage) { - ComTypes.IDataObject dataObject = CreateRuntimeDataObjectForDrag(data); + DataObject dataObject = CreateRuntimeDataObjectForDrag(data); DROPEFFECT finalEffect; diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Controls/RichTextBox/RichTextBox.OleCallback.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Controls/RichTextBox/RichTextBox.OleCallback.cs index 0237f112b09..61dc63f779e 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Controls/RichTextBox/RichTextBox.OleCallback.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Controls/RichTextBox/RichTextBox.OleCallback.cs @@ -16,18 +16,12 @@ public partial class RichTextBox private unsafe class OleCallback : IRichEditOleCallback.Interface, IManagedWrapper { private readonly RichTextBox _owner; - private IDataObject? _lastDataObject; + private DataObject? _lastDataObject; private DragDropEffects _lastEffect; private DragEventArgs? _lastDragEventArgs; internal OleCallback(RichTextBox owner) => _owner = owner; - private void ClearDropDescription() - { - _lastDragEventArgs = null; - DragDropHelper.ClearDropDescription(_lastDataObject); - } - public HRESULT GetNewStorage(IStorage** lplpstg) { if (lplpstg is null) @@ -145,7 +139,7 @@ public HRESULT QueryAcceptData(Com.IDataObject* lpdataobj, ushort* lpcfFormat, R if (!fReally) { - // We are just querying + // Just querying to see if we can drop e.DropImageType = DropImageType.Invalid; e.Message = string.Empty; @@ -168,9 +162,12 @@ public HRESULT QueryAcceptData(Com.IDataObject* lpdataobj, ushort* lpcfFormat, R } else { + // Actual drop if (e.DropImageType > DropImageType.Invalid) { - ClearDropDescription(); + // Valid drop image type, go ahead and drop + _lastDragEventArgs = null; + DragDropHelper.ClearDropDescription(_lastDataObject); DragDropHelper.Drop(e); DragDropHelper.DragLeave(); } @@ -187,7 +184,7 @@ public HRESULT QueryAcceptData(Com.IDataObject* lpdataobj, ushort* lpcfFormat, R public HRESULT GetClipboardData(CHARRANGE* lpchrg, uint reco, Com.IDataObject** lplpdataobj) => HRESULT.E_NOTIMPL; - public unsafe HRESULT GetDragDropEffect(BOOL fDrag, MODIFIERKEYS_FLAGS grfKeyState, DROPEFFECT* pdwEffect) + public HRESULT GetDragDropEffect(BOOL fDrag, MODIFIERKEYS_FLAGS grfKeyState, DROPEFFECT* pdwEffect) { if (pdwEffect is null) { @@ -218,10 +215,11 @@ public unsafe HRESULT GetDragDropEffect(BOOL fDrag, MODIFIERKEYS_FLAGS grfKeySta // The below is the complete reverse of what the docs on MSDN suggest, // but if we follow the docs, we would be firing OnDragDrop all the - // time instead of OnDragOver (see - - // drag - fDrag = false, grfKeyState != 0 - // drop - fDrag = false, grfKeyState = 0 + // time instead of OnDragOver. + // + // drag - fDrag = false, grfKeyState != 0 + // drop - fDrag = false, grfKeyState = 0 + // // We only care about the drag. // // When we drop, lastEffect will have the right state diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Controls/ToolStrips/ToolStripDropTargetManager.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Controls/ToolStrips/ToolStripDropTargetManager.cs index 587061bebec..37951f0608f 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Controls/ToolStrips/ToolStripDropTargetManager.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Controls/ToolStrips/ToolStripDropTargetManager.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Private.Windows.Ole; using System.Runtime.InteropServices; namespace System.Windows.Forms; @@ -198,7 +199,7 @@ private void UpdateDropTarget(IDropTarget? newTarget, DragEventArgs e) // tell the drag image manager you've left if (e.DropImageType > DropImageType.Invalid) { - DragDropHelper.ClearDropDescription(e.Data); + DragDropHelper.ClearDropDescription(((IDragEvent)e).DataObject); DragDropHelper.DragLeave(); } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Controls/ToolStrips/ToolStripItem.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Controls/ToolStrips/ToolStripItem.cs index 0f6edc77145..90dc61223a2 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Controls/ToolStrips/ToolStripItem.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Controls/ToolStrips/ToolStripItem.cs @@ -6,10 +6,10 @@ using System.Drawing; using System.Drawing.Design; using System.Drawing.Imaging; +using System.Private.Windows.Ole; using System.Windows.Forms.Layout; using Windows.Win32.System.Ole; using Com = Windows.Win32.System.Com; -using IComDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; namespace System.Windows.Forms; @@ -2159,7 +2159,7 @@ public DragDropEffects DoDragDrop(object data, DragDropEffects allowedEffects) [EditorBrowsable(EditorBrowsableState.Advanced)] public unsafe DragDropEffects DoDragDrop(object data, DragDropEffects allowedEffects, Bitmap? dragImage, Point cursorOffset, bool useDefaultDragImage) { - if (data is not IComDataObject dataObject) + if (data is not IComVisibleDataObject dataObject) { DataObject? iwdata; if (data is IDataObject idataObject) @@ -2216,7 +2216,7 @@ public unsafe DragDropEffects DoDragDrop(object data, DragDropEffects allowedEff /// Else if the parent does not support reordering of items (Parent.AllowItemReorder = false) - /// then call back on the ToolStripItem's OnQueryContinueDrag/OnGiveFeedback methods. /// - internal IDropSource.Interface CreateDropSource(IComDataObject dataObject, Bitmap? dragImage, Point cursorOffset, bool useDefaultDragImage) + internal IDropSource.Interface CreateDropSource(IComVisibleDataObject dataObject, Bitmap? dragImage, Point cursorOffset, bool useDefaultDragImage) => ParentInternal is not null && ParentInternal.AllowItemReorder && ParentInternal.ItemReorderDropSource is not null ? new DropSource(ParentInternal.ItemReorderDropSource, dataObject, dragImage, cursorOffset, useDefaultDragImage) : new DropSource(this, dataObject, dragImage, cursorOffset, useDefaultDragImage); diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Controls/WebBrowser/WebBrowser.WebBrowserSite.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Controls/WebBrowser/WebBrowser.WebBrowserSite.cs index 85ba16c1c86..dcee7acaf47 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Controls/WebBrowser/WebBrowser.WebBrowserSite.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Controls/WebBrowser/WebBrowser.WebBrowserSite.cs @@ -5,7 +5,7 @@ using static Interop.Mshtml; using ComTypes = System.Runtime.InteropServices.ComTypes; using MsHtml = Windows.Win32.Web.MsHtml; -using Ole = Windows.Win32.System.Ole; +using OleIDropTarget = Windows.Win32.System.Ole.IDropTarget; namespace System.Windows.Forms; @@ -134,7 +134,7 @@ HRESULT IDocHostUIHandler.GetOptionKeyPath(string[] pbstrKey, uint dw) return HRESULT.E_NOTIMPL; } - HRESULT IDocHostUIHandler.GetDropTarget(Ole.IDropTarget.Interface pDropTarget, out Ole.IDropTarget.Interface? ppDropTarget) + HRESULT IDocHostUIHandler.GetDropTarget(OleIDropTarget.Interface pDropTarget, out OleIDropTarget.Interface? ppDropTarget) { // Set to null no matter what we return, to prevent the marshaller // from having issues if the pointer points to random stuff. diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/GiveFeedbackEventArgs.cs b/src/System.Windows.Forms/src/System/Windows/Forms/GiveFeedbackEventArgs.cs index 5d3933f6504..6d54e32ead1 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/GiveFeedbackEventArgs.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/GiveFeedbackEventArgs.cs @@ -2,13 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Drawing; +using System.Private.Windows.Graphics; +using System.Private.Windows.Ole; namespace System.Windows.Forms; /// /// Provides data for the event. /// -public class GiveFeedbackEventArgs : EventArgs +public class GiveFeedbackEventArgs : EventArgs, IGiveFeedbackEvent { /// /// Initializes a new instance of the class. @@ -50,6 +52,8 @@ public GiveFeedbackEventArgs(DragDropEffects effect, bool useDefaultCursors, Bit /// public Bitmap? DragImage { get; set; } + IBitmap? IGiveFeedbackEvent.DragImage => DragImage; + /// /// Gets or sets the drag image cursor offset. /// diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Nrbf/WinFormsNrbfSerializer.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Nrbf/WinFormsNrbfSerializer.cs new file mode 100644 index 00000000000..d88e1ed8380 --- /dev/null +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Nrbf/WinFormsNrbfSerializer.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Formats.Nrbf; +using System.Private.Windows.Nrbf; +using System.Reflection.Metadata; +using System.Windows.Forms.BinaryFormat; + +namespace System.Windows.Forms.Nrbf; + +/// +/// Provides binary format serialization services for Windows Forms. +/// +internal sealed partial class WinFormsNrbfSerializer : INrbfSerializer +{ + private static Dictionary? s_knownTypes; + + // These types are read from and written to serialized stream manually, accessing record field by field. + // Thus they are re-hydrated with no formatters and are safe. The default resolver should recognize them + // to resolve primitive types or fields of the specified type T. + private static readonly Type[] s_intrinsicTypes = + [ + // Common WinForms types. + typeof(ImageListStreamer), + typeof(Drawing.Bitmap), + ]; + + // Do not allow construction of this type. + private WinFormsNrbfSerializer() { } + + public static bool TryBindToType(TypeName typeName, [NotNullWhen(true)] out Type? type) + { + if (CoreNrbfSerializer.TryBindToType(typeName, out type)) + { + return true; + } + + if (s_knownTypes is null) + { + s_knownTypes = new(s_intrinsicTypes.Length, TypeNameComparer.Default); + foreach (Type intrinsic in s_intrinsicTypes) + { + s_knownTypes.Add(intrinsic.ToTypeName(), intrinsic); + } + } + + return s_knownTypes.TryGetValue(typeName, out type); + + throw new NotImplementedException(); + } + + public static bool TryGetObject(SerializationRecord record, [NotNullWhen(true)] out object? value) => + CoreNrbfSerializer.TryGetObject(record, out value) + || TryGetBitmap(record, out value) + || TryGetImageListStreamer(record, out value); + + public static bool TryWriteObject(Stream stream, object value) => + CoreNrbfSerializer.TryWriteObject(stream, value) + || WinFormsBinaryFormatWriter.TryWriteObject(stream, value); + + public static bool IsSupportedType() => CoreNrbfSerializer.IsSupportedType() + || typeof(T) == typeof(Drawing.Bitmap) + || typeof(T) == typeof(ImageListStreamer); +} diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Nrbf/WinFormsSerializationRecordExtensions.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Nrbf/WinFormsSerializationRecordExtensions.cs index 57e34f2bd0a..d8311b593b7 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Nrbf/WinFormsSerializationRecordExtensions.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Nrbf/WinFormsSerializationRecordExtensions.cs @@ -3,20 +3,17 @@ using System.Drawing; using System.Formats.Nrbf; -using System.Private.Windows.BinaryFormat; -using System.Reflection.Metadata; -using System.Runtime.Serialization; -using System.Text.Json; +using System.Private.Windows.Nrbf; namespace System.Windows.Forms.Nrbf; -internal static class WinFormsSerializationRecordExtensions +internal sealed partial class WinFormsNrbfSerializer { /// - /// Tries to get this object as a binary formatted . + /// Tries to get a record as a binary formatted . /// - public static bool TryGetImageListStreamer( - this SerializationRecord record, + private static bool TryGetImageListStreamer( + SerializationRecord record, out object? imageListStreamer) { return SerializationRecordExtensions.TryGet(Get, record, out imageListStreamer); @@ -39,9 +36,9 @@ static bool Get(SerializationRecord record, [NotNullWhen(true)] out object? imag } /// - /// Tries to get this object as a binary formatted . + /// Tries to get a record as a binary formatted . /// - public static bool TryGetBitmap(this SerializationRecord record, out object? bitmap) + private static bool TryGetBitmap(SerializationRecord record, out object? bitmap) { bitmap = null; @@ -56,62 +53,4 @@ public static bool TryGetBitmap(this SerializationRecord record, out object? bit bitmap = new Bitmap(new MemoryStream(data.GetArray())); return true; } - - /// - /// Tries to deserialize this object if it was serialized as JSON. - /// - /// - /// if the data was serialized as JSON. Otherwise, . - /// - /// If the data was supposed to be our , but was serialized incorrectly./> - /// If an exception occurred while JSON deserializing. - public static bool TryGetObjectFromJson(this SerializationRecord record, ITypeResolver resolver, out object? @object) - { - @object = null; - - if (record.TypeName.AssemblyName?.FullName != IJsonData.CustomAssemblyName) - { - // The data was not serialized as JSON. - return false; - } - - if (record is not ClassRecord types - || types.GetRawValue("k__BackingField") is not SZArrayRecord byteData - || types.GetRawValue("k__BackingField") is not string innerTypeFullName - || !TypeName.TryParse(innerTypeFullName, out TypeName? serializedTypeName)) - { - // This is supposed to be JsonData, but somehow the binary formatted data is corrupt. - throw new SerializationException(); - } - - Type serializedType = resolver.GetType(serializedTypeName); - if (!serializedType.IsAssignableTo(typeof(T))) - { - // Not the type the caller asked for so @object remains null. - return true; - } - - try - { - @object = JsonSerializer.Deserialize(byteData.GetArray()); - } - catch (Exception ex) - { - throw new NotSupportedException(SR.ClipboardOrDragDrop_JsonDeserializationFailed, ex); - } - - return true; - } - - /// - /// Try to get a supported object. This supports common types used in WinForms that do not have type converters. - /// - public static bool TryGetResXObject(this SerializationRecord record, [NotNullWhen(true)] out object? value) => - record.TryGetFrameworkObject(out value) - || record.TryGetBitmap(out value) - || record.TryGetImageListStreamer(out value); - - public static bool TryGetCommonObject(this SerializationRecord record, [NotNullWhen(true)] out object? value) => - record.TryGetResXObject(out value) - || record.TryGetDrawingPrimitivesObject(out value); } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.BinaryFormatUtilities.cs b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.BinaryFormatUtilities.cs index 34e16d24c8c..7efd1ea183f 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.BinaryFormatUtilities.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.BinaryFormatUtilities.cs @@ -3,19 +3,16 @@ using System.Formats.Nrbf; using System.Private.Windows.BinaryFormat; +using System.Private.Windows.Nrbf; using System.Reflection.Metadata; using System.Runtime.ExceptionServices; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters; using System.Runtime.Serialization.Formatters.Binary; -using System.Windows.Forms.BinaryFormat; -using System.Windows.Forms.Nrbf; -using CoreAppContextSwitches = System.Private.Windows.CoreAppContextSwitches; +namespace System.Private.Windows.Ole; -namespace System.Windows.Forms; - -internal static class BinaryFormatUtilities +internal static class BinaryFormatUtilities where TNrbfSerializer : INrbfSerializer { internal static void WriteObjectToStream(MemoryStream stream, object data, bool restrictSerialization) { @@ -23,7 +20,7 @@ internal static void WriteObjectToStream(MemoryStream stream, object data, bool try { - if (WinFormsBinaryFormatWriter.TryWriteCommonObject(stream, data)) + if (TNrbfSerializer.TryWriteObject(stream, data)) { return; } @@ -61,25 +58,22 @@ internal static void WriteObjectToStream(MemoryStream stream, object data, bool #pragma warning restore SYSLIB0011 } - internal static object? ReadObjectFromStream( - MemoryStream stream, - Func? resolver, - bool legacyMode) + internal static object? ReadObjectFromStream(MemoryStream stream, ref readonly DataRequest request) { long startPosition = stream.Position; SerializationRecord? record; - SerializationBinder binder = new TypeBinder(typeof(T), resolver, legacyMode); + SerializationBinder binder = new TypeBinder(typeof(T), in request); IReadOnlyDictionary recordMap; try { - record = stream.Decode(out recordMap); + record = stream.DecodeNrbf(out recordMap); } catch (Exception ex) when (!ex.IsCriticalException()) { // Couldn't parse for some reason, let BinaryFormatter handle the legacy invocation. // The typed APIs can't compare the specified type when the root record is not available. - if (legacyMode && CoreAppContextSwitches.ClipboardDragDropEnableUnsafeBinaryFormatterSerialization) + if (request.UntypedRequest && CoreAppContextSwitches.ClipboardDragDropEnableUnsafeBinaryFormatterSerialization) { stream.Position = startPosition; return ReadObjectWithBinaryFormatter(stream, binder); @@ -99,7 +93,7 @@ record = stream.Decode(out recordMap); // For the new TryGet APIs, ensure that the stream contains the requested type, // or type that can be assigned to the requested type. Type type = typeof(T); - if (!legacyMode && !type.MatchExceptAssemblyVersion(record.TypeName)) + if (!request.UntypedRequest && !type.MatchExceptAssemblyVersion(record.TypeName)) { if (record.TryGetObjectFromJson((ITypeResolver)binder, out object? data)) { @@ -120,7 +114,7 @@ record = stream.Decode(out recordMap); } } - if (record.TryGetCommonObject(out object? value)) + if (TNrbfSerializer.TryGetObject(record, out object? value)) { return value; } @@ -132,10 +126,12 @@ record = stream.Decode(out recordMap); typeof(T).FullName)); } - // NRBF deserializer fixes some known BinaryFormatter issues: - // 1. Doesn't allow arrays that have a non-zero base index (can't create these in C# or VB) - // 2. Only allows IObjectReference types that contain primitives (to avoid observable cycle - // dependencies to indeterminate state) + // NRBF deserializer avoids some known BinaryFormatter issues: + // + // 1. Doesn't allow arrays that have a non-zero base index (can't create these in C# or VB) + // 2. Only allows IObjectReference types that contain primitives (to avoid observable cycle + // dependencies to indeterminate state) + // // But it usually requires a resolver. Resolver is not available in the legacy mode, // so we will fall back to BinaryFormatter in that case. if (CoreAppContextSwitches.ClipboardDragDropEnableNrbfSerialization) @@ -144,7 +140,7 @@ record = stream.Decode(out recordMap); { return record.Deserialize(recordMap, (ITypeResolver)binder); } - catch (Exception ex) when (!ex.IsCriticalException() && legacyMode) + catch (Exception ex) when (!ex.IsCriticalException() && request.UntypedRequest) { } } @@ -155,15 +151,13 @@ record = stream.Decode(out recordMap); internal static object? ReadRestrictedObjectFromStream( MemoryStream stream, - Func? resolver, - bool legacyMode) + ref readonly DataRequest request) { - long startPosition = stream.Position; SerializationRecord? record; try { - record = stream.Decode(); + record = stream.DecodeNrbf(); } catch (Exception ex) when (!ex.IsCriticalException()) { @@ -173,9 +167,9 @@ record = stream.Decode(); // For the new TryGet APIs, ensure that the stream contains the requested type, // or type that can be assigned to the requested type. Type type = typeof(T); - if (!legacyMode && !type.MatchExceptAssemblyVersion(record.TypeName)) + if (!request.UntypedRequest && !type.MatchExceptAssemblyVersion(record.TypeName)) { - if (!TypeNameIsAssignableToType(record.TypeName, type, new TypeBinder(type, resolver, legacyMode))) + if (!TypeNameIsAssignableToType(record.TypeName, type, new TypeBinder(type, in request))) { // If clipboard contains an exception from SetData, we will get its message and throw. if (record.TypeName.FullName == typeof(NotSupportedException).FullName @@ -189,7 +183,7 @@ record = stream.Decode(); } } - return record.TryGetCommonObject(out object? value) + return TNrbfSerializer.TryGetObject(record, out object? value) ? value : throw new RestrictedTypeDeserializationException(SR.UnexpectedClipboardType); } @@ -198,7 +192,7 @@ private static bool TypeNameIsAssignableToType(TypeName typeName, Type type, ITy { try { - return resolver.GetType(typeName)?.IsAssignableTo(type) == true; + return resolver.BindToType(typeName)?.IsAssignableTo(type) == true; } catch (Exception ex) when (!ex.IsCriticalException()) { diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.Binder.cs b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.Binder.cs index 0a2f8b717f7..68da249358a 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.Binder.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.Binder.cs @@ -2,290 +2,113 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Private.Windows.BinaryFormat; +using System.Private.Windows.Nrbf; using System.Reflection.Metadata; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; -using CoreAppContextSwitches = System.Private.Windows.CoreAppContextSwitches; -namespace System.Windows.Forms; +namespace System.Private.Windows.Ole; /// -/// A type resolver for use in the when processing binary formatted stream -/// contained in our class using the typed consumption side APIs, such as -/// . This class recognizes primitive types, exchange types from -/// System.Drawing.Primitives, s or arrays of primitive types, and common WinForms types. -/// The user can provide a custom resolver for additional types. If the resolver function is not provided, -/// the parameter specified by the user is resolved automatically. +/// A type resolver for use in the +/// when processing binary formatted streams. /// /// /// +/// This class recognizes primitive types, exchange types from System.Drawing.Primitives, s +/// or arrays of primitive types, and common WinForms types. The user can provide a custom resolver for additional +/// types. If the resolver function is not provided, the parameter specified by the user is +/// resolved automatically. +/// +/// /// This class is used in and NRBF deserialization. /// /// -internal sealed class TypeBinder : SerializationBinder, ITypeResolver +internal sealed class TypeBinder : SerializationBinder, ITypeResolver + where TNrbfSerializer : INrbfSerializer { - private readonly Func? _resolver; - private readonly bool _legacyMode; - - // These types are read from and written to serialized stream manually, accessing record field by field. - // Thus they are re-hydrated with no formatters and are safe. The default resolver should recognize them - // to resolve primitive types or fields of the specified type T. - private static readonly Type[] s_intrinsicTypes = - [ - // Primitive types. - typeof(byte), - typeof(sbyte), - typeof(short), - typeof(ushort), - typeof(int), - typeof(uint), - typeof(long), - typeof(ulong), - typeof(double), - typeof(float), - typeof(char), - typeof(bool), - typeof(string), - typeof(decimal), - typeof(DateTime), - typeof(TimeSpan), - typeof(IntPtr), - typeof(UIntPtr), - // Special type we use to report that binary formatting is disabled. - typeof(NotSupportedException), - // Lists of primitive types - typeof(List), - typeof(List), - typeof(List), - typeof(List), - typeof(List), - typeof(List), - typeof(List), - typeof(List), - typeof(List), - typeof(List), - typeof(List), - typeof(List), - typeof(List), - typeof(List), - typeof(List), - typeof(List), - // Arrays of primitive types. - typeof(byte[]), - typeof(sbyte[]), - typeof(short[]), - typeof(ushort[]), - typeof(int[]), - typeof(uint[]), - typeof(long[]), - typeof(ulong[]), - typeof(float[]), - typeof(double[]), - typeof(char[]), - typeof(bool[]), - typeof(string[]), - typeof(decimal[]), - typeof(DateTime[]), - typeof(TimeSpan[]), - // Common WinForms types. - typeof(ImageListStreamer), - typeof(Drawing.Bitmap), - // Exchange types, they are serialized with the .NET Framework assembly name. - // In .NET they are located in System.Drawing.Primitives. - typeof(Drawing.RectangleF), - typeof(PointF), - typeof(Drawing.SizeF), - typeof(Drawing.Rectangle), - typeof(Point), - typeof(Drawing.Size), - typeof(Color) - ]; + private readonly Func _resolver; + private readonly bool _isUntypedRequest; + private readonly bool _hasCustomResolver; - private static Dictionary? s_knownTypes; - - private readonly Dictionary _userTypes = new(TypeNameComparer.Default); + private Dictionary? _cachedTypeNames; /// /// Type resolver for use with and NRBF deserializers to restrict types /// that can be instantiated. /// /// that the user expects to read from the binary formatted stream. - /// - /// Provides the list of custom allowed types that user considers safe to deserialize from the payload. - /// Resolver should recognize the closure of all non-primitive and not known types in the payload, - /// such as field types and types in the inheritance hierarchy and the code to match these types to the - /// s read from the deserialized stream. - /// - /// - /// if the user had not requested any specific type, i.e. the call originates from - /// API family, that returns an . - /// if the user had requested a specific type by calling API family. - /// - public TypeBinder(Type type, Func? resolver, bool legacyMode) + public TypeBinder(Type type, ref readonly DataRequest request) { - Debug.Assert(!legacyMode || (legacyMode && resolver is null), "GetData methods should not provide a resolver."); - _resolver = resolver; - _legacyMode = legacyMode; + Debug.Assert( + !request.UntypedRequest || (request.UntypedRequest && request.Resolver is null), + "Untyped methods should not provide a resolver."); + + _isUntypedRequest = request.UntypedRequest; + _hasCustomResolver = request.Resolver is not null; + _resolver = request.Resolver ?? CreateResolver(type); - if (resolver is null) + static Func CreateResolver(Type type) { // Resolver was not provided by the user, we will match the T using our default method: - // 1. If the type is a Value type and nullable, unwrap it - // 2. Check if the type had been forwarded from another assembly - // 3. Match assembly name with no version - // 4. Match namespace and type name + // + // 1. If the type is a Value type and nullable, unwrap it + // 2. Check if the type had been forwarded from another assembly + // 3. Match assembly name with no version + // 4. Match namespace and type name + // // Provide a custom resolver function to supports different type matching logic. - TypeName typeName = type.ToTypeName(); - - _userTypes.Add(typeName, type); - } - } - - [MemberNotNull(nameof(s_knownTypes))] - private static void InitializeCommonTypes() - { - if (s_knownTypes is not null) - { - return; - } - - s_knownTypes = new(TypeNameComparer.Default); - - foreach (Type type in s_intrinsicTypes) - { - s_knownTypes.Add(type.ToTypeName(), type); + TypeName knownType = type.ToTypeName(); + return typeName => knownType.Matches(typeName) ? type : null; } } - public static bool IsKnownType() => - typeof(T) == typeof(byte) - || typeof(T) == typeof(sbyte) - || typeof(T) == typeof(short) - || typeof(T) == typeof(ushort) - || typeof(T) == typeof(int) - || typeof(T) == typeof(uint) - || typeof(T) == typeof(long) - || typeof(T) == typeof(ulong) - || typeof(T) == typeof(double) - || typeof(T) == typeof(float) - || typeof(T) == typeof(char) - || typeof(T) == typeof(bool) - || typeof(T) == typeof(string) - || typeof(T) == typeof(decimal) - || typeof(T) == typeof(DateTime) - || typeof(T) == typeof(TimeSpan) - || typeof(T) == typeof(IntPtr) - || typeof(T) == typeof(UIntPtr) - || typeof(T) == typeof(NotSupportedException) - || typeof(T) == typeof(List) - || typeof(T) == typeof(List) - || typeof(T) == typeof(List) - || typeof(T) == typeof(List) - || typeof(T) == typeof(List) - || typeof(T) == typeof(List) - || typeof(T) == typeof(List) - || typeof(T) == typeof(List) - || typeof(T) == typeof(List) - || typeof(T) == typeof(List) - || typeof(T) == typeof(List) - || typeof(T) == typeof(List) - || typeof(T) == typeof(List) - || typeof(T) == typeof(List) - || typeof(T) == typeof(List) - || typeof(T) == typeof(List) - || typeof(T) == typeof(byte[]) - || typeof(T) == typeof(sbyte[]) - || typeof(T) == typeof(short[]) - || typeof(T) == typeof(ushort[]) - || typeof(T) == typeof(int[]) - || typeof(T) == typeof(uint[]) - || typeof(T) == typeof(long[]) - || typeof(T) == typeof(ulong[]) - || typeof(T) == typeof(float[]) - || typeof(T) == typeof(double[]) - || typeof(T) == typeof(char[]) - || typeof(T) == typeof(bool[]) - || typeof(T) == typeof(string[]) - || typeof(T) == typeof(decimal[]) - || typeof(T) == typeof(DateTime[]) - || typeof(T) == typeof(TimeSpan[]) - || typeof(T) == typeof(ImageListStreamer) - || typeof(T) == typeof(Drawing.Bitmap) - || typeof(T) == typeof(Drawing.RectangleF) - || typeof(T) == typeof(PointF) - || typeof(T) == typeof(Drawing.SizeF) - || typeof(T) == typeof(Drawing.Rectangle) - || typeof(T) == typeof(Point) - || typeof(T) == typeof(Drawing.Size) - || typeof(T) == typeof(Color); - public override Type? BindToType(string assemblyName, string typeName) { ArgumentException.ThrowIfNullOrWhiteSpace(assemblyName); ArgumentException.ThrowIfNullOrWhiteSpace(typeName); - if (GetCachedType(assemblyName, typeName, typeName: null) is Type type) + if (_isUntypedRequest) { - return type; - } - - if (_legacyMode) - { - return CoreAppContextSwitches.ClipboardDragDropEnableUnsafeBinaryFormatterSerialization - ? null - : throw new NotSupportedException(string.Format( - SR.BinaryFormatter_NotSupported_InClipboardOrDragDrop_UseTypedAPI, - $"{assemblyName}.{typeName}")); + if (CoreAppContextSwitches.ClipboardDragDropEnableUnsafeBinaryFormatterSerialization) + { + // With the switch enabled, we'll let the BinaryFormatter allow any type to be deserialized + // when coming in from the untyped APIs. + return null; + } + + // Should never have gotten here - should not be creating a BinaryFormatter when not enabled. + throw new NotSupportedException(string.Format( + SR.BinaryFormatter_NotSupported_InClipboardOrDragDrop_UseTypedAPI, + $"{assemblyName}.{typeName}")); } - TypeName parsed = TypeName.Parse($"{typeName}, {assemblyName}"); - return UseResolver(parsed); - } + FullyQualifiedTypeName fullName = new(typeName, assemblyName); - [RequiresUnreferencedCode("Calls user-provided method that resolves types from names.")] - [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] - private Type UseResolver(TypeName typeName) - { - if (_resolver is null) + _cachedTypeNames ??= []; + if (!_cachedTypeNames.TryGetValue(fullName, out TypeName? parsed)) { - throw new NotSupportedException(string.Format( - SR.ClipboardOrDragDrop_UseTypedAPI, - typeName.AssemblyQualifiedName)); + parsed = TypeName.Parse(fullName.ToString()); + _cachedTypeNames.Add(fullName, parsed); } - Type resolved = _resolver(typeName) - ?? throw new NotSupportedException(string.Format( - SR.ClipboardOrDragDrop_TypedAPI_InvalidResolver, - typeName.AssemblyQualifiedName)); - - _userTypes.Add(typeName, resolved); - return resolved; - } - - private Type? GetCachedType(string assemblyName, string fullTypeName, TypeName? typeName) - { - InitializeCommonTypes(); - - typeName ??= TypeName.Parse($"{fullTypeName}, {assemblyName}"); - - return s_knownTypes.TryGetValue(typeName, out Type? type) - ? type - : _userTypes.TryGetValue(typeName, out type) ? type : null; + return BindToType(parsed); } - [RequiresUnreferencedCode("Calls System.Reflection.Assembly.GetType(String)")] + [RequiresUnreferencedCode("Calls user-provided method that resolves types from names.")] [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] - public Type GetType(TypeName typeName) + public Type BindToType(TypeName typeName) { - typeName.OrThrowIfNull(); + ArgumentNullException.ThrowIfNull(typeName); - if (typeName.AssemblyName is not AssemblyNameInfo info - || info.FullName is not string fullName) + if (!TNrbfSerializer.TryBindToType(typeName, out Type? type)) { - throw new ArgumentException(message: null, nameof(typeName)); + type = _resolver(typeName) ?? throw new NotSupportedException(string.Format( + _hasCustomResolver ? SR.ClipboardOrDragDrop_TypedAPI_InvalidResolver : SR.ClipboardOrDragDrop_UseTypedAPI, + typeName.AssemblyQualifiedName)); } - return GetCachedType(fullName, typeName.FullName, typeName) is Type type ? type : UseResolver(typeName); + return type; } } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.NativeToRuntimeAdapter.cs b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.NativeToRuntimeAdapter.cs index f00ecd1dd2f..6214e9e80fe 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.NativeToRuntimeAdapter.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.NativeToRuntimeAdapter.cs @@ -5,16 +5,15 @@ using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using Com = Windows.Win32.System.Com; -using ComTypes = System.Runtime.InteropServices.ComTypes; -namespace System.Windows.Forms; +namespace System.Private.Windows.Ole; -internal unsafe partial class Composition +internal unsafe partial class Composition { /// - /// Maps native pointer to . + /// Maps native pointer to the .NET Runtime . /// - private class NativeToRuntimeAdapter : ComTypes.IDataObject + private class NativeToRuntimeAdapter : IDataObject { private readonly AgileComPointer _nativeDataObject; diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.NativeToWinFormsAdapter.cs b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.NativeToWinFormsAdapter.cs index 55cdaae90a2..2fcd6c20990 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.NativeToWinFormsAdapter.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.NativeToWinFormsAdapter.cs @@ -2,25 +2,24 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Drawing; -using System.Private.Windows.Ole; using System.Reflection.Metadata; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Text; using Com = Windows.Win32.System.Com; -namespace System.Windows.Forms; +namespace System.Private.Windows.Ole; -internal unsafe partial class Composition +internal unsafe partial class Composition { /// /// Maps native pointer to . /// - private unsafe class NativeToWinFormsAdapter : IDataObjectInternal, Com.IDataObject.Interface + private unsafe class NativeToManagedAdapter : IDataObjectInternal, Com.IDataObject.Interface { private readonly AgileComPointer _nativeDataObject; - public NativeToWinFormsAdapter(Com.IDataObject* dataObject) + public NativeToManagedAdapter(Com.IDataObject* dataObject) { #if DEBUG _nativeDataObject = new(dataObject, takeOwnership: true, trackDisposal: false); @@ -92,9 +91,7 @@ public HRESULT SetData(Com.FORMATETC* pformatetc, Com.STGMEDIUM* pmedium, BOOL f /// private static bool TryGetDataFromHGLOBAL( HGLOBAL hglobal, - string format, - Func? resolver, - bool legacyMode, + ref readonly DataRequest request, [NotNullWhen(true)] out T? data) { data = default; @@ -103,7 +100,7 @@ private static bool TryGetDataFromHGLOBAL( return false; } - object? value = format switch + object? value = request.Format switch { DataFormatNames.Text or DataFormatNames.Rtf or DataFormatNames.OemText => ReadStringFromHGLOBAL(hglobal, unicode: false), @@ -112,7 +109,7 @@ private static bool TryGetDataFromHGLOBAL( DataFormatNames.FileDrop => ReadFileListFromHDROP((HDROP)(nint)hglobal), DataFormatNames.FileNameAnsi => new string[] { ReadStringFromHGLOBAL(hglobal, unicode: false) }, DataFormatNames.FileNameUnicode => new string[] { ReadStringFromHGLOBAL(hglobal, unicode: true) }, - _ => ReadObjectOrStreamFromHGLOBAL(hglobal, DataObject.RestrictDeserializationToSafeTypes(format), resolver, legacyMode) + _ => ReadObjectOrStreamFromHGLOBAL(hglobal, in request) }; if (value is T t) @@ -125,16 +122,14 @@ private static bool TryGetDataFromHGLOBAL( static object? ReadObjectOrStreamFromHGLOBAL( HGLOBAL hglobal, - bool restrictDeserialization, - Func? resolver, - bool legacyMode) + ref readonly DataRequest request) { MemoryStream stream = ReadByteStreamFromHGLOBAL(hglobal, out bool isSerializedObject); return !isSerializedObject - ? stream - : restrictDeserialization - ? BinaryFormatUtilities.ReadRestrictedObjectFromStream(stream, resolver, legacyMode) - : BinaryFormatUtilities.ReadObjectFromStream(stream, resolver, legacyMode); + ? stream + : DataFormatNames.RestrictDeserializationToSafeTypes(request.Format) + ? BinaryFormatUtilities.ReadRestrictedObjectFromStream(stream, in request) + : BinaryFormatUtilities.ReadObjectFromStream(stream, in request); } } @@ -250,9 +245,7 @@ private static unsafe string ReadUtf8StringFromHGLOBAL(HGLOBAL hglobal) /// is deserialization failed. private static bool TryGetObjectFromDataObject( Com.IDataObject* dataObject, - string format, - Func? resolver, - bool legacyMode, + ref readonly DataRequest request, out bool doNotContinue, [NotNullWhen(true)] out T? data) { @@ -264,20 +257,20 @@ private static bool TryGetObjectFromDataObject( { // Try to get the data as a bitmap first. if ((typeof(Bitmap) == typeof(T) || typeof(Image) == typeof(T)) - && TryGetBitmapData(dataObject, format, out Bitmap? bitmap)) + && TryGetBitmapData(dataObject, request.Format, out Bitmap? bitmap)) { data = (T)(object)bitmap; return true; } - result = TryGetHGLOBALData(dataObject, format, resolver, legacyMode, out doNotContinue, out data); + result = TryGetHGLOBALData(dataObject, in request, out doNotContinue, out data); if (!result && !doNotContinue) { // Lastly check to see if the data is an IStream. - result = TryGetIStreamData(dataObject, format, resolver, legacyMode, out data); + result = TryGetIStreamData(dataObject, in request, out data); } } - catch (Exception e) when (legacyMode || e is not NotSupportedException) + catch (Exception e) when (request.UntypedRequest || e is not NotSupportedException) { Debug.Fail(e.ToString()); } @@ -287,9 +280,7 @@ private static bool TryGetObjectFromDataObject( private static bool TryGetHGLOBALData( Com.IDataObject* dataObject, - string format, - Func? resolver, - bool legacyMode, + ref readonly DataRequest request, out bool doNotContinue, [NotNullWhen(true)] out T? data) { @@ -298,7 +289,7 @@ private static bool TryGetHGLOBALData( Com.FORMATETC formatetc = new() { - cfFormat = (ushort)DataFormats.GetFormat(format).Id, + cfFormat = (ushort)DataFormatsCore.GetOrAddFormat(request.Format).Id, dwAspect = (uint)Com.DVASPECT.DVASPECT_CONTENT, lindex = -1, tymed = (uint)Com.TYMED.TYMED_HGLOBAL @@ -327,7 +318,7 @@ private static bool TryGetHGLOBALData( { if (medium.tymed == Com.TYMED.TYMED_HGLOBAL && !medium.hGlobal.IsNull && hr != HRESULT.COR_E_SERIALIZATION) { - result = TryGetDataFromHGLOBAL(medium.hGlobal, format, resolver, legacyMode, out data); + result = TryGetDataFromHGLOBAL(medium.hGlobal, in request, out data); } } catch (RestrictedTypeDeserializationException) @@ -336,7 +327,7 @@ private static bool TryGetHGLOBALData( data = default; doNotContinue = true; } - catch (Exception ex) when (legacyMode || ex is not NotSupportedException) + catch (Exception ex) when (request.UntypedRequest || ex is not NotSupportedException) { Debug.WriteLine(ex.ToString()); } @@ -350,15 +341,13 @@ private static bool TryGetHGLOBALData( private static unsafe bool TryGetIStreamData( Com.IDataObject* dataObject, - string format, - Func? resolver, - bool legacyMode, + ref readonly DataRequest request, [NotNullWhen(true)] out T? data) { data = default; Com.FORMATETC formatEtc = new() { - cfFormat = (ushort)DataFormats.GetFormat(format).Id, + cfFormat = (ushort)DataFormatsCore.GetOrAddFormat(request.Format).Id, dwAspect = (uint)Com.DVASPECT.DVASPECT_CONTENT, lindex = -1, tymed = (uint)Com.TYMED.TYMED_ISTREAM @@ -395,7 +384,7 @@ private static unsafe bool TryGetIStreamData( pStream.Value->Read((byte*)ptr, (uint)sstg.cbSize, null); PInvokeCore.GlobalUnlock(hglobal); - return TryGetDataFromHGLOBAL(hglobal, format, resolver, legacyMode, out data); + return TryGetDataFromHGLOBAL(hglobal, in request, out data); } finally { @@ -418,7 +407,7 @@ private static bool TryGetBitmapData(Com.IDataObject* dataObject, string format, Com.FORMATETC formatEtc = new() { - cfFormat = (ushort)DataFormats.GetFormat(format).Id, + cfFormat = (ushort)DataFormatsCore.GetOrAddFormat(format).Id, dwAspect = (uint)Com.DVASPECT.DVASPECT_CONTENT, lindex = -1, tymed = (uint)Com.TYMED.TYMED_GDI @@ -460,7 +449,7 @@ private static bool TryGetBitmapData(Com.IDataObject* dataObject, string format, private static void ThrowIfFormatAndTypeRequireResolver(string format) { // Restricted format is either read directly from the HGLOBAL or serialization record is read manually. - if (!DataObject.IsRestrictedFormat(format) + if (!DataFormatNames.IsRestrictedFormat(format) // This check is a convenience for simple usages if TryGetData APIs that don't take the resolver. && IsUnboundedType()) { @@ -483,25 +472,22 @@ static bool IsUnboundedType() } private bool TryGetDataInternal( - string format, - Func? resolver, - bool autoConvert, - bool legacyMode, + ref readonly DataRequest request, [NotNullWhen(true)] out T? data) { data = default; - if (!legacyMode && resolver is null) + if (!request.UntypedRequest && request.Resolver is null) { // DataObject.GetData methods do not validate format string, but the typed methods do. // This validation is specific to the WinForms DataObject implementation, it's not executed for // overridden methods. - ThrowIfFormatAndTypeRequireResolver(format); + ThrowIfFormatAndTypeRequireResolver(request.Format); } using var nativeDataObject = _nativeDataObject.GetInterface(); bool result = TryGetObjectFromDataObject( - nativeDataObject, format, resolver, legacyMode, out bool doNotContinue, out data); + nativeDataObject, in request, out bool doNotContinue, out data); if (doNotContinue) { @@ -512,27 +498,32 @@ private bool TryGetDataInternal( return false; } - if (result || !autoConvert) + if (result || !request.AutoConvert) { return result; } List mappedFormats = []; - DataFormatNames.AddMappedFormats(format, mappedFormats); + DataFormatNames.AddMappedFormats(request.Format, mappedFormats); // Try to find a mapped format that works better. foreach (string mappedFormat in mappedFormats) { - if (format.Equals(mappedFormat)) + if (request.Format.Equals(mappedFormat)) { continue; } + DataRequest mappedRequest = new(mappedFormat) + { + AutoConvert = request.AutoConvert, + Resolver = request.Resolver, + UntypedRequest = request.UntypedRequest + }; + result = TryGetObjectFromDataObject( nativeDataObject, - mappedFormat, - resolver, - legacyMode, + in mappedRequest, out doNotContinue, out data); @@ -554,13 +545,20 @@ private bool TryGetDataInternal( #region IDataObject public object? GetData(string format, bool autoConvert) { - TryGetDataInternal(format, resolver: null, autoConvert, legacyMode: true, out object? data); + DataRequest request = new(format) + { + AutoConvert = autoConvert, + Resolver = null, + UntypedRequest = true + }; + + TryGetDataInternal(in request, out object? data); return data; } public object? GetData(string format) => GetData(format, autoConvert: true); - public object? GetData(Type format) => GetData(format.FullName!); - public bool GetDataPresent(Type format) => GetDataPresent(format.FullName!); + public object? GetData(Type format) => GetData(format.FullName.OrThrowIfNull()); + public bool GetDataPresent(Type format) => GetDataPresent(format.FullName.OrThrowIfNull()); public bool GetDataPresent(string format, bool autoConvert) { @@ -609,7 +607,7 @@ public string[] GetFormats(bool autoConvert) while (enumFORMATETC.Value->Next(1, &formatEtc) == HRESULT.S_OK) { - string name = DataFormats.GetFormat(formatEtc.cfFormat).Name; + string name = DataFormatsCore.GetOrAddFormat(formatEtc.cfFormat).Name; distinctFormats.Add(name); if (autoConvert) @@ -636,30 +634,49 @@ public void SetData(object? data) { } string format, Func resolver, bool autoConvert, - [NotNullWhen(true), MaybeNullWhen(false)] out T data) => - TryGetDataInternal(format, resolver, autoConvert, legacyMode: false, out data); + [NotNullWhen(true), MaybeNullWhen(false)] out T data) + { + DataRequest request = new(format) + { + AutoConvert = autoConvert, + Resolver = resolver, + UntypedRequest = false + }; + + return TryGetDataInternal(in request, out data); + } public bool TryGetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>( string format, bool autoConvert, - [NotNullWhen(true), MaybeNullWhen(false)] out T data) => - TryGetDataInternal(format, resolver: null!, autoConvert, legacyMode: false, out data); + [NotNullWhen(true), MaybeNullWhen(false)] out T data) + { + DataRequest request = new(format) + { + AutoConvert = autoConvert, + }; + + return TryGetDataInternal(in request, out data); + } public bool TryGetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>( string format, [NotNullWhen(true), MaybeNullWhen(false)] out T data) => - TryGetDataInternal(format, resolver: null!, autoConvert: true, legacyMode: false, out data); + TryGetData(format, autoConvert: true, out data); public bool TryGetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>( [NotNullWhen(true), MaybeNullWhen(false)] out T data) => - TryGetDataInternal(typeof(T).FullName!, resolver: null!, autoConvert: true, legacyMode: false, out data); + TryGetData( + typeof(T).FullName.OrThrowIfNull(), + autoConvert: true, + out data); #endregion private bool GetDataPresentInner(string format) { Com.FORMATETC formatEtc = new() { - cfFormat = (ushort)(DataFormats.GetFormat(format).Id), + cfFormat = (ushort)(DataFormatsCore.GetOrAddFormat(format).Id), dwAspect = (uint)Com.DVASPECT.DVASPECT_CONTENT, lindex = -1, tymed = (uint)AllowedTymeds diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.RuntimeToNativeAdapter.cs b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.RuntimeToNativeAdapter.cs index 4010b283a67..eb864c0dcbe 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.RuntimeToNativeAdapter.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.RuntimeToNativeAdapter.cs @@ -3,20 +3,19 @@ using System.Runtime.InteropServices.ComTypes; using Com = Windows.Win32.System.Com; -using ComTypes = System.Runtime.InteropServices.ComTypes; -namespace System.Windows.Forms; +namespace System.Private.Windows.Ole; -internal unsafe partial class Composition +internal unsafe partial class Composition { /// - /// Maps the runtime to the native . + /// Maps the runtime to the native . /// - private class RuntimeToNativeAdapter : Com.IDataObject.Interface, ComTypes.IDataObject, Com.IManagedWrapper + private class RuntimeToNativeAdapter : Com.IDataObject.Interface, IDataObject, Com.IManagedWrapper { - private readonly ComTypes.IDataObject _runtimeDataObject; + private readonly IDataObject _runtimeDataObject; - public RuntimeToNativeAdapter(ComTypes.IDataObject dataObject) => _runtimeDataObject = dataObject; + public RuntimeToNativeAdapter(IDataObject dataObject) => _runtimeDataObject = dataObject; #region ComTypes.IDataObject public int DAdvise(ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection) => _runtimeDataObject.DAdvise(ref pFormatetc, advf, adviseSink, out connection); diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.WinFormsToNativeAdapter.cs b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.WinFormsToNativeAdapter.cs index bf0d3f98e7e..2d61da9788c 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.WinFormsToNativeAdapter.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.WinFormsToNativeAdapter.cs @@ -1,22 +1,19 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Drawing; -using System.Private.Windows.Ole; using System.Runtime.Serialization; using System.Text; using Windows.Win32.System.Com; -using Com = Windows.Win32.System.Com; using ComTypes = System.Runtime.InteropServices.ComTypes; -namespace System.Windows.Forms; +namespace System.Private.Windows.Ole; -internal unsafe partial class Composition +internal unsafe partial class Composition { /// - /// Maps to . + /// Maps to . /// - private unsafe class ManagedToNativeAdapter : Com.IDataObject.Interface, IManagedWrapper + private unsafe class ManagedToNativeAdapter : IDataObject.Interface, IManagedWrapper { private const int DATA_S_SAMEFORMATETC = 0x00040130; @@ -45,9 +42,9 @@ public HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) return HRESULT.E_POINTER; } - if (DragDropHelper.IsInDragLoop(_dataObject)) + if (DragDropHelper.IsInDragLoop(_dataObject)) { - string formatName = DataFormats.GetFormat(pformatetcIn->cfFormat).Name; + string formatName = DataFormatsCore.GetOrAddFormat(pformatetcIn->cfFormat).Name; if (!_dataObject.GetDataPresent(formatName)) { *pmedium = default; @@ -109,7 +106,7 @@ public HRESULT GetDataHere(FORMATETC* pformatetc, STGMEDIUM* pmedium) return HRESULT.DV_E_TYMED; } - string format = DataFormats.GetFormat(pformatetc->cfFormat).Name; + string format = DataFormatsCore.GetOrAddFormat(pformatetc->cfFormat).Name; if (!_dataObject.GetDataPresent(format)) { @@ -125,7 +122,11 @@ public HRESULT GetDataHere(FORMATETC* pformatetc, STGMEDIUM* pmedium) { try { - return SaveDataToHGLOBAL(data, format, ref *pmedium); + HRESULT result = SaveDataToHGLOBAL(data, format, ref *pmedium); + if (result != HRESULT.E_UNEXPECTED) + { + return result; + } } catch (NotSupportedException ex) { @@ -143,50 +144,7 @@ public HRESULT GetDataHere(FORMATETC* pformatetc, STGMEDIUM* pmedium) } } - if (((TYMED)pformatetc->tymed).HasFlag(TYMED.TYMED_GDI)) - { - if (format.Equals(DataFormats.Bitmap) && data is Bitmap bitmap) - { - // Save bitmap - pmedium->u.hBitmap = GetCompatibleBitmap(bitmap); - } - - return HRESULT.S_OK; - } - - return HRESULT.DV_E_TYMED; - - static HBITMAP GetCompatibleBitmap(Bitmap bitmap) - { - using var screenDC = GetDcScope.ScreenDC; - - // GDI+ returns a DIBSECTION based HBITMAP. The clipboard only deals well with bitmaps created using - // CreateCompatibleBitmap(). So, we convert the DIBSECTION into a compatible bitmap. - HBITMAP hbitmap = bitmap.GetHBITMAP(); - - // Create a compatible DC to render the source bitmap. - using CreateDcScope sourceDC = new(screenDC); - using SelectObjectScope sourceBitmapSelection = new(sourceDC, hbitmap); - - // Create a compatible DC and a new compatible bitmap. - using CreateDcScope destinationDC = new(screenDC); - HBITMAP compatibleBitmap = PInvokeCore.CreateCompatibleBitmap(screenDC, bitmap.Size.Width, bitmap.Size.Height); - - // Select the new bitmap into a compatible DC and render the blt the original bitmap. - using SelectObjectScope destinationBitmapSelection = new(destinationDC, compatibleBitmap); - PInvokeCore.BitBlt( - destinationDC, - 0, - 0, - bitmap.Size.Width, - bitmap.Size.Height, - sourceDC, - 0, - 0, - ROP_CODE.SRCCOPY); - - return compatibleBitmap; - } + return TRuntime.GetDataHere(format, data, pformatetc, pmedium); } public HRESULT QueryGetData(FORMATETC* pformatetc) @@ -211,7 +169,7 @@ public HRESULT QueryGetData(FORMATETC* pformatetc) return HRESULT.S_FALSE; } - if (!_dataObject.GetDataPresent(DataFormats.GetFormat(pformatetc->cfFormat).Name)) + if (!_dataObject.GetDataPresent(DataFormatsCore.GetOrAddFormat(pformatetc->cfFormat).Name)) { return HRESULT.DV_E_FORMATETC; } @@ -242,9 +200,10 @@ public HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) return HRESULT.E_POINTER; } - if (DragDropHelper.IsInDragLoopFormat(*pformatetc) || DragDropHelper.IsInDragLoop(_dataObject)) + if (DragDropHelper.IsInDragLoopFormat(*pformatetc) + || DragDropHelper.IsInDragLoop(_dataObject)) { - string formatName = DataFormats.GetFormat(pformatetc->cfFormat).Name; + string formatName = DataFormatsCore.GetOrAddFormat(pformatetc->cfFormat).Name; if (_dataObject.GetDataPresent(formatName) && _dataObject.GetData(formatName) is DragDropFormat dragDropFormat) { dragDropFormat.RefreshData(pformatetc->cfFormat, *pmedium, !fRelease); @@ -269,7 +228,10 @@ public HRESULT EnumFormatEtc(uint dwDirection, IEnumFORMATETC** ppenumFormatEtc) if (dwDirection == (uint)ComTypes.DATADIR.DATADIR_GET) { - *ppenumFormatEtc = ComHelpers.GetComPointer(new FormatEnumerator(_dataObject)); + *ppenumFormatEtc = ComHelpers.GetComPointer(new FormatEnumerator( + _dataObject, + (format) => DataFormatsCore.GetOrAddFormat(format).Id)); + return HRESULT.S_OK; } @@ -317,14 +279,10 @@ DataFormatNames.Text or DataFormatNames.Rtf or DataFormatNames.OemText => SaveStringToHGLOBAL(medium.hGlobal, ((string[])data)[0], unicode: false), DataFormatNames.FileNameUnicode => SaveStringToHGLOBAL(medium.hGlobal, ((string[])data)[0], unicode: true), - DataFormatNames.Dib when data is Image - // GDI+ does not properly handle saving to DIB images. Since the clipboard will take - // an HBITMAP and publish a Dib, we don't need to support this. - => HRESULT.DV_E_TYMED, #pragma warning disable SYSLIB0050 // Type or member is obsolete _ when format == DataFormatNames.Serializable || data is ISerializable || data.GetType().IsSerializable #pragma warning restore - => SaveObjectToHGLOBAL(ref medium.hGlobal, data, DataObject.RestrictDeserializationToSafeTypes(format)), + => SaveObjectToHGLOBAL(ref medium.hGlobal, data, DataFormatNames.RestrictDeserializationToSafeTypes(format)), _ => HRESULT.E_UNEXPECTED }; @@ -334,7 +292,7 @@ private static HRESULT SaveObjectToHGLOBAL(ref HGLOBAL hglobal, object data, boo stream.Write(s_serializedObjectID); // Throws in case of serialization failure. - BinaryFormatUtilities.WriteObjectToStream(stream, data, restrictSerialization); + BinaryFormatUtilities.WriteObjectToStream(stream, data, restrictSerialization); return SaveStreamToHGLOBAL(ref hglobal, stream); } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.cs b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.cs index 772aff04cff..5af5279745b 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.Composition.cs @@ -1,21 +1,22 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Private.Windows.Ole; using System.Reflection.Metadata; -using System.Runtime.InteropServices.ComTypes; -using Com = Windows.Win32.System.Com; +using Windows.Win32.System.Com; using ComTypes = System.Runtime.InteropServices.ComTypes; -namespace System.Windows.Forms; +namespace System.Private.Windows.Ole; /// -/// Contains the logic to move between , , +/// Contains the logic to move between , , /// and calls. /// -internal sealed unsafe partial class Composition : IDataObjectInternal, Com.IDataObject.Interface, ComTypes.IDataObject +internal sealed unsafe partial class Composition + : IDataObjectInternal, IDataObject.Interface, ComTypes.IDataObject + where TDataFormat : IDataFormat + where TRuntime : IRuntime { - private const Com.TYMED AllowedTymeds = Com.TYMED.TYMED_HGLOBAL | Com.TYMED.TYMED_ISTREAM | Com.TYMED.TYMED_GDI; + private const TYMED AllowedTymeds = TYMED.TYMED_HGLOBAL | TYMED.TYMED_ISTREAM | TYMED.TYMED_GDI; // We use this to identify that a stream is actually a serialized object. On read, we don't know if the contents // of a stream were saved "raw" or if the stream is really pointing to a serialized object. If we saved an object, @@ -30,37 +31,37 @@ internal sealed unsafe partial class Composition : IDataObjectInternal, Com.IDat ]; internal IDataObjectInternal ManagedDataObject { get; } - private readonly Com.IDataObject.Interface _nativeDataObject; + private readonly IDataObject.Interface _nativeDataObject; private readonly ComTypes.IDataObject _runtimeDataObject; - private Composition(IDataObjectInternal managedDataObject, Com.IDataObject.Interface nativeDataObject, ComTypes.IDataObject runtimeDataObject) + private Composition(IDataObjectInternal managedDataObject, IDataObject.Interface nativeDataObject, ComTypes.IDataObject runtimeDataObject) { ManagedDataObject = managedDataObject; _nativeDataObject = nativeDataObject; _runtimeDataObject = runtimeDataObject; } - public static Composition CreateFromManagedDataObject(IDataObjectInternal managedDataObject) + public static Composition CreateFromManagedDataObject(IDataObjectInternal managedDataObject) { ManagedToNativeAdapter winFormsToNative = new(managedDataObject); - NativeToRuntimeAdapter nativeToRuntime = new(ComHelpers.GetComPointer(winFormsToNative)); + NativeToRuntimeAdapter nativeToRuntime = new(ComHelpers.GetComPointer(winFormsToNative)); return new(managedDataObject, winFormsToNative, nativeToRuntime); } - public static Composition CreateFromNativeDataObject(Com.IDataObject* nativeDataObject) + public static Composition CreateFromNativeDataObject(IDataObject* nativeDataObject) { // Add ref so each adapter can take ownership of the native data object. nativeDataObject->AddRef(); nativeDataObject->AddRef(); - NativeToWinFormsAdapter nativeToWinForms = new(nativeDataObject); + NativeToManagedAdapter nativeToWinForms = new(nativeDataObject); NativeToRuntimeAdapter nativeToRuntime = new(nativeDataObject); return new(nativeToWinForms, nativeToWinForms, nativeToRuntime); } - public static Composition CreateFromRuntimeDataObject(ComTypes.IDataObject runtimeDataObject) + public static Composition CreateFromRuntimeDataObject(ComTypes.IDataObject runtimeDataObject) { RuntimeToNativeAdapter runtimeToNative = new(runtimeDataObject); - NativeToWinFormsAdapter nativeToWinForms = new(ComHelpers.GetComPointer(runtimeToNative)); + NativeToManagedAdapter nativeToWinForms = new(ComHelpers.GetComPointer(runtimeToNative)); return new(nativeToWinForms, runtimeToNative, runtimeDataObject); } @@ -100,30 +101,30 @@ public static Composition CreateFromRuntimeDataObject(ComTypes.IDataObject runti public bool TryGetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>( [NotNullWhen(true), MaybeNullWhen(false)] out T data) => - ManagedDataObject.TryGetData(typeof(T).FullName!, out data); + ManagedDataObject.TryGetData(typeof(T).FullName.OrThrowIfNull(), out data); #endregion #region Com.IDataObject.Interface - public HRESULT DAdvise(Com.FORMATETC* pformatetc, uint advf, Com.IAdviseSink* pAdvSink, uint* pdwConnection) => _nativeDataObject.DAdvise(pformatetc, advf, pAdvSink, pdwConnection); + public HRESULT DAdvise(FORMATETC* pformatetc, uint advf, IAdviseSink* pAdvSink, uint* pdwConnection) => _nativeDataObject.DAdvise(pformatetc, advf, pAdvSink, pdwConnection); public HRESULT DUnadvise(uint dwConnection) => _nativeDataObject.DUnadvise(dwConnection); - public HRESULT EnumDAdvise(Com.IEnumSTATDATA** ppenumAdvise) => _nativeDataObject.EnumDAdvise(ppenumAdvise); - public HRESULT EnumFormatEtc(uint dwDirection, Com.IEnumFORMATETC** ppenumFormatEtc) => _nativeDataObject.EnumFormatEtc(dwDirection, ppenumFormatEtc); - public HRESULT GetCanonicalFormatEtc(Com.FORMATETC* pformatectIn, Com.FORMATETC* pformatetcOut) => _nativeDataObject.GetCanonicalFormatEtc(pformatectIn, pformatetcOut); - public HRESULT GetData(Com.FORMATETC* pformatetcIn, Com.STGMEDIUM* pmedium) => _nativeDataObject.GetData(pformatetcIn, pmedium); - public HRESULT GetDataHere(Com.FORMATETC* pformatetc, Com.STGMEDIUM* pmedium) => _nativeDataObject.GetDataHere(pformatetc, pmedium); - public HRESULT QueryGetData(Com.FORMATETC* pformatetc) => _nativeDataObject.QueryGetData(pformatetc); - public HRESULT SetData(Com.FORMATETC* pformatetc, Com.STGMEDIUM* pmedium, BOOL fRelease) => _nativeDataObject.SetData(pformatetc, pmedium, fRelease); + public HRESULT EnumDAdvise(IEnumSTATDATA** ppenumAdvise) => _nativeDataObject.EnumDAdvise(ppenumAdvise); + public HRESULT EnumFormatEtc(uint dwDirection, IEnumFORMATETC** ppenumFormatEtc) => _nativeDataObject.EnumFormatEtc(dwDirection, ppenumFormatEtc); + public HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) => _nativeDataObject.GetCanonicalFormatEtc(pformatectIn, pformatetcOut); + public HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) => _nativeDataObject.GetData(pformatetcIn, pmedium); + public HRESULT GetDataHere(FORMATETC* pformatetc, STGMEDIUM* pmedium) => _nativeDataObject.GetDataHere(pformatetc, pmedium); + public HRESULT QueryGetData(FORMATETC* pformatetc) => _nativeDataObject.QueryGetData(pformatetc); + public HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) => _nativeDataObject.SetData(pformatetc, pmedium, fRelease); #endregion #region ComTypes.IDataObject.Interface - public int DAdvise(ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection) => _runtimeDataObject.DAdvise(ref pFormatetc, advf, adviseSink, out connection); + public int DAdvise(ref ComTypes.FORMATETC pFormatetc, ComTypes.ADVF advf, ComTypes.IAdviseSink adviseSink, out int connection) => _runtimeDataObject.DAdvise(ref pFormatetc, advf, adviseSink, out connection); public void DUnadvise(int connection) => _runtimeDataObject.DUnadvise(connection); - public int EnumDAdvise(out IEnumSTATDATA? enumAdvise) => _runtimeDataObject.EnumDAdvise(out enumAdvise); - public IEnumFORMATETC EnumFormatEtc(DATADIR direction) => _runtimeDataObject.EnumFormatEtc(direction); - public int GetCanonicalFormatEtc(ref FORMATETC formatIn, out FORMATETC formatOut) => _runtimeDataObject.GetCanonicalFormatEtc(ref formatIn, out formatOut); - public void GetData(ref FORMATETC format, out STGMEDIUM medium) => _runtimeDataObject.GetData(ref format, out medium); - public void GetDataHere(ref FORMATETC format, ref STGMEDIUM medium) => _runtimeDataObject.GetDataHere(ref format, ref medium); - public int QueryGetData(ref FORMATETC format) => _runtimeDataObject.QueryGetData(ref format); - public void SetData(ref FORMATETC formatIn, ref STGMEDIUM medium, bool release) => _runtimeDataObject.SetData(ref formatIn, ref medium, release); + public int EnumDAdvise(out ComTypes.IEnumSTATDATA? enumAdvise) => _runtimeDataObject.EnumDAdvise(out enumAdvise); + public ComTypes.IEnumFORMATETC EnumFormatEtc(ComTypes.DATADIR direction) => _runtimeDataObject.EnumFormatEtc(direction); + public int GetCanonicalFormatEtc(ref ComTypes.FORMATETC formatIn, out ComTypes.FORMATETC formatOut) => _runtimeDataObject.GetCanonicalFormatEtc(ref formatIn, out formatOut); + public void GetData(ref ComTypes.FORMATETC format, out ComTypes.STGMEDIUM medium) => _runtimeDataObject.GetData(ref format, out medium); + public void GetDataHere(ref ComTypes.FORMATETC format, ref ComTypes.STGMEDIUM medium) => _runtimeDataObject.GetDataHere(ref format, ref medium); + public int QueryGetData(ref ComTypes.FORMATETC format) => _runtimeDataObject.QueryGetData(ref format); + public void SetData(ref ComTypes.FORMATETC formatIn, ref ComTypes.STGMEDIUM medium, bool release) => _runtimeDataObject.SetData(ref formatIn, ref medium, release); #endregion } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.FormatEnumerator.cs b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.FormatEnumerator.cs index e1dd2ca4b77..784e9db9975 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.FormatEnumerator.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.FormatEnumerator.cs @@ -1,26 +1,22 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Private.Windows.Ole; using System.Runtime.CompilerServices; using Windows.Win32.System.Com; using ComTypes = System.Runtime.InteropServices.ComTypes; -namespace System.Windows.Forms; +namespace System.Private.Windows.Ole; /// /// Part of IComDataObject, used to interop with OLE. /// internal unsafe class FormatEnumerator : ComTypes.IEnumFORMATETC, IEnumFORMATETC.Interface, IManagedWrapper { + // Want to keep a reference to the data object to ensure it's not collected. private readonly IDataObjectInternal _dataObject; private readonly List _formats = []; private int _current; - public FormatEnumerator(IDataObjectInternal dataObject) : this(dataObject, dataObject.GetFormats()) - { - } - private FormatEnumerator(FormatEnumerator source) { _dataObject = source._dataObject; @@ -28,12 +24,13 @@ private FormatEnumerator(FormatEnumerator source) _formats.AddRange(source._formats); } - public FormatEnumerator(IDataObjectInternal dataObject, string[]? formats) + public FormatEnumerator(IDataObjectInternal dataObject, Func getFormatId) { _dataObject = dataObject; - - if (formats is null) + if (dataObject.GetFormats() is not string[] formats) { + // This can happen with user data objects. + Debug.WriteLine("IDataObject.GetFormats returned null."); return; } @@ -42,11 +39,11 @@ public FormatEnumerator(IDataObjectInternal dataObject, string[]? formats) string format = formats[i]; ComTypes.FORMATETC temp = new() { - cfFormat = (short)(ushort)DataFormats.GetFormat(format).Id, + cfFormat = (short)(ushort)getFormatId(format), dwAspect = ComTypes.DVASPECT.DVASPECT_CONTENT, ptd = 0, lindex = -1, - tymed = format.Equals(DataFormats.Bitmap) ? ComTypes.TYMED.TYMED_GDI : ComTypes.TYMED.TYMED_HGLOBAL + tymed = format.Equals(DataFormatNames.Bitmap) ? ComTypes.TYMED.TYMED_GDI : ComTypes.TYMED.TYMED_HGLOBAL }; _formats.Add(temp); diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.cs index 6004f5d3ea4..0f98f701971 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.cs @@ -10,6 +10,7 @@ using Com = Windows.Win32.System.Com; using ComTypes = System.Runtime.InteropServices.ComTypes; using System.Private.Windows.Ole; +using System.Windows.Forms.Nrbf; namespace System.Windows.Forms; @@ -20,24 +21,28 @@ namespace System.Windows.Forms; public unsafe partial class DataObject : ITypedDataObject, IDataObjectInternal, + // Built-in COM interop chooses the first interface that implements an IID, + // we want the CsWin32 to be chosen over System.Runtime.InteropServices.ComTypes + // so it must come first. Com.IDataObject.Interface, ComTypes.IDataObject, - Com.IManagedWrapper + Com.IManagedWrapper, + IComVisibleDataObject { - private readonly Composition _innerData; + private readonly Composition _innerData; /// /// Initializes a new instance of the class, with the raw /// and the managed data object the raw pointer is associated with. /// /// - internal DataObject(Com.IDataObject* data) => _innerData = Composition.CreateFromNativeDataObject(data); + internal DataObject(Com.IDataObject* data) => _innerData = Composition.CreateFromNativeDataObject(data); /// /// Initializes a new instance of the class, which can store arbitrary data. /// /// - public DataObject() => _innerData = Composition.CreateFromManagedDataObject(new DataStore()); + public DataObject() => _innerData = Composition.CreateFromManagedDataObject(new DataStore()); /// /// Initializes a new instance of the class, containing the specified data. @@ -55,19 +60,19 @@ public DataObject(object data) { if (data is IDataObjectInternal internalDataObject) { - _innerData = Composition.CreateFromManagedDataObject(internalDataObject); + _innerData = Composition.CreateFromManagedDataObject(internalDataObject); } else if (data is IDataObject iDataObject) { - _innerData = Composition.CreateFromManagedDataObject(new DataObjectAdapter(iDataObject)); + _innerData = Composition.CreateFromManagedDataObject(new DataObjectAdapter(iDataObject)); } else if (data is ComTypes.IDataObject comDataObject) { - _innerData = Composition.CreateFromRuntimeDataObject(comDataObject); + _innerData = Composition.CreateFromRuntimeDataObject(comDataObject); } else { - _innerData = Composition.CreateFromManagedDataObject(new DataStore()); + _innerData = Composition.CreateFromManagedDataObject(new DataStore()); SetData(data); } } @@ -159,53 +164,11 @@ private static object TryJsonSerialize(string format, T data) throw new InvalidOperationException(string.Format(SR.ClipboardOrDragDrop_CannotJsonSerializeDataObject, nameof(SetData))); } - return IsRestrictedFormat(format) || TypeBinder.IsKnownType() + return DataFormatNames.IsRestrictedFormat(format) || WinFormsNrbfSerializer.IsSupportedType() ? data : new JsonData() { JsonBytes = JsonSerializer.SerializeToUtf8Bytes(data) }; } - /// - /// Check if the is one of the restricted formats, which formats that - /// correspond to primitives or are pre-defined in the OS such as strings, bitmaps, and OLE types. - /// - internal static bool IsRestrictedFormat(string format) => RestrictDeserializationToSafeTypes(format) - || format is DataFormatNames.Text - or DataFormatNames.UnicodeText - or DataFormatNames.Rtf - or DataFormatNames.Html - or DataFormatNames.OemText - or DataFormatNames.FileDrop - or DataFormatNames.FileNameAnsi - or DataFormatNames.FileNameUnicode; - - /// - /// We are restricting binary serialization and deserialization of formats that represent strings, bitmaps or OLE types. - /// - /// format name - /// - serialize only safe types, strings or bitmaps. - /// - /// - /// These formats are also restricted in WPF - /// https://github.com/dotnet/wpf/blob/db1ae73aae0e043326e2303b0820d361de04e751/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/dataobject.cs#L2801 - /// - /// - internal static bool RestrictDeserializationToSafeTypes(string format) => - format is DataFormatNames.String - or DataFormatNames.BinaryFormatBitmap - or DataFormatNames.Csv - or DataFormatNames.Dib - or DataFormatNames.Dif - or DataFormatNames.Locale - or DataFormatNames.PenData - or DataFormatNames.Riff - or DataFormatNames.SymbolicLink - or DataFormatNames.Tiff - or DataFormatNames.WaveAudio - or DataFormatNames.Bitmap - or DataFormatNames.Emf - or DataFormatNames.Palette - or DataFormatNames.Wmf; - #region IDataObject [Obsolete( Obsoletions.DataObjectGetDataMessage, diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DragDropEffects.cs b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DragDropEffects.cs index c7241de0eae..5122b07f5e4 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DragDropEffects.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DragDropEffects.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Windows.Win32.System.Ole; + namespace System.Windows.Forms; [Flags] @@ -9,27 +11,27 @@ public enum DragDropEffects /// /// The drop target does not accept the data. /// - None = 0x00000000, + None = (int)DROPEFFECT.DROPEFFECT_NONE, /// /// The data is copied to the drop target. /// - Copy = 0x00000001, + Copy = (int)DROPEFFECT.DROPEFFECT_COPY, /// /// The data from the drag source is moved to the drop target. /// - Move = 0x00000002, + Move = (int)DROPEFFECT.DROPEFFECT_MOVE, /// /// The data from the drag source is linked to the drop target. /// - Link = 0x00000004, + Link = (int)DROPEFFECT.DROPEFFECT_LINK, /// /// Scrolling is about to start or is currently occurring in the drop target. /// - Scroll = unchecked((int)0x80000000), + Scroll = unchecked((int)DROPEFFECT.DROPEFFECT_SCROLL), /// /// The data is copied, removed from the drag source, and scrolled in the diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DragDropFormat.cs b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DragDropFormat.cs index a913dbbd211..e8b388b051c 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DragDropFormat.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DragDropFormat.cs @@ -5,7 +5,7 @@ using Windows.Win32.System.Ole; using Windows.Win32.System.Com; -namespace System.Windows.Forms; +namespace System.Private.Windows.Ole; /// /// Represents a private format used for data transfer by the drag-and-drop helpers. diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DragDropHelper.cs b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DragDropHelper.cs index 0638726790c..b20aab8bff0 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DragDropHelper.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DragDropHelper.cs @@ -2,93 +2,59 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; -using System.Drawing; +using System.Private.Windows.Graphics; using System.Runtime.InteropServices; using Windows.Win32.System.Com; using Windows.Win32.System.Ole; using static Windows.Win32.System.Memory.GLOBAL_ALLOC_FLAGS; -using Com = Windows.Win32.System.Com; -using ComTypes = System.Runtime.InteropServices.ComTypes; -using IComDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; -using PrivateOle = System.Private.Windows.Ole; -namespace System.Windows.Forms; +namespace System.Private.Windows.Ole; /// /// Helper class for drop targets to display the drag image while the cursor is over the target window and allows the /// application to specify the drag image bitmap that will be displayed during a drag-and-drop operation. /// -internal static unsafe class DragDropHelper +/// +/// This class effectively requires the data object on events to be an instance of DataObject +/// (via ) currently. +/// +internal static unsafe class DragDropHelper + where TOleServices : IOleServices + where TDataFormat : IDataFormat { - /// - /// A format used internally by the drag image manager. - /// - internal const string DRAGCONTEXT = "DragContext"; - - /// - /// A format that contains the drag image bottom-up device-independent bitmap bits. - /// - internal const string DRAGIMAGEBITS = "DragImageBits"; - - /// - /// A format that contains the value passed to - /// and controls whether to allow text specified in to be displayed on the drag image. - /// - internal const string DRAGSOURCEHELPERFLAGS = "DragSourceHelperFlags"; - - /// - /// A format used to identify an object's drag image window so that it's visual information can be updated dynamically. - /// - internal const string DRAGWINDOW = "DragWindow"; - - /// - /// A format that is non-zero if the drop target supports drag images. - /// - internal const string ISSHOWINGLAYERED = "IsShowingLayered"; - - /// - /// A format that is non-zero if the drop target supports drop description text. - /// - internal const string ISSHOWINGTEXT = "IsShowingText"; - - /// - /// A format that is non-zero if the drag image is a layered window with a size of 96x96. - /// - internal const string USINGDEFAULTDRAGIMAGE = "UsingDefaultDragImage"; - /// /// Sets the drop object image and accompanying text back to the default. /// - public static void ClearDropDescription(IDataObject? dataObject) + public static void ClearDropDescription(IComVisibleDataObject? dataObject) { - if (dataObject is not IComDataObject comDataObject) + if (dataObject is null) { return; } - SetDropDescription(comDataObject, DropImageType.Invalid, string.Empty, string.Empty); + SetDropDescription(dataObject, DROPIMAGETYPE.DROPIMAGE_INVALID, string.Empty, string.Empty); } /// /// Notifies the drag-image manager that the drop target has been entered, and provides it with the information /// needed to display the drag image. /// - public static void DragEnter(HWND targetWindowHandle, DragEventArgs e) + public static void DragEnter(HWND targetWindowHandle, IDragEvent e) { - if (e.Data is not IComDataObject dataObject) + if (e.DataObject is not IComVisibleDataObject dataObject) { return; } Point point = new(e.X, e.Y); - DragEnter(targetWindowHandle, dataObject, ref point, (DROPEFFECT)(uint)e.Effect); + DragEnter(targetWindowHandle, dataObject, ref point, e.Effect); } /// /// Notifies the drag-image manager that the drop target has been entered, and provides it with the information /// needed to display the drag image. /// - public static void DragEnter(HWND targetWindowHandle, IComDataObject dataObject, ref Point point, DROPEFFECT effect) + public static void DragEnter(HWND targetWindowHandle, IComVisibleDataObject dataObject, ref Point point, DROPEFFECT effect) { using ComScope dropTargetHelper = new(null); if (!TryGetDragDropHelper(dropTargetHelper)) @@ -96,7 +62,7 @@ public static void DragEnter(HWND targetWindowHandle, IComDataObject dataObject, return; } - using var dataObjectScope = ComHelpers.GetComScope(dataObject); + using var dataObjectScope = ComHelpers.GetComScope(dataObject); dropTargetHelper.Value->DragEnter(targetWindowHandle, dataObjectScope, in point, effect); } @@ -118,7 +84,7 @@ public static void DragLeave() /// Notifies the drag-image manager that the cursor position has changed /// and provides it with the information needed to display the drag image. /// - public static void DragOver(DragEventArgs e) + public static void DragOver(IDragEvent e) { using ComScope dropTargetHelper = new(null); if (!TryGetDragDropHelper(dropTargetHelper)) @@ -127,75 +93,37 @@ public static void DragOver(DragEventArgs e) } Point point = new(e.X, e.Y); - dropTargetHelper.Value->DragOver(in point, (DROPEFFECT)(uint)e.Effect); + dropTargetHelper.Value->DragOver(&point, (DROPEFFECT)(uint)e.Effect); } /// /// Notifies the drag-image manager that the object has been dropped, and provides it with the information needed /// to display the drag image. /// - public static void Drop(DragEventArgs e) + public static void Drop(IDragEvent e) { using ComScope dropTargetHelper = new(null); - if (!TryGetDragDropHelper(dropTargetHelper) || e.Data is not IComDataObject dataObject) + if (!TryGetDragDropHelper(dropTargetHelper) || e.DataObject is not IDataObject.Interface dataObject) { return; } Point point = new(e.X, e.Y); - using var dataObjectScope = ComHelpers.GetComScope(dataObject); + using var dataObjectScope = ComHelpers.GetComScope(dataObject); dropTargetHelper.Value->Drop(dataObjectScope, in point, (DROPEFFECT)(uint)e.Effect); } - /// - /// Gets boolean formats from a data object used to set and display drag images and drop descriptions. - /// - private static unsafe bool GetBooleanFormat(IComDataObject dataObject, string format) - { - ArgumentNullException.ThrowIfNull(dataObject); - ArgumentException.ThrowIfNullOrEmpty(format); - - ComTypes.STGMEDIUM medium = default; - - try - { - ComTypes.FORMATETC formatEtc = new() - { - cfFormat = (short)PInvokeCore.RegisterClipboardFormat(format), - dwAspect = ComTypes.DVASPECT.DVASPECT_CONTENT, - lindex = -1, - ptd = IntPtr.Zero, - tymed = ComTypes.TYMED.TYMED_HGLOBAL - }; - - if (dataObject.QueryGetData(ref formatEtc) != (int)HRESULT.S_OK) - { - return false; - } - - medium = default; - dataObject.GetData(ref formatEtc, out medium); - void* basePtr = PInvokeCore.GlobalLock((HGLOBAL)medium.unionmember); - return (basePtr is not null) && (*(BOOL*)basePtr == true); - } - finally - { - PInvokeCore.GlobalUnlock((HGLOBAL)medium.unionmember); - var comMedium = (STGMEDIUM)medium; - PInvokeCore.ReleaseStgMedium(ref comMedium); - } - } - /// /// Determines whether the data object is in a drag loop. /// /// /// if is in a drag-and-drop loop; otherwise . /// - public static unsafe bool IsInDragLoop(PrivateOle.IDataObjectInternal dataObject) + public static unsafe bool IsInDragLoop(IDataObjectInternal dataObject) { ArgumentNullException.ThrowIfNull(dataObject); + // https://learn.microsoft.com/windows/win32/shell/clipboard#cfstr_indragloop if (dataObject.GetDataPresent(PInvokeCore.CFSTR_INDRAGLOOP) && dataObject.GetData(PInvokeCore.CFSTR_INDRAGLOOP) is DragDropFormat dragDropFormat) { @@ -215,14 +143,6 @@ public static unsafe bool IsInDragLoop(PrivateOle.IDataObjectInternal dataObject } } - /// - /// Determines whether the data object is in a drag loop. - /// - /// - /// if is in a drag-and-drop loop; otherwise . - /// - public static bool IsInDragLoop(IComDataObject dataObject) => GetBooleanFormat(dataObject, PInvokeCore.CFSTR_INDRAGLOOP); - /// /// Determines whether the specified format is a drag loop format. /// @@ -231,22 +151,23 @@ public static unsafe bool IsInDragLoop(PrivateOle.IDataObjectInternal dataObject /// public static bool IsInDragLoopFormat(FORMATETC format) { - string formatName = DataFormats.GetFormat(format.cfFormat).Name; - return formatName.Equals(DRAGCONTEXT) || formatName.Equals(DRAGIMAGEBITS) || formatName.Equals(DRAGSOURCEHELPERFLAGS) - || formatName.Equals(DRAGWINDOW) || formatName.Equals(PInvokeCore.CFSTR_DROPDESCRIPTION) || formatName.Equals(PInvokeCore.CFSTR_INDRAGLOOP) - || formatName.Equals(ISSHOWINGLAYERED) || formatName.Equals(ISSHOWINGTEXT) || formatName.Equals(USINGDEFAULTDRAGIMAGE); + string formatName = DataFormatsCore.GetOrAddFormat(format.cfFormat).Name; + return formatName.Equals(DataFormatNames.DragContext) + || formatName.Equals(DataFormatNames.DragImageBits) + || formatName.Equals(DataFormatNames.DragSourceHelperFlags) + || formatName.Equals(DataFormatNames.DragWindow) + || formatName.Equals(PInvokeCore.CFSTR_DROPDESCRIPTION) + || formatName.Equals(PInvokeCore.CFSTR_INDRAGLOOP) + || formatName.Equals(DataFormatNames.IsShowingLayered) + || formatName.Equals(DataFormatNames.IsShowingText) + || formatName.Equals(DataFormatNames.UsingDefaultDragImage); } /// /// Releases formats used by the drag-and-drop helper. /// - public static void ReleaseDragDropFormats(IComDataObject comDataObject) + public static void ReleaseDragDropFormats(IComVisibleDataObject dataObject) { - if (comDataObject is not IDataObject dataObject) - { - return; - } - foreach (string format in dataObject.GetFormats()) { if (dataObject.GetData(format) is DragDropFormat dragDropFormat) @@ -259,43 +180,42 @@ public static void ReleaseDragDropFormats(IComDataObject comDataObject) /// /// Sets boolean formats into a data object used to set and display drag images and drop descriptions. /// - private static unsafe void SetBooleanFormat(IComDataObject dataObject, string format, bool value) + private static unsafe void SetBooleanFormat(IComVisibleDataObject dataObject, string format, bool value) { ArgumentNullException.ThrowIfNull(dataObject); ArgumentException.ThrowIfNullOrEmpty(format); - ComTypes.FORMATETC formatEtc = new() + FORMATETC formatEtc = new() { - cfFormat = (short)PInvokeCore.RegisterClipboardFormat(format), - dwAspect = ComTypes.DVASPECT.DVASPECT_CONTENT, + cfFormat = (ushort)(short)PInvokeCore.RegisterClipboardFormat(format), + dwAspect = (uint)DVASPECT.DVASPECT_CONTENT, lindex = -1, - ptd = IntPtr.Zero, - tymed = ComTypes.TYMED.TYMED_HGLOBAL + ptd = null, + tymed = (uint)TYMED.TYMED_HGLOBAL }; - ComTypes.STGMEDIUM medium = new() + STGMEDIUM medium = new() { pUnkForRelease = null, - tymed = ComTypes.TYMED.TYMED_HGLOBAL, - unionmember = PInvokeCore.GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, (nuint)sizeof(BOOL)) + tymed = TYMED.TYMED_HGLOBAL, + hGlobal = PInvokeCore.GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, (nuint)sizeof(BOOL)) }; - if (medium.unionmember == IntPtr.Zero) + if (medium.hGlobal.IsNull) { throw new Win32Exception(Marshal.GetLastSystemError(), SR.ExternalException); } - void* basePtr = PInvokeCore.GlobalLock((HGLOBAL)medium.unionmember); + void* basePtr = PInvokeCore.GlobalLock(medium.hGlobal); if (basePtr is null) { - PInvokeCore.GlobalFree((HGLOBAL)medium.unionmember); - medium.unionmember = IntPtr.Zero; + PInvokeCore.GlobalFree(medium.hGlobal); throw new Win32Exception(Marshal.GetLastSystemError(), SR.ExternalException); } *(BOOL*)basePtr = value; - PInvokeCore.GlobalUnlock((HGLOBAL)medium.unionmember); - dataObject.SetData(ref formatEtc, ref medium, release: true); + PInvokeCore.GlobalUnlock(medium.hGlobal); + dataObject.SetData(&formatEtc, &medium, true); } /// @@ -308,7 +228,7 @@ private static unsafe void SetBooleanFormat(IComDataObject dataObject, string fo /// with pre-multiplied alpha blending, but this method will multiply it again, doubling the resulting alpha value. /// /// - public static void SetDragImage(IComDataObject dataObject, GiveFeedbackEventArgs e) + public static void SetDragImage(IComVisibleDataObject dataObject, IGiveFeedbackEvent e) { ArgumentNullException.ThrowIfNull(e); SetDragImage(dataObject, e.DragImage, e.CursorOffset, e.UseDefaultDragImage); @@ -324,7 +244,7 @@ public static void SetDragImage(IComDataObject dataObject, GiveFeedbackEventArgs /// with pre-multiplied alpha blending, but this method will multiply it again, doubling the resulting alpha value. /// /// - public static void SetDragImage(IComDataObject dataObject, Bitmap? dragImage, Point cursorOffset, bool usingDefaultDragImage) + public static void SetDragImage(IComVisibleDataObject dataObject, IBitmap? dragImage, Point cursorOffset, bool usingDefaultDragImage) { ArgumentNullException.ThrowIfNull(dataObject); @@ -344,7 +264,7 @@ public static void SetDragImage(IComDataObject dataObject, Bitmap? dragImage, Po SetInDragLoop(dataObject, inDragLoop: true); } - using HBITMAP hbmpDragImage = dragImage is not null ? dragImage.GetHBITMAP() : HBITMAP.Null; + using HBITMAP hbmpDragImage = dragImage is not null ? dragImage.GetHbitmap() : HBITMAP.Null; // The Windows drag image manager will own this bitmap object and free the memory when its finished. Only // call DeleteObject if an exception occurs while initializing. @@ -364,7 +284,7 @@ public static void SetDragImage(IComDataObject dataObject, Bitmap? dragImage, Po return; } - using var dataObjectScope = ComHelpers.GetComScope(dataObject); + using var dataObjectScope = ComHelpers.GetComScope(dataObject); if (dragSourceHelper.Value->InitializeFromBitmap(shDragImage, dataObjectScope).Failed) { return; @@ -379,9 +299,9 @@ public static void SetDragImage(IComDataObject dataObject, Bitmap? dragImage, Po /// /// Sets the drop description into a data object. Describes the image and accompanying text for a drop object. /// - public static void SetDropDescription(DragEventArgs e) + public static void SetDropDescription(IDragEvent e) { - if (e.Data is not IComDataObject dataObject) + if (e.DataObject is not IComVisibleDataObject dataObject) { return; } @@ -402,8 +322,8 @@ public static void SetDropDescription(DragEventArgs e) /// /// public static unsafe void SetDropDescription( - IComDataObject dataObject, - DropImageType dropImageType, + IComVisibleDataObject dataObject, + DROPIMAGETYPE dropImageType, string message, string messageReplacementToken) { @@ -420,40 +340,39 @@ public static unsafe void SetDropDescription( throw new ArgumentOutOfRangeException(nameof(messageReplacementToken)); } - ComTypes.FORMATETC formatEtc = new() + FORMATETC formatEtc = new() { - cfFormat = (short)PInvokeCore.RegisterClipboardFormat(PInvokeCore.CFSTR_DROPDESCRIPTION), - dwAspect = ComTypes.DVASPECT.DVASPECT_CONTENT, + cfFormat = (ushort)(short)PInvokeCore.RegisterClipboardFormat(PInvokeCore.CFSTR_DROPDESCRIPTION), + dwAspect = (uint)DVASPECT.DVASPECT_CONTENT, lindex = -1, - ptd = IntPtr.Zero, - tymed = ComTypes.TYMED.TYMED_HGLOBAL + ptd = null, + tymed = (uint)TYMED.TYMED_HGLOBAL }; - ComTypes.STGMEDIUM medium = new() + STGMEDIUM medium = new() { pUnkForRelease = null, - tymed = ComTypes.TYMED.TYMED_HGLOBAL, - unionmember = PInvokeCore.GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, (uint)sizeof(DROPDESCRIPTION)) + tymed = TYMED.TYMED_HGLOBAL, + hGlobal = PInvokeCore.GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, (nuint)sizeof(DROPDESCRIPTION)) }; - if (medium.unionmember == IntPtr.Zero) + if (medium.hGlobal.IsNull) { throw new Win32Exception(Marshal.GetLastSystemError(), SR.ExternalException); } - void* basePtr = PInvokeCore.GlobalLock((HGLOBAL)medium.unionmember); + void* basePtr = PInvokeCore.GlobalLock(medium.hGlobal); if (basePtr is null) { - PInvokeCore.GlobalFree((HGLOBAL)medium.unionmember); - medium.unionmember = IntPtr.Zero; + PInvokeCore.GlobalFree(medium.hGlobal); throw new Win32Exception(Marshal.GetLastSystemError(), SR.ExternalException); } DROPDESCRIPTION* pDropDescription = (DROPDESCRIPTION*)basePtr; - pDropDescription->type = (DROPIMAGETYPE)dropImageType; + pDropDescription->type = dropImageType; pDropDescription->szMessage = message; pDropDescription->szInsert = messageReplacementToken; - PInvokeCore.GlobalUnlock((HGLOBAL)medium.unionmember); + PInvokeCore.GlobalUnlock(medium.hGlobal); // Set the InShellDragLoop flag to true to facilitate loading and retrieving arbitrary private formats. The // drag-and-drop helper object calls IDataObject::SetData to load private formats--used for cross-process support--into @@ -465,7 +384,7 @@ public static unsafe void SetDropDescription( SetInDragLoop(dataObject, inDragLoop: true); } - dataObject.SetData(ref formatEtc, ref medium, release: true); + dataObject.SetData(&formatEtc, &medium, fRelease: true); SetIsShowingText(dataObject, isShowingText: true); } @@ -477,7 +396,7 @@ public static unsafe void SetDropDescription( /// Used to determine whether the data object is in a drag loop. /// /// - public static void SetInDragLoop(IComDataObject dataObject, bool inDragLoop) + public static void SetInDragLoop(IComVisibleDataObject dataObject, bool inDragLoop) { SetBooleanFormat(dataObject, PInvokeCore.CFSTR_INDRAGLOOP, inDragLoop); @@ -497,8 +416,8 @@ public static void SetInDragLoop(IComDataObject dataObject, bool inDragLoop) /// to true; otherwise the drop description text will not be displayed. /// /// - private static void SetIsShowingText(IComDataObject dataObject, bool isShowingText) - => SetBooleanFormat(dataObject, ISSHOWINGTEXT, isShowingText); + private static void SetIsShowingText(IComVisibleDataObject dataObject, bool isShowingText) + => SetBooleanFormat(dataObject, DataFormatNames.IsShowingText, isShowingText); /// /// Sets the UsingDefaultDragImage format into a data object. @@ -509,8 +428,8 @@ private static void SetIsShowingText(IComDataObject dataObject, bool isShowingTe /// drag image with a size of 96x96. /// /// - private static void SetUsingDefaultDragImage(IComDataObject dataObject, bool usingDefaultDragImage) - => SetBooleanFormat(dataObject, USINGDEFAULTDRAGIMAGE, usingDefaultDragImage); + private static void SetUsingDefaultDragImage(IComVisibleDataObject dataObject, bool usingDefaultDragImage) + => SetBooleanFormat(dataObject, DataFormatNames.UsingDefaultDragImage, usingDefaultDragImage); /// /// Creates an in-process server drag-image manager object and returns the specified interface pointer. @@ -518,10 +437,7 @@ private static void SetUsingDefaultDragImage(IComDataObject dataObject, bool usi private static bool TryGetDragDropHelper(TDragHelper** dragDropHelper) where TDragHelper : unmanaged, IComIID { - if (Control.CheckForIllegalCrossThreadCalls && Application.OleRequired() != ApartmentState.STA) - { - throw new InvalidOperationException(SR.ThreadMustBeSTA); - } + TOleServices.EnsureThreadState(); HRESULT hr = PInvokeCore.CoCreateInstance( CLSID.DragDropHelper, diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DragEventArgs.cs b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DragEventArgs.cs index 2d4ec50f3b5..54ade9542d5 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DragEventArgs.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DragEventArgs.cs @@ -1,13 +1,16 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Private.Windows.Ole; +using Windows.Win32.System.Ole; + namespace System.Windows.Forms; /// /// Provides data for the , , /// or event. /// -public class DragEventArgs : EventArgs +public class DragEventArgs : EventArgs, IDragEvent { /// /// Initializes a new instance of the class. @@ -54,19 +57,17 @@ public DragEventArgs( /// public IDataObject? Data { get; } + IComVisibleDataObject? IDragEvent.DataObject => Data as IComVisibleDataObject; + /// /// Gets the current state of the SHIFT, CTRL, and ALT keys. /// public int KeyState { get; } - /// - /// Gets the x-coordinate of the mouse pointer. - /// + /// public int X { get; } - /// - /// Gets the y-coordinate of the mouse pointer. - /// + /// public int Y { get; } /// @@ -80,11 +81,15 @@ public DragEventArgs( /// public DragDropEffects Effect { get; set; } + DROPEFFECT IDragEvent.Effect => (DROPEFFECT)Effect; + /// /// Gets or sets the drop description image type. /// public DropImageType DropImageType { get; set; } + DROPIMAGETYPE IDragEvent.DropImageType => (DROPIMAGETYPE)DropImageType; + /// /// Gets or sets the drop description text such as "Move to %1". /// @@ -107,10 +112,16 @@ public DragEventArgs( /// public string? MessageReplacementToken { get; set; } - internal DragEventArgs Clone() - { - return (DragEventArgs)MemberwiseClone(); - } + internal DragEventArgs Clone() => new( + Data, + KeyState, + X, + Y, + AllowedEffect, + Effect, + DropImageType, + Message, + MessageReplacementToken); internal bool Equals(DragEventArgs? dragEventArgs) { diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DropImageType.cs b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DropImageType.cs index 38695d81470..6f98bb3248a 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DropImageType.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DropImageType.cs @@ -11,40 +11,40 @@ public enum DropImageType /// /// No drop image preference; use the default image. /// - Invalid = -1, + Invalid = DROPIMAGETYPE.DROPIMAGE_INVALID, /// /// A red bisected circle such as that found on a "no smoking" sign. /// - None = 0, + None = DROPIMAGETYPE.DROPIMAGE_NONE, /// /// A plus sign (+) that indicates a copy operation. /// - Copy = DragDropEffects.Copy, + Copy = DROPIMAGETYPE.DROPIMAGE_COPY, /// /// An arrow that indicates a move operation. /// - Move = DragDropEffects.Move, + Move = DROPIMAGETYPE.DROPIMAGE_MOVE, /// /// An arrow that indicates a link. /// - Link = DragDropEffects.Link, + Link = DROPIMAGETYPE.DROPIMAGE_LINK, /// /// A tag icon that indicates that the metadata will be changed. /// - Label = 6, + Label = DROPIMAGETYPE.DROPIMAGE_LABEL, /// /// A yellow exclamation mark that indicates that a problem has been encountered in the operation. /// - Warning = 7, + Warning = DROPIMAGETYPE.DROPIMAGE_WARNING, /// /// Windows 7 and later. Use no drop image. /// - NoImage = 8 + NoImage = DROPIMAGETYPE.DROPIMAGE_NOIMAGE } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DropSource.cs b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DropSource.cs index dea3da6e069..a662a821df8 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DropSource.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DropSource.cs @@ -2,22 +2,22 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Drawing; +using System.Private.Windows.Ole; using Windows.Win32.System.Com; using Windows.Win32.System.Ole; using Windows.Win32.System.SystemServices; -using IComDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; namespace System.Windows.Forms; internal class DropSource : IDropSource.Interface, IDropSourceNotify.Interface, IManagedWrapper { private readonly ISupportOleDropSource _peer; - private readonly IComDataObject _dataObject; + private readonly IComVisibleDataObject _dataObject; private HWND _lastHwndTarget; private uint _lastHwndTargetThreadId; private GiveFeedbackEventArgs? _lastGiveFeedbackEventArgs; - public DropSource(ISupportOleDropSource peer, IComDataObject dataObject, Bitmap? dragImage, Point cursorOffset, bool useDefaultDragImage) + public DropSource(ISupportOleDropSource peer, IComVisibleDataObject dataObject, Bitmap? dragImage, Point cursorOffset, bool useDefaultDragImage) { _peer = peer.OrThrowIfNull(); _dataObject = dataObject.OrThrowIfNull(); @@ -81,7 +81,7 @@ public HRESULT GiveFeedback(DROPEFFECT dwEffect) return HRESULT.S_OK; - void UpdateDragImage(GiveFeedbackEventArgs e, IComDataObject? dataObject, HWND lastHwndTarget) + void UpdateDragImage(GiveFeedbackEventArgs e, IComVisibleDataObject? dataObject, HWND lastHwndTarget) { if (dataObject is null) { diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DropTarget.cs b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DropTarget.cs index e1a2a1a8aab..a8fef520645 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DropTarget.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DropTarget.cs @@ -5,13 +5,14 @@ using Windows.Win32.System.Com; using Windows.Win32.System.SystemServices; using Com = Windows.Win32.System.Com; -using Ole = Windows.Win32.System.Ole; +using DROPEFFECT = Windows.Win32.System.Ole.DROPEFFECT; +using OleIDropTarget = Windows.Win32.System.Ole.IDropTarget; namespace System.Windows.Forms; -internal unsafe class DropTarget : Ole.IDropTarget.Interface, IManagedWrapper +internal unsafe class DropTarget : OleIDropTarget.Interface, IManagedWrapper { - private IDataObject? _lastDataObject; + private DataObject? _lastDataObject; private DragDropEffects _lastEffect = DragDropEffects.None; private DragEventArgs? _lastDragEventArgs; private readonly HWND _hwndTarget; @@ -44,23 +45,33 @@ private void ClearDropDescription() /// should have associated ComWrappers created wrapper that implements /// to be passed out as is. Otherwise, the data will be wrapped in a . /// - private static IDataObject? CreateManagedDataObjectForOutgoingDropData(Com.IDataObject* nativeDataObject) + private static DataObject? CreateManagedDataObjectForOutgoingDropData(Com.IDataObject* nativeDataObject) { + DataObject? dataObject = null; + using var unknown = ComScope.QueryFrom(nativeDataObject); + if (ComWrappers.TryGetObject(unknown, out object? obj) && obj is IDataObject iDataObject) { - // If the original data object implemented IDataObject, we might've wrapped it. We need to give the original back out. - return iDataObject is DataObject dataObject && dataObject.TryUnwrapUserDataObject(out IDataObject? originalDataObject) - ? originalDataObject - : iDataObject; + // If the original data object implemented IDataObject, we might've wrapped it. + // We need to give the original back out. + + dataObject = iDataObject as DataObject; + + if (dataObject is not null + && dataObject.TryUnwrapUserDataObject(out IDataObject? originalIDataObject) + && originalIDataObject is DataObject originalDataObject) + { + dataObject = originalDataObject; + } } - return new DataObject(nativeDataObject); + return dataObject ?? new DataObject(nativeDataObject); } - private DragEventArgs? CreateDragEventArgs(Com.IDataObject* pDataObj, MODIFIERKEYS_FLAGS grfKeyState, POINTL pt, Ole.DROPEFFECT pdwEffect) + private DragEventArgs? CreateDragEventArgs(Com.IDataObject* pDataObj, MODIFIERKEYS_FLAGS grfKeyState, POINTL pt, DROPEFFECT pdwEffect) { - IDataObject? data; + DataObject? data; if (pDataObj is null) { @@ -92,7 +103,7 @@ private void ClearDropDescription() return dragEvent; } - HRESULT Ole.IDropTarget.Interface.DragEnter(Com.IDataObject* pDataObj, MODIFIERKEYS_FLAGS grfKeyState, POINTL pt, Ole.DROPEFFECT* pdwEffect) + HRESULT OleIDropTarget.Interface.DragEnter(Com.IDataObject* pDataObj, MODIFIERKEYS_FLAGS grfKeyState, POINTL pt, DROPEFFECT* pdwEffect) { Debug.Assert(pDataObj is not null, "OleDragEnter didn't give us a valid data object."); @@ -104,7 +115,7 @@ HRESULT Ole.IDropTarget.Interface.DragEnter(Com.IDataObject* pDataObj, MODIFIERK if (CreateDragEventArgs(pDataObj, grfKeyState, pt, *pdwEffect) is { } dragEvent) { _owner.OnDragEnter(dragEvent); - *pdwEffect = (Ole.DROPEFFECT)dragEvent.Effect; + *pdwEffect = (DROPEFFECT)dragEvent.Effect; _lastEffect = dragEvent.Effect; if (dragEvent.DropImageType > DropImageType.Invalid) @@ -115,13 +126,13 @@ HRESULT Ole.IDropTarget.Interface.DragEnter(Com.IDataObject* pDataObj, MODIFIERK } else { - *pdwEffect = Ole.DROPEFFECT.DROPEFFECT_NONE; + *pdwEffect = DROPEFFECT.DROPEFFECT_NONE; } return HRESULT.S_OK; } - HRESULT Ole.IDropTarget.Interface.DragOver(MODIFIERKEYS_FLAGS grfKeyState, POINTL pt, Ole.DROPEFFECT* pdwEffect) + HRESULT OleIDropTarget.Interface.DragOver(MODIFIERKEYS_FLAGS grfKeyState, POINTL pt, DROPEFFECT* pdwEffect) { if (pdwEffect is null) { @@ -131,7 +142,7 @@ HRESULT Ole.IDropTarget.Interface.DragOver(MODIFIERKEYS_FLAGS grfKeyState, POINT if (CreateDragEventArgs(null, grfKeyState, pt, *pdwEffect) is { } dragEvent) { _owner.OnDragOver(dragEvent); - *pdwEffect = (Ole.DROPEFFECT)dragEvent.Effect; + *pdwEffect = (DROPEFFECT)dragEvent.Effect; _lastEffect = dragEvent.Effect; if (dragEvent.DropImageType > DropImageType.Invalid) @@ -142,13 +153,13 @@ HRESULT Ole.IDropTarget.Interface.DragOver(MODIFIERKEYS_FLAGS grfKeyState, POINT } else { - *pdwEffect = Ole.DROPEFFECT.DROPEFFECT_NONE; + *pdwEffect = DROPEFFECT.DROPEFFECT_NONE; } return HRESULT.S_OK; } - HRESULT Ole.IDropTarget.Interface.DragLeave() + HRESULT OleIDropTarget.Interface.DragLeave() { if (_lastDragEventArgs?.DropImageType > DropImageType.Invalid) { @@ -161,7 +172,7 @@ HRESULT Ole.IDropTarget.Interface.DragLeave() return HRESULT.S_OK; } - HRESULT Ole.IDropTarget.Interface.Drop(Com.IDataObject* pDataObj, MODIFIERKEYS_FLAGS grfKeyState, POINTL pt, Ole.DROPEFFECT* pdwEffect) + HRESULT OleIDropTarget.Interface.Drop(Com.IDataObject* pDataObj, MODIFIERKEYS_FLAGS grfKeyState, POINTL pt, DROPEFFECT* pdwEffect) { if (pdwEffect is null) { @@ -177,11 +188,11 @@ HRESULT Ole.IDropTarget.Interface.Drop(Com.IDataObject* pDataObj, MODIFIERKEYS_F } _owner.OnDragDrop(dragEvent); - *pdwEffect = (Ole.DROPEFFECT)dragEvent.Effect; + *pdwEffect = (DROPEFFECT)dragEvent.Effect; } else { - *pdwEffect = Ole.DROPEFFECT.DROPEFFECT_NONE; + *pdwEffect = DROPEFFECT.DROPEFFECT_NONE; } _lastEffect = DragDropEffects.None; diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/WinFormsOleServices.cs b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/WinFormsOleServices.cs new file mode 100644 index 00000000000..8806dccf075 --- /dev/null +++ b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/WinFormsOleServices.cs @@ -0,0 +1,79 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Drawing; +using System.Private.Windows.Ole; +using Windows.Win32.System.Com; + +namespace System.Windows.Forms.Ole; + +/// +/// Provides Windows Forms specific OLE services. +/// +internal sealed class WinFormsOleServices : IOleServices +{ + private WinFormsOleServices() { } + + static void IOleServices.EnsureThreadState() + { + if (Control.CheckForIllegalCrossThreadCalls && Application.OleRequired() != ApartmentState.STA) + { + throw new ThreadStateException(SR.ThreadMustBeSTA); + } + } + + static unsafe HRESULT IOleServices.GetDataHere(string format, object data, FORMATETC* pformatetc, STGMEDIUM* pmedium) + { + if (format == DataFormatNames.Dib && data is Image) + { + // GDI+ does not properly handle saving to DIB images. Since the clipboard will take + // an HBITMAP and publish a Dib, we don't need to support this. + return HRESULT.DV_E_TYMED; + } + + if (((TYMED)pformatetc->tymed).HasFlag(TYMED.TYMED_GDI)) + { + if (format.Equals(DataFormatNames.Bitmap) && data is Bitmap bitmap) + { + // Save bitmap + pmedium->u.hBitmap = GetCompatibleBitmap(bitmap); + } + + return HRESULT.S_OK; + } + + return HRESULT.DV_E_TYMED; + + static HBITMAP GetCompatibleBitmap(Bitmap bitmap) + { + using var screenDC = GetDcScope.ScreenDC; + + // GDI+ returns a DIBSECTION based HBITMAP. The clipboard only deals well with bitmaps created using + // CreateCompatibleBitmap(). So, we convert the DIBSECTION into a compatible bitmap. + HBITMAP hbitmap = bitmap.GetHBITMAP(); + + // Create a compatible DC to render the source bitmap. + using CreateDcScope sourceDC = new(screenDC); + using SelectObjectScope sourceBitmapSelection = new(sourceDC, hbitmap); + + // Create a compatible DC and a new compatible bitmap. + using CreateDcScope destinationDC = new(screenDC); + HBITMAP compatibleBitmap = PInvokeCore.CreateCompatibleBitmap(screenDC, bitmap.Size.Width, bitmap.Size.Height); + + // Select the new bitmap into a compatible DC and render the blt the original bitmap. + using SelectObjectScope destinationBitmapSelection = new(destinationDC, compatibleBitmap); + PInvokeCore.BitBlt( + destinationDC, + 0, + 0, + bitmap.Size.Width, + bitmap.Size.Height, + sourceDC, + 0, + 0, + ROP_CODE.SRCCOPY); + + return compatibleBitmap; + } + } +} diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/WinFormsRuntime.cs b/src/System.Windows.Forms/src/System/Windows/Forms/WinFormsRuntime.cs new file mode 100644 index 00000000000..b7fb6a7b2df --- /dev/null +++ b/src/System.Windows.Forms/src/System/Windows/Forms/WinFormsRuntime.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Windows.Forms.Nrbf; +using System.Windows.Forms.Ole; + +namespace System.Windows.Forms; + +/// +/// Represents Windows Forms specific runtime services and types. +/// +internal class WinFormsRuntime : Runtime +{ + // This class is not intended to be instantiated. + private WinFormsRuntime() { } +} diff --git a/src/System.Windows.Forms/tests/ComDisabledTests/DataObjectComTests.cs b/src/System.Windows.Forms/tests/ComDisabledTests/DataObjectComTests.cs index d836d6e1e54..d354ff2df5c 100644 --- a/src/System.Windows.Forms/tests/ComDisabledTests/DataObjectComTests.cs +++ b/src/System.Windows.Forms/tests/ComDisabledTests/DataObjectComTests.cs @@ -48,7 +48,7 @@ public void DataObject_CustomIDataObject_MockRoundTrip() var inDataPtr = ComHelpers.GetComScope(inData); IDataObject outData = dropTargetAccessor.CreateDelegate()(inDataPtr); - outData.Should().BeSameAs(data); + outData.Should().NotBeSameAs(data); } [WinFormsFact] diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/BinaryFormat/WinFormsBinaryFormattedObjectTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/BinaryFormat/WinFormsBinaryFormattedObjectTests.cs index ad6c5898ca7..7b970fdedbc 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/BinaryFormat/WinFormsBinaryFormattedObjectTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/BinaryFormat/WinFormsBinaryFormattedObjectTests.cs @@ -6,6 +6,8 @@ using System.ComponentModel; using System.Drawing; using System.Formats.Nrbf; +using System.Private.Windows.Nrbf; +using System.Private.Windows.Ole; using System.Reflection.Metadata; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; @@ -25,7 +27,8 @@ public void BinaryFormattedObject_NonJsonData_RemainsSerialized() { SimpleTestData testData = new() { X = 1, Y = 1 }; SerializationRecord format = testData.SerializeAndDecode(); - ITypeResolver resolver = new TypeBinder(typeof(SimpleTestData), resolver: null, legacyMode: false); + DataRequest request = new("test"); + ITypeResolver resolver = new TypeBinder(typeof(SimpleTestData), in request); format.TryGetObjectFromJson(resolver, out _).Should().BeFalse(); } @@ -39,13 +42,15 @@ public void BinaryFormattedObject_JsonData_RoundTrip() JsonBytes = JsonSerializer.SerializeToUtf8Bytes(testData) }; + DataRequest request = new("test"); + using MemoryStream stream = new(); - WinFormsBinaryFormatWriter.WriteJsonData(stream, json); + BinaryFormatWriter.TryWriteJsonData(stream, json); stream.Position = 0; SerializationRecord binary = NrbfDecoder.Decode(stream); binary.TypeName.AssemblyName!.FullName.Should().Be(IJsonData.CustomAssemblyName); - ITypeResolver resolver = new TypeBinder(typeof(SimpleTestData), resolver: null, legacyMode: false); + ITypeResolver resolver = new TypeBinder(typeof(SimpleTestData), in request); binary.TryGetObjectFromJson(resolver, out _).Should().BeTrue(); binary.TryGetObjectFromJson(resolver, out object? result).Should().BeTrue(); SimpleTestData deserialized = result.Should().BeOfType().Which; @@ -62,7 +67,7 @@ public void BinaryFormattedObject_Deserialize_FromStream_WithBinaryFormatter() }; using MemoryStream stream = new(); - WinFormsBinaryFormatWriter.WriteJsonData(stream, data); + BinaryFormatWriter.TryWriteJsonData(stream, data); stream.Position = 0; using BinaryFormatterScope scope = new(enable: true); @@ -118,7 +123,8 @@ public void BinaryFormattedObject_Bitmap_FromBinaryFormatter() root.TypeName.AssemblyName!.FullName.Should().Be(AssemblyRef.SystemDrawing); ArrayRecord arrayRecord = root.GetArrayRecord("Data")!; arrayRecord.Should().BeAssignableTo>(); - rootRecord.TryGetBitmap(out object? result).Should().BeTrue(); + bool success = typeof(WinFormsNrbfSerializer).TestAccessor().Dynamic.TryGetBitmap(rootRecord, out object? result); + success.Should().BeTrue(); using Bitmap deserialized = result.Should().BeOfType().Which; deserialized.Size.Should().Be(bitmap.Size); } @@ -133,7 +139,8 @@ public void BinaryFormattedObject_Bitmap_RoundTrip() stream.Position = 0; SerializationRecord rootRecord = NrbfDecoder.Decode(stream); - rootRecord.TryGetBitmap(out object? result).Should().BeTrue(); + bool success = typeof(WinFormsNrbfSerializer).TestAccessor().Dynamic.TryGetBitmap(rootRecord, out object? result); + success.Should().BeTrue(); using Bitmap deserialized = result.Should().BeOfType().Which; deserialized.Size.Should().Be(bitmap.Size); } @@ -172,7 +179,8 @@ public void BinaryFormattedObject_ImageListStreamer_FromBinaryFormatter() root.TypeName.AssemblyName!.FullName.Should().Be(typeof(WinFormsBinaryFormatWriter).Assembly.FullName); root.GetArrayRecord("Data")!.Should().BeAssignableTo>(); - rootRecord.TryGetImageListStreamer(out object? result).Should().BeTrue(); + bool success = typeof(WinFormsNrbfSerializer).TestAccessor().Dynamic.TryGetImageListStreamer(rootRecord, out object? result); + success.Should().BeTrue(); using ImageListStreamer deserialized = result.Should().BeOfType().Which; using ImageList newList = new(); newList.ImageStream = deserialized; @@ -194,7 +202,8 @@ public void BinaryFormattedObject_ImageListStreamer_RoundTrip() memoryStream.Position = 0; SerializationRecord rootRecord = NrbfDecoder.Decode(memoryStream); - rootRecord.TryGetImageListStreamer(out object? result).Should().BeTrue(); + bool success = typeof(WinFormsNrbfSerializer).TestAccessor().Dynamic.TryGetImageListStreamer(rootRecord, out object? result); + success.Should().BeTrue(); using ImageListStreamer deserialized = result.Should().BeOfType().Which; using ImageList newList = new(); newList.ImageStream = deserialized; diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/BinaryFormatUtilitiesTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/BinaryFormatUtilitiesTests.cs index 79673b8f0bf..88b456246a2 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/BinaryFormatUtilitiesTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/BinaryFormatUtilitiesTests.cs @@ -5,9 +5,10 @@ using System.Collections; using System.Drawing; +using System.Private.Windows.Ole; using System.Reflection.Metadata; using System.Runtime.Serialization; -using Utilities = System.Windows.Forms.BinaryFormatUtilities; +using Utilities = System.Private.Windows.Ole.BinaryFormatUtilities; namespace System.Windows.Forms.Tests; @@ -25,33 +26,58 @@ private void WriteObjectToStream(object value, bool restrictSerialization = fals private object? ReadObjectFromStream() { _stream.Position = 0; - return Utilities.ReadObjectFromStream(_stream, resolver: null, legacyMode: true); + DataRequest request = new("test") + { + UntypedRequest = true + }; + + return Utilities.ReadObjectFromStream(_stream, in request); } private object? ReadRestrictedObjectFromStream() { _stream.Position = 0; - return Utilities.ReadRestrictedObjectFromStream(_stream, resolver: null, legacyMode: true); + DataRequest request = new("test") + { + UntypedRequest = true + }; + + return Utilities.ReadRestrictedObjectFromStream(_stream, in request); } private object? ReadObjectFromStream(bool restrictDeserialization, Func? resolver) { _stream.Position = 0; + DataRequest request = new("test") + { + Resolver = resolver, + }; + return restrictDeserialization - ? Utilities.ReadRestrictedObjectFromStream(_stream, resolver, legacyMode: false) - : Utilities.ReadObjectFromStream(_stream, resolver, legacyMode: false); + ? Utilities.ReadRestrictedObjectFromStream(_stream, in request) + : Utilities.ReadObjectFromStream(_stream, in request); } private object? ReadObjectFromStream(Func? resolver) { _stream.Position = 0; - return Utilities.ReadObjectFromStream(_stream, resolver, legacyMode: false); + DataRequest request = new("test") + { + Resolver = resolver + }; + + return Utilities.ReadObjectFromStream(_stream, in request); } private object? ReadRestrictedObjectFromStream(Func? resolver) { _stream.Position = 0; - return Utilities.ReadRestrictedObjectFromStream(_stream, resolver, legacyMode: false); + DataRequest request = new("test") + { + Resolver = resolver + }; + + return Utilities.ReadRestrictedObjectFromStream(_stream, in request); } private object? RoundTripObject(object value) @@ -663,15 +689,20 @@ public void ReadFontSerializedOnNet481() byte[] bytes = Convert.FromBase64String(font); using MemoryStream stream = new(bytes); + DataRequest request = new("test") + { + UntypedRequest = true + }; + // Default deserialization with the NRBF deserializer. using (BinaryFormatterInClipboardDragDropScope binaryFormatScope = new(enable: true)) { // GetData case. stream.Position = 0; + Action getData = () => Utilities.ReadObjectFromStream( stream, - resolver: null, - legacyMode: true); + in request); getData.Should().Throw(); @@ -685,8 +716,7 @@ public void ReadFontSerializedOnNet481() stream.Position = 0; var result = Utilities.ReadObjectFromStream( stream, - resolver: null, - legacyMode: true).Should().BeOfType().Subject; + in request).Should().BeOfType().Subject; result.Name.Should().Be("Microsoft Sans Serif"); result.Size.Should().Be(10); @@ -697,10 +727,15 @@ static void TryGetData(MemoryStream stream) { // TryGetData case. stream.Position = 0; + + DataRequest request = new("test") + { + Resolver = FontResolver, + }; + var result = Utilities.ReadObjectFromStream( stream, - resolver: FontResolver, - legacyMode: false).Should().BeOfType().Subject; + in request).Should().BeOfType().Subject; result.Name.Should().Be("Microsoft Sans Serif"); result.Size.Should().Be(10); @@ -738,9 +773,14 @@ public void Sample_GetData_UseBinaryFormatter() using BinaryFormatterFullCompatScope scope = new(); WriteObjectToStream(value); + DataRequest request = new("test") + { + UntypedRequest = true + }; + // legacyMode == true follows the GetData path. _stream.Position = 0; - Utilities.ReadObjectFromStream(_stream, resolver: null, legacyMode: true) + Utilities.ReadObjectFromStream(_stream, in request) .Should().BeEquivalentTo(value); } @@ -753,9 +793,14 @@ public void Sample_GetData_UseNrbfDeserialize() using BinaryFormatterInClipboardDragDropScope clipboardScope = new(enable: true); WriteObjectToStream(value); + DataRequest request = new("test") + { + UntypedRequest = true + }; + // This works because GetData falls back to the BinaryFormatter deserializer, NRBF deserializer fails because it requires a resolver. _stream.Position = 0; - Utilities.ReadObjectFromStream(_stream, resolver: null, legacyMode: true) + Utilities.ReadObjectFromStream(_stream, in request) .Should().BeEquivalentTo(value); } diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DataObjectComTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DataObjectComTests.cs index 432dbd1d7e0..48b7c6bc58d 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DataObjectComTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DataObjectComTests.cs @@ -27,7 +27,7 @@ public void DataObject_CustomIDataObject_MockRoundTrip() var inDataPtr = ComHelpers.GetComScope(inData); IDataObject outData = dropTargetAccessor.CreateDelegate()(inDataPtr); - outData.Should().BeSameAs(data); + outData.Should().NotBeSameAs(data); } [WinFormsFact] diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DataObjectTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DataObjectTests.cs index 00b9fdcfe45..225f57c6c98 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DataObjectTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DataObjectTests.cs @@ -2798,7 +2798,15 @@ public unsafe void DataObject_MockRoundTrip_OutData_IsSame(object data) using var inDataPtr = ComHelpers.GetComScope(inData); IDataObject outData = dropTargetAccessor.CreateDelegate()(inDataPtr); - outData.Should().BeSameAs(data); + + if (data is CustomDataObject) + { + outData.Should().NotBeSameAs(data); + } + else + { + outData.Should().BeSameAs(data); + } } public static IEnumerable DataObjectWithJsonMockRoundTripData() @@ -2857,7 +2865,7 @@ public unsafe void DataObject_IDataObject_MockRoundTrip_IsWrapped() using var inDataPtr = ComHelpers.GetComScope(inData); IDataObject outData = dropTargetAccessor.CreateDelegate()(inDataPtr); - outData.Should().BeSameAs(data); + outData.Should().NotBeSameAs(data); } [WinFormsFact] diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DragDropFormatTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DragDropFormatTests.cs index 339e0f22d5a..1fb3233e9ea 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DragDropFormatTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DragDropFormatTests.cs @@ -12,6 +12,7 @@ using STGMEDIUM = System.Runtime.InteropServices.ComTypes.STGMEDIUM; using TYMED = System.Runtime.InteropServices.ComTypes.TYMED; using Com = Windows.Win32.System.Com; +using System.Private.Windows.Ole; namespace System.Windows.Forms.Tests; diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DragDropHelperTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DragDropHelperTests.cs index 1c72b47d9ad..3b2e831bf2b 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DragDropHelperTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DragDropHelperTests.cs @@ -7,8 +7,11 @@ using System.Private.Windows.Ole; using System.Runtime.CompilerServices; using System.Runtime.InteropServices.ComTypes; +using Windows.Win32.System.Ole; using Com = Windows.Win32.System.Com; -using IComDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; +using DragDropHelper = System.Private.Windows.Ole.DragDropHelper< + System.Windows.Forms.Ole.WinFormsOleServices, + System.Windows.Forms.DataFormats.Format>; namespace System.Windows.Forms.Tests; @@ -67,7 +70,7 @@ public static IEnumerable InDragLoop_TestData() [Fact] public void IsInDragLoop_NullComDataObject_ThrowsArgumentNullException() { - IComDataObject dataObject = null; + IDataObjectInternal dataObject = null; Assert.Throws(nameof(dataObject), () => DragDropHelper.IsInDragLoop(dataObject)); } @@ -79,15 +82,15 @@ public void IsInDragLoop_NullDataObject_ThrowsArgumentNullException() } [Theory] - [InlineData(DragDropHelper.DRAGCONTEXT, true)] - [InlineData(DragDropHelper.DRAGIMAGEBITS, true)] - [InlineData(DragDropHelper.DRAGSOURCEHELPERFLAGS, true)] - [InlineData(DragDropHelper.DRAGWINDOW, true)] + [InlineData(DataFormatNames.DragContext, true)] + [InlineData(DataFormatNames.DragImageBits, true)] + [InlineData(DataFormatNames.DragSourceHelperFlags, true)] + [InlineData(DataFormatNames.DragWindow, true)] [InlineData(PInvokeCore.CFSTR_DROPDESCRIPTION, true)] [InlineData(PInvokeCore.CFSTR_INDRAGLOOP, true)] - [InlineData(DragDropHelper.ISSHOWINGLAYERED, true)] - [InlineData(DragDropHelper.ISSHOWINGTEXT, true)] - [InlineData(DragDropHelper.USINGDEFAULTDRAGIMAGE, true)] + [InlineData(DataFormatNames.IsShowingLayered, true)] + [InlineData(DataFormatNames.IsShowingText, true)] + [InlineData(DataFormatNames.UsingDefaultDragImage, true)] public void IsInDragLoopFormat_ReturnsExpected(string format, bool expectedIsInDragLoopFormat) { FORMATETC formatEtc = new() @@ -109,8 +112,9 @@ public unsafe void SetDragImage_DataObject_Bitmap_Point_bool_ReturnsExpected(Dat try { DragDropHelper.SetDragImage(dataObject, dragImage, cursorOffset, useDefaultDragImage); + // This DataObject is backed up by the DataStore. - dataObject.TryGetData(DragDropHelper.DRAGIMAGEBITS, out DragDropFormat dragDropFormat).Should().BeTrue(); + dataObject.TryGetData(DataFormatNames.DragImageBits, out DragDropFormat dragDropFormat).Should().BeTrue(); dragDropFormat.Should().NotBeNull(); void* basePtr = PInvokeCore.GlobalLock(dragDropFormat.Medium.hGlobal); SHDRAGIMAGE* pDragImage = (SHDRAGIMAGE*)basePtr; @@ -135,8 +139,9 @@ public unsafe void SetDragImage_DataObject_GiveFeedbackEventArgs_ReturnsExpected try { DragDropHelper.SetDragImage(dataObject, e); + // This DataObject is backed up by the DataStore. - dataObject.TryGetData(DragDropHelper.DRAGIMAGEBITS, out DragDropFormat dragDropFormat).Should().BeTrue(); + dataObject.TryGetData(DataFormatNames.DragImageBits, out DragDropFormat dragDropFormat).Should().BeTrue(); dragDropFormat.Should().NotBeNull(); void* basePtr = PInvokeCore.GlobalLock(dragDropFormat.Medium.hGlobal); SHDRAGIMAGE* pDragImage = (SHDRAGIMAGE*)basePtr; @@ -178,13 +183,32 @@ public void SetDragImage_NullGiveFeedbackEventArgs_ThrowsArgumentNullException() Assert.Throws(nameof(e), () => DragDropHelper.SetDragImage(new DataObject(), e)); } + private class DragEvent : IDragEvent + { + public int X { get; set; } + public int Y { get; set; } + public DROPEFFECT Effect { get; set; } + public DROPIMAGETYPE DropImageType { get; set; } + public IComVisibleDataObject DataObject { get; set; } + public string Message { get; set; } + public string MessageReplacementToken { get; set; } + } + [Theory] [MemberData(nameof(DropDescription_DataObject_DropImageType_string_string_TestData))] public unsafe void SetDropDescription_ClearDropDescription_ReturnsExpected(DataObject dataObject, DropImageType dropImageType, string message, string messageReplacementToken) { try { - DragDropHelper.SetDropDescription(dataObject, dropImageType, message, messageReplacementToken); + DragEvent dragEvent = new() + { + DataObject = dataObject, + DropImageType = (DROPIMAGETYPE)dropImageType, + Message = message, + MessageReplacementToken = messageReplacementToken + }; + + DragDropHelper.SetDropDescription(dragEvent); DragDropHelper.ClearDropDescription(dataObject); dataObject.TryGetData(PInvokeCore.CFSTR_DROPDESCRIPTION, autoConvert: false, out DragDropFormat dragDropFormat).Should().BeTrue(); dragDropFormat.Should().NotBeNull(); @@ -210,7 +234,7 @@ public unsafe void SetDropDescription_ClearDropDescription_ReturnsExpected(DataO public void SetDropDescription_InvalidDropImageType_ThrowsArgumentNullException(DropImageType dropImageType) { Assert.Throws(nameof(dropImageType), - () => DragDropHelper.SetDropDescription(new DataObject(), dropImageType, string.Empty, string.Empty)); + () => DragDropHelper.SetDropDescription(new DataObject(), (DROPIMAGETYPE)dropImageType, string.Empty, string.Empty)); } [Theory] @@ -219,9 +243,8 @@ public void SetDropDescription_IsInDragLoop_ReturnsExpected(DataObject dataObjec { try { - DragDropHelper.SetDropDescription(dataObject, dropImageType, message, messageReplacementToken); - Assert.True(DragDropHelper.IsInDragLoop(dataObject as IComDataObject)); - Assert.True(DragDropHelper.IsInDragLoop(dataObject as IDataObjectInternal)); + DragDropHelper.SetDropDescription(dataObject, (DROPIMAGETYPE)dropImageType, message, messageReplacementToken); + Assert.True(DragDropHelper.IsInDragLoop(dataObject)); } finally { @@ -233,7 +256,7 @@ public void SetDropDescription_IsInDragLoop_ReturnsExpected(DataObject dataObjec [MemberData(nameof(DropDescription_LengthExceedsMaxPath_TestData))] public void SetDropDescription_LengthExceedsMaxPath_ThrowsArgumentOutOfRangeException(DataObject dataObject, DropImageType dropImageType, string message, string messageReplacementToken) { - Assert.Throws(() => DragDropHelper.SetDropDescription(dataObject, dropImageType, message, messageReplacementToken)); + Assert.Throws(() => DragDropHelper.SetDropDescription(dataObject, (DROPIMAGETYPE)dropImageType, message, messageReplacementToken)); } [Fact] @@ -241,14 +264,14 @@ public void SetDropDescription_NullDataObject_ThrowsArgumentNullException() { DataObject dataObject = null; Assert.Throws(nameof(dataObject), - () => DragDropHelper.SetDropDescription(dataObject, DropImageType.Invalid, string.Empty, string.Empty)); + () => DragDropHelper.SetDropDescription(dataObject, (DROPIMAGETYPE)DropImageType.Invalid, string.Empty, string.Empty)); } [Theory] [MemberData(nameof(DropDescription_DataObject_DropImageType_string_string_TestData))] public unsafe void SetDropDescription_ReleaseDragDropFormats_ReturnsExpected(DataObject dataObject, DropImageType dropImageType, string message, string messageReplacementToken) { - DragDropHelper.SetDropDescription(dataObject, dropImageType, message, messageReplacementToken); + DragDropHelper.SetDropDescription(dataObject, (DROPIMAGETYPE)dropImageType, message, messageReplacementToken); DragDropHelper.ReleaseDragDropFormats(dataObject); foreach (string format in dataObject.GetFormats()) @@ -282,7 +305,7 @@ public unsafe void SetDropDescription_DragEventArgs_ReturnsExpected(DragEventArg } finally { - if (e.Data is IComDataObject dataObject) + if (e.Data is IComVisibleDataObject dataObject) { DragDropHelper.ReleaseDragDropFormats(dataObject); } @@ -295,7 +318,7 @@ public unsafe void SetDropDescription_DataObject_DropImageType_string_string_Ret { try { - DragDropHelper.SetDropDescription(dataObject, dropImageType, message, messageReplacementToken); + DragDropHelper.SetDropDescription(dataObject, (DROPIMAGETYPE)dropImageType, message, messageReplacementToken); dataObject.TryGetData(PInvokeCore.CFSTR_DROPDESCRIPTION, autoConvert: false, out DragDropFormat dragDropFormat).Should().BeTrue(); void* basePtr = PInvokeCore.GlobalLock(dragDropFormat.Medium.hGlobal); DROPDESCRIPTION* pDropDescription = (DROPDESCRIPTION*)basePtr;