From 64528f3e176738f7e621ed6959c4593b24db9453 Mon Sep 17 00:00:00 2001 From: Loni Tra Date: Thu, 18 Jan 2024 14:09:42 -0800 Subject: [PATCH] Convert Rest of Ole to Cswin32 (#10651) * Add test to capture wrapping behavior * Convert rest of ole to cswin32 * update ComNativeDescriptorTest * Add test and comment capturing clipboard behavior * fix build --- .../Interop/Ole32/Interop.CoCreateInstance.cs | 19 -- .../src/Interop/Ole32/Interop.DoDragDrop.cs | 19 -- .../Interop/Ole32/Interop.OleGetClipboard.cs | 14 - .../Interop/Ole32/Interop.OleSetClipboard.cs | 14 - .../Interop/Ole32/Interop.ReleaseStgMedium.cs | 18 -- .../Shell32/Interop.IDragSourceHelper2.cs | 30 --- .../Shell32/Interop.IDropTargetHelper.cs | 44 --- .../src/NativeMethods.json | 11 + .../src/NativeMethods.txt | 6 + .../src/System/Windows/Forms/Control.cs | 56 ++-- .../RichTextBox/RichTextBox.OleCallback.cs | 2 +- .../ToolStrips/ToolStripDropTargetManager.cs | 2 +- .../Controls/ToolStrips/ToolStripItem.cs | 13 +- .../src/System/Windows/Forms/OLE/Clipboard.cs | 46 +++- .../OLE/DataObject.ComDataObjectAdapter.cs | 10 +- .../Windows/Forms/OLE/DragDropFormat.cs | 8 +- .../Windows/Forms/OLE/DragDropHelper.cs | 238 +++++++--------- .../System/Windows/Forms/OLE/DropTarget.cs | 43 +-- .../UIIntegrationTests/DragDropTests.cs | 254 +++++++++--------- .../UIIntegrationTests/GlobalUsings.cs | 1 - .../UiaClient/Interop.IUIAutomation.cs | 123 --------- .../UiaClient/Interop.IUIAutomationElement.cs | 178 ------------ .../tests/InteropTests/PropertyGridTests.cs | 25 +- .../System/Windows/Forms/ClipboardTests.cs | 23 ++ .../Com2Interop/ComNativeDescriptorTests.cs | 9 +- .../System/Windows/Forms/DataObjectTests.cs | 124 ++++++++- 26 files changed, 516 insertions(+), 814 deletions(-) delete mode 100644 src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.CoCreateInstance.cs delete mode 100644 src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.DoDragDrop.cs delete mode 100644 src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.OleGetClipboard.cs delete mode 100644 src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.OleSetClipboard.cs delete mode 100644 src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.ReleaseStgMedium.cs delete mode 100644 src/System.Windows.Forms.Primitives/src/Interop/Shell32/Interop.IDragSourceHelper2.cs delete mode 100644 src/System.Windows.Forms.Primitives/src/Interop/Shell32/Interop.IDropTargetHelper.cs delete mode 100644 src/System.Windows.Forms/tests/IntegrationTests/UIIntegrationTests/Interop/UiaClient/Interop.IUIAutomation.cs delete mode 100644 src/System.Windows.Forms/tests/IntegrationTests/UIIntegrationTests/Interop/UiaClient/Interop.IUIAutomationElement.cs diff --git a/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.CoCreateInstance.cs b/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.CoCreateInstance.cs deleted file mode 100644 index 79a395c8205..00000000000 --- a/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.CoCreateInstance.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.InteropServices; -using Windows.Win32.System.Com; - -internal partial class Interop -{ - internal static partial class Ole32 - { - [DllImport(Libraries.Ole32, ExactSpelling = true)] - public static extern HRESULT CoCreateInstance( - in Guid rclsid, - IntPtr punkOuter, - CLSCTX dwClsContext, - in Guid riid, - [MarshalAs(UnmanagedType.Interface)] out object ppv); - } -} diff --git a/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.DoDragDrop.cs b/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.DoDragDrop.cs deleted file mode 100644 index a030b2c34ee..00000000000 --- a/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.DoDragDrop.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.InteropServices; -using System.Runtime.InteropServices.ComTypes; -using Windows.Win32.System.Ole; - -internal static partial class Interop -{ - internal static partial class Ole32 - { - [DllImport(Libraries.Ole32, ExactSpelling = true)] - public static extern HRESULT DoDragDrop( - IDataObject pDataObj, - IDropSource.Interface pDropSource, - DROPEFFECT dwOKEffects, - out DROPEFFECT pdwEffect); - } -} diff --git a/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.OleGetClipboard.cs b/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.OleGetClipboard.cs deleted file mode 100644 index cc5fea431c3..00000000000 --- a/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.OleGetClipboard.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.InteropServices; -using System.Runtime.InteropServices.ComTypes; - -internal static partial class Interop -{ - internal static partial class Ole32 - { - [DllImport(Libraries.Ole32, ExactSpelling = true)] - public static extern HRESULT OleGetClipboard(ref IDataObject? data); - } -} diff --git a/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.OleSetClipboard.cs b/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.OleSetClipboard.cs deleted file mode 100644 index 91f598dec99..00000000000 --- a/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.OleSetClipboard.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.InteropServices; -using System.Runtime.InteropServices.ComTypes; - -internal static partial class Interop -{ - internal static partial class Ole32 - { - [DllImport(Libraries.Ole32, ExactSpelling = true)] - public static extern HRESULT OleSetClipboard(IDataObject? pDataObj); - } -} diff --git a/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.ReleaseStgMedium.cs b/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.ReleaseStgMedium.cs deleted file mode 100644 index ec2db601431..00000000000 --- a/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.ReleaseStgMedium.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.InteropServices; -using System.Runtime.InteropServices.ComTypes; - -internal static partial class Interop -{ - internal static partial class Ole32 - { - // Even though the windows headers indicate that STGMEDIUM is input only this is not true, - // ReleaseStgMedium will clear the fields it released. It is important for classic marshaling - // to *NOT* mark the argument as input parameter because otherwise .NET will not transfer - // ownership of the COM pointers correctly. This defeats the purpose of calling this method. - [DllImport(Libraries.Ole32, ExactSpelling = true)] - public static extern void ReleaseStgMedium(ref STGMEDIUM pmedium); - } -} diff --git a/src/System.Windows.Forms.Primitives/src/Interop/Shell32/Interop.IDragSourceHelper2.cs b/src/System.Windows.Forms.Primitives/src/Interop/Shell32/Interop.IDragSourceHelper2.cs deleted file mode 100644 index b872340144c..00000000000 --- a/src/System.Windows.Forms.Primitives/src/Interop/Shell32/Interop.IDragSourceHelper2.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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.Runtime.InteropServices; -using System.Runtime.InteropServices.ComTypes; - -internal static partial class Interop -{ - internal static partial class Shell32 - { - [ComImport] - [Guid("DE5BF786-477A-11D2-839D-00C04FD918D0")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - public interface IDragSourceHelper2 - { - HRESULT InitializeFromBitmap( - in SHDRAGIMAGE pshdi, - IDataObject dataObject); - - HRESULT InitializeFromWindow( - IntPtr hwnd, - in Point ppt, - IDataObject dataObject); - - HRESULT SetFlags( - int dwFlags); - } - } -} diff --git a/src/System.Windows.Forms.Primitives/src/Interop/Shell32/Interop.IDropTargetHelper.cs b/src/System.Windows.Forms.Primitives/src/Interop/Shell32/Interop.IDropTargetHelper.cs deleted file mode 100644 index fa81ae0ceca..00000000000 --- a/src/System.Windows.Forms.Primitives/src/Interop/Shell32/Interop.IDropTargetHelper.cs +++ /dev/null @@ -1,44 +0,0 @@ -// 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.Runtime.InteropServices; -using Windows.Win32.System.Ole; -using IComDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; - -internal static partial class Interop -{ - internal static partial class Shell32 - { - [ComImport] - [Guid("4657278B-411B-11D2-839A-00C04FD918D0")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - public interface IDropTargetHelper - { - [PreserveSig] - HRESULT DragEnter( - IntPtr hwndTarget, - IComDataObject pDataObj, - ref Point ppt, - DROPEFFECT dwEffect); - - [PreserveSig] - HRESULT DragLeave(); - - [PreserveSig] - HRESULT DragOver( - ref Point ppt, - DROPEFFECT dwEffect); - - [PreserveSig] - HRESULT Drop( - IComDataObject pDataObj, - ref Point ppt, - DROPEFFECT dwEffect); - - [PreserveSig] - HRESULT Show( - BOOL fShow); - } - } -} diff --git a/src/System.Windows.Forms.Primitives/src/NativeMethods.json b/src/System.Windows.Forms.Primitives/src/NativeMethods.json index 78bf6523ad5..93cd32561e6 100644 --- a/src/System.Windows.Forms.Primitives/src/NativeMethods.json +++ b/src/System.Windows.Forms.Primitives/src/NativeMethods.json @@ -52,6 +52,12 @@ "IDispatchEx.GetTypeInfoCount", "IDispatchEx.Invoke", "IDispatchEx.InvokeEx", + "IDragSourceHelper2.SetFlags", + "IDragSourceHelper2.InitializeFromBitmap", + "IDropTargetHelper.DragEnter", + "IDropTargetHelper.DragLeave", + "IDropTargetHelper.DragOver", + "IDropTargetHelper.Drop", "IEnumConnectionPoints.Next", "IEnumOLEVERB.Clone", "IEnumOLEVERB.Next", @@ -364,6 +370,11 @@ "ITypeInfo.ReleaseTypeAttr", "ITypeLib.GetLibAttr", "ITypeLib.GetTypeInfoOfGuid", + "IUIAutomation.GetFocusedElement", + "IUIAutomationElement.get_CurrentBoundingRectangle", + "IUIAutomationElement.get_CurrentName", + "IUIAutomationElement.GetClickablePoint", + "IUIAutomationElement.SetFocus", "IUnknown.QueryInterface", "IValueProvider.get_IsReadOnly", "IValueProvider.get_Value", diff --git a/src/System.Windows.Forms.Primitives/src/NativeMethods.txt b/src/System.Windows.Forms.Primitives/src/NativeMethods.txt index 53616828b48..380fb3f48ee 100644 --- a/src/System.Windows.Forms.Primitives/src/NativeMethods.txt +++ b/src/System.Windows.Forms.Primitives/src/NativeMethods.txt @@ -293,8 +293,10 @@ IDataObject IDC_* IDispatchEx IDropSource +IDragSourceHelper2 IDropSourceNotify IDropTarget +IDropTargetHelper IEnumFORMATETC IEnumOLEVERB IEnumString @@ -414,6 +416,8 @@ ITextProvider ITextProvider2 ITextRangeProvider IToggleProvider +IUIAutomation +IUIAutomationElement IValueProvider IVBFormat IVBGetControl @@ -548,7 +552,9 @@ OleCreatePropertyFrame OleCreatePropertyFrameIndirect OleDuplicateData OleFlushClipboard +OleGetClipboard OleInitialize +OleSetClipboard OleUninitialize OPEN_FILENAME_FLAGS OPEN_FILENAME_FLAGS_EX 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 836402e572a..b2e4d5a52bb 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs @@ -14,7 +14,7 @@ using Windows.Win32.System.Ole; using Windows.Win32.UI.Accessibility; using Windows.Win32.UI.Input.KeyboardAndMouse; -using static Interop; +using Com = Windows.Win32.System.Com; using ComTypes = System.Runtime.InteropServices.ComTypes; using Encoding = System.Text.Encoding; @@ -5198,12 +5198,44 @@ public DragDropEffects DoDragDrop(object data, DragDropEffects allowedEffects) /// value. /// /// - public DragDropEffects DoDragDrop( + public unsafe DragDropEffects DoDragDrop( object data, DragDropEffects allowedEffects, Bitmap? dragImage, Point cursorOffset, bool useDefaultDragImage) + { + ComTypes.IDataObject dataObject = PrepareIncomingDragData(data); + + DROPEFFECT finalEffect; + + try + { + using var dropSource = ComHelpers.GetComScope( + new DropSource(this, dataObject, dragImage, cursorOffset, useDefaultDragImage)); + using var dataScope = ComHelpers.GetComScope(dataObject); + if (PInvoke.DoDragDrop(dataScope, dropSource, (DROPEFFECT)(uint)allowedEffects, out finalEffect).Failed) + { + return DragDropEffects.None; + } + } + finally + { + if (DragDropHelper.IsInDragLoop(dataObject)) + { + DragDropHelper.SetInDragLoop(dataObject, inDragLoop: false); + } + } + + return (DragDropEffects)finalEffect; + } + + /// + /// Prepares the incoming drag data for consumption. + /// The incoming should implement to be taken as is. + /// Otherwise, the data will be wrapped in a . + /// + private static ComTypes.IDataObject PrepareIncomingDragData(object data) { ComTypes.IDataObject dataObject; @@ -5227,25 +5259,7 @@ public DragDropEffects DoDragDrop( dataObject = iwdata; } - DROPEFFECT finalEffect; - - try - { - IDropSource.Interface dropSource = new DropSource(this, dataObject, dragImage, cursorOffset, useDefaultDragImage); - if (Ole32.DoDragDrop(dataObject, dropSource, (DROPEFFECT)(uint)allowedEffects, out finalEffect).Failed) - { - return DragDropEffects.None; - } - } - finally - { - if (DragDropHelper.IsInDragLoop(dataObject)) - { - DragDropHelper.SetInDragLoop(dataObject, inDragLoop: false); - } - } - - return (DragDropEffects)finalEffect; + return dataObject; } public void DrawToBitmap(Bitmap bitmap, Rectangle targetBounds) 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 b46ccbdc3e8..e44da513732 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 @@ -183,7 +183,7 @@ public HRESULT QueryAcceptData(Com.IDataObject* lpdataobj, ushort* lpcfFormat, R if (CanShowImage(e)) { UpdateDropDescription(e); - DragDropHelper.DragEnter(_owner.Handle, e); + DragDropHelper.DragEnter(_owner.HWND, e); } } else 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 1d54c41e1b2..0f59e1c8d9e 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 @@ -290,7 +290,7 @@ private void UpdateDropTarget(IDropTarget? newTarget, DragEventArgs e) if (dragEnterArgs.DropImageType > DropImageType.Invalid && _owner is ToolStrip toolStrip && toolStrip.IsHandleCreated) { DragDropHelper.SetDropDescription(dragEnterArgs); - DragDropHelper.DragEnter(toolStrip.Handle, dragEnterArgs); + DragDropHelper.DragEnter(toolStrip.HWND, dragEnterArgs); } } } 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 3e4ba61b1a9..7faf28825c3 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 @@ -7,8 +7,8 @@ using System.Drawing.Design; using System.Drawing.Imaging; using System.Windows.Forms.Layout; +using Com = Windows.Win32.System.Com; using Windows.Win32.System.Ole; -using static Interop; using IComDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; namespace System.Windows.Forms; @@ -2211,11 +2211,9 @@ public DragDropEffects DoDragDrop(object data, DragDropEffects allowedEffects) /// /// [EditorBrowsable(EditorBrowsableState.Advanced)] - public DragDropEffects DoDragDrop(object data, DragDropEffects allowedEffects, Bitmap? dragImage, Point cursorOffset, bool useDefaultDragImage) + public unsafe DragDropEffects DoDragDrop(object data, DragDropEffects allowedEffects, Bitmap? dragImage, Point cursorOffset, bool useDefaultDragImage) { - IComDataObject? dataObject = data as IComDataObject; - - if (dataObject is null) + if (data is not IComDataObject dataObject) { DataObject? iwdata = null; if (data is IDataObject idataObject) @@ -2246,8 +2244,9 @@ public DragDropEffects DoDragDrop(object data, DragDropEffects allowedEffects, B try { - IDropSource.Interface dropSource = CreateDropSource(dataObject, dragImage, cursorOffset, useDefaultDragImage); - if (Ole32.DoDragDrop(dataObject, dropSource, (DROPEFFECT)(uint)allowedEffects, out finalEffect).Failed) + using var dropSource = ComHelpers.GetComScope(CreateDropSource(dataObject, dragImage, cursorOffset, useDefaultDragImage)); + using var dataObjectScope = ComHelpers.GetComScope(dataObject); + if (PInvoke.DoDragDrop(dataObjectScope, dropSource, (DROPEFFECT)(uint)allowedEffects, out finalEffect).Failed) { return DragDropEffects.None; } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/Clipboard.cs b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/Clipboard.cs index e24d7a13029..632339cde8b 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/Clipboard.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/Clipboard.cs @@ -4,7 +4,8 @@ using System.Collections.Specialized; using System.Drawing; using System.Runtime.InteropServices; -using static Interop; +using Windows.Win32.System.Com; +using Com = Windows.Win32.System.Com; using IComDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; namespace System.Windows.Forms; @@ -29,7 +30,7 @@ public static void SetDataObject(object data, bool copy) /// Places data on the system and uses copy to specify whether the data /// should remain on the after the application exits. /// - public static void SetDataObject(object data, bool copy, int retryTimes, int retryDelay) + public static unsafe void SetDataObject(object data, bool copy, int retryTimes, int retryDelay) { if (Application.OleRequired() != ApartmentState.STA) { @@ -40,11 +41,11 @@ public static void SetDataObject(object data, bool copy, int retryTimes, int ret ArgumentOutOfRangeException.ThrowIfNegative(retryTimes); ArgumentOutOfRangeException.ThrowIfNegative(retryDelay); - IComDataObject dataObject = data as IComDataObject ?? new DataObject(data); + using var dataObject = ComHelpers.GetComScope(data is IComDataObject ? data : new DataObject(data)); HRESULT hr; int retry = retryTimes; - while ((hr = Ole32.OleSetClipboard(dataObject)) != HRESULT.S_OK) + while ((hr = PInvoke.OleSetClipboard(dataObject)).Failed) { if (--retry < 0) { @@ -57,7 +58,7 @@ public static void SetDataObject(object data, bool copy, int retryTimes, int ret if (copy) { retry = retryTimes; - while ((hr = PInvoke.OleFlushClipboard()) != HRESULT.S_OK) + while ((hr = PInvoke.OleFlushClipboard()).Failed) { if (--retry < 0) { @@ -72,7 +73,7 @@ public static void SetDataObject(object data, bool copy, int retryTimes, int ret /// /// Retrieves the data that is currently on the system . /// - public static IDataObject? GetDataObject() + public static unsafe IDataObject? GetDataObject() { if (Application.OleRequired() != ApartmentState.STA) { @@ -82,9 +83,9 @@ public static void SetDataObject(object data, bool copy, int retryTimes, int ret } int retryTimes = 10; - IComDataObject? dataObject = null; + ComScope proxyDataObject = new(null); HRESULT hr; - while ((hr = Ole32.OleGetClipboard(ref dataObject)) != HRESULT.S_OK) + while ((hr = PInvoke.OleGetClipboard(proxyDataObject)).Failed) { if (--retryTimes < 0) { @@ -94,9 +95,32 @@ public static void SetDataObject(object data, bool copy, int retryTimes, int ret Thread.Sleep(millisecondsTimeout: 100); } - return dataObject is null - ? null - : dataObject is IDataObject ido && !Marshal.IsComObject(dataObject) + // OleGetClipboard always returns a proxy. The proxy forwards all IDataObject method calls to the real data object, + // without giving out the real data object. If the real data object is not one of our CCWs, marshal will know how to + // retrieve the original object using the proxy. However, if the original object is one of our own, we need the real + // data object in order to retrieve the original object via ComWrappers. In order to retrieve the real data object, + // we must query for an interface that is not known to the proxy e.g. IComCallableWrapper. If we are able to query + // for IComCallableWrapper it means that the real data object is one of our CCWs and we've retrieved it successfully, + // otherwise it is not ours and we will be able to retrieve the original object with the proxy. + IUnknown* target = default; + var realDataObject = proxyDataObject.TryQuery(out hr); + if (hr.Succeeded) + { + target = realDataObject.AsUnknown; + proxyDataObject.Dispose(); + } + else + { + target = proxyDataObject.AsUnknown; + } + + if (!ComHelpers.TryGetObjectForIUnknown(target, out IComDataObject? dataObject)) + { + target->Release(); + return null; + } + + return dataObject is IDataObject ido && !Marshal.IsComObject(dataObject) ? ido : new DataObject(dataObject); } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.ComDataObjectAdapter.cs b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.ComDataObjectAdapter.cs index 73904560c64..faeddcaa516 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.ComDataObjectAdapter.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataObject.ComDataObjectAdapter.cs @@ -10,7 +10,6 @@ using System.Runtime.Serialization.Formatters.Binary; using System.Text; using System.Windows.Forms.BinaryFormat; -using static Interop; using static Windows.Win32.System.Memory.GLOBAL_ALLOC_FLAGS; using Com = Windows.Win32.System.Com; using IComDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; @@ -259,7 +258,8 @@ static unsafe Stream ReadByteStreamFromHGLOBAL(HGLOBAL hglobal, out bool isSeria } finally { - Ole32.ReleaseStgMedium(ref medium); + var comMedium = (Com.STGMEDIUM)medium; + PInvoke.ReleaseStgMedium(ref comMedium); } return data; @@ -311,7 +311,8 @@ static unsafe Stream ReadByteStreamFromHGLOBAL(HGLOBAL hglobal, out bool isSeria } finally { - Ole32.ReleaseStgMedium(ref medium); + var comMedium = (Com.STGMEDIUM)medium; + PInvoke.ReleaseStgMedium(ref comMedium); } return data; @@ -377,7 +378,8 @@ static unsafe Stream ReadByteStreamFromHGLOBAL(HGLOBAL hglobal, out bool isSeria PInvokeCore.GlobalFree(hglobal); } - Ole32.ReleaseStgMedium(ref medium); + var comMedium = (Com.STGMEDIUM)medium; + PInvoke.ReleaseStgMedium(ref comMedium); } } } 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 99e99d81316..61163f3400e 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 @@ -4,7 +4,7 @@ using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using Windows.Win32.System.Ole; -using static Interop; +using Com = Windows.Win32.System.Com; namespace System.Windows.Forms; @@ -109,7 +109,8 @@ private static STGMEDIUM CopyData(short format, STGMEDIUM mediumSource) } catch { - Ole32.ReleaseStgMedium(ref mediumDestination); + var comMedium = (Com.STGMEDIUM)mediumDestination; + PInvoke.ReleaseStgMedium(ref comMedium); return default; } } @@ -119,7 +120,8 @@ private static STGMEDIUM CopyData(short format, STGMEDIUM mediumSource) /// private void ReleaseData() { - Ole32.ReleaseStgMedium(ref _medium); + var comMedium = (Com.STGMEDIUM)_medium; + PInvoke.ReleaseStgMedium(ref comMedium); _medium.pUnkForRelease = null; _medium.tymed = TYMED.TYMED_NULL; _medium.unionmember = IntPtr.Zero; 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 7da5098969d..bc698519a31 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 @@ -6,12 +6,10 @@ using System.Runtime.InteropServices; using Windows.Win32.System.Com; using Windows.Win32.System.Ole; -using Shell = Windows.Win32.UI.Shell; +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 static Interop; -using static Interop.Shell32; -using static Windows.Win32.System.Memory.GLOBAL_ALLOC_FLAGS; namespace System.Windows.Forms; @@ -19,7 +17,7 @@ namespace System.Windows.Forms; /// 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 class DragDropHelper +internal static unsafe class DragDropHelper { /// /// A format used internally by the drag image manager. @@ -32,8 +30,8 @@ internal static class DragDropHelper 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. + /// 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"; @@ -58,7 +56,7 @@ internal static class DragDropHelper internal const string USINGDEFAULTDRAGIMAGE = "UsingDefaultDragImage"; /// - /// Sets the drop object image and accompanying text back to the default. + /// Sets the drop object image and accompanying text back to the default. /// public static void ClearDropDescription(IDataObject? dataObject) { @@ -71,10 +69,10 @@ public static void ClearDropDescription(IDataObject? dataObject) } /// - /// Notifies the drag-image manager that the drop target has been entered, and provides it with the information - /// needed to display the drag image. + /// 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(IntPtr targetWindowHandle, DragEventArgs e) + public static void DragEnter(HWND targetWindowHandle, DragEventArgs e) { if (e.Data is not IComDataObject dataObject) { @@ -86,88 +84,66 @@ public static void DragEnter(IntPtr targetWindowHandle, DragEventArgs e) } /// - /// Notifies the drag-image manager that the drop target has been entered, and provides it with the information - /// needed to display the drag image. + /// 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(IntPtr targetWindowHandle, IComDataObject dataObject, ref Point point, DROPEFFECT effect) + public static void DragEnter(HWND targetWindowHandle, IComDataObject dataObject, ref Point point, DROPEFFECT effect) { - if (!TryGetDragDropHelper(out IDropTargetHelper? dropTargetHelper)) + using ComScope dropTargetHelper = new(null); + if (!TryGetDragDropHelper(dropTargetHelper)) { return; } - try - { - dropTargetHelper.DragEnter(targetWindowHandle, dataObject, ref point, effect); - } - finally - { - Marshal.ReleaseComObject(dropTargetHelper); - } + using var dataObjectScope = ComHelpers.GetComScope(dataObject); + dropTargetHelper.Value->DragEnter(targetWindowHandle, dataObjectScope, in point, effect); } /// - /// Notifies the drag-image manager that the cursor has left the drop target. + /// Notifies the drag-image manager that the cursor has left the drop target. /// public static void DragLeave() { - if (!TryGetDragDropHelper(out IDropTargetHelper? dropTargetHelper)) + using ComScope dropTargetHelper = new(null); + if (!TryGetDragDropHelper(dropTargetHelper)) { return; } - try - { - dropTargetHelper.DragLeave(); - } - finally - { - Marshal.ReleaseComObject(dropTargetHelper); - } + dropTargetHelper.Value->DragLeave(); } /// - /// Notifies the drag-image manager that the cursor position has changed and provides it with the information needed - /// to display the drag image. + /// 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) { - if (!TryGetDragDropHelper(out IDropTargetHelper? dropTargetHelper)) + using ComScope dropTargetHelper = new(null); + if (!TryGetDragDropHelper(dropTargetHelper)) { return; } - try - { - Point point = new(e.X, e.Y); - dropTargetHelper.DragOver(ref point, (DROPEFFECT)(uint)e.Effect); - } - finally - { - Marshal.ReleaseComObject(dropTargetHelper); - } + Point point = new(e.X, e.Y); + dropTargetHelper.Value->DragOver(in 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. + /// 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) { - if (!TryGetDragDropHelper(out IDropTargetHelper? dropTargetHelper) || e.Data is not IComDataObject dataObject) + using ComScope dropTargetHelper = new(null); + if (!TryGetDragDropHelper(dropTargetHelper) || e.Data is not IComDataObject dataObject) { return; } - try - { - Point point = new(e.X, e.Y); - dropTargetHelper.Drop(dataObject, ref point, (DROPEFFECT)(uint)e.Effect); - } - finally - { - Marshal.ReleaseComObject(dropTargetHelper); - } + Point point = new(e.X, e.Y); + using var dataObjectScope = ComHelpers.GetComScope(dataObject); + dropTargetHelper.Value->Drop(dataObjectScope, in point, (DROPEFFECT)(uint)e.Effect); } /// @@ -204,7 +180,8 @@ private static unsafe bool GetBooleanFormat(IComDataObject dataObject, string fo finally { PInvokeCore.GlobalUnlock((HGLOBAL)medium.unionmember); - Ole32.ReleaseStgMedium(ref medium); + var comMedium = (STGMEDIUM)medium; + PInvoke.ReleaseStgMedium(ref comMedium); } } @@ -212,7 +189,7 @@ private static unsafe bool GetBooleanFormat(IComDataObject dataObject, string fo /// Determines whether the data object is in a drag loop. /// /// - /// if is in a drag-and-drop loop; otherwise . + /// if is in a drag-and-drop loop; otherwise . /// public static unsafe bool IsInDragLoop(IDataObject dataObject) { @@ -238,15 +215,12 @@ public static unsafe bool IsInDragLoop(IDataObject dataObject) } /// - /// Determines whether the data object is in a drag loop. + /// Determines whether the data object is in a drag loop. /// /// - /// if is in a drag-and-drop loop; otherwise . + /// if is in a drag-and-drop loop; otherwise . /// - public static bool IsInDragLoop(IComDataObject dataObject) - { - return GetBooleanFormat(dataObject, PInvoke.CFSTR_INDRAGLOOP); - } + public static bool IsInDragLoop(IComDataObject dataObject) => GetBooleanFormat(dataObject, PInvoke.CFSTR_INDRAGLOOP); /// /// Determines whether the specified format is a drag loop format. @@ -263,7 +237,7 @@ public static bool IsInDragLoopFormat(FORMATETC format) } /// - /// Releases formats used by the drag-and-drop helper. + /// Releases formats used by the drag-and-drop helper. /// public static void ReleaseDragDropFormats(IComDataObject comDataObject) { @@ -282,7 +256,7 @@ public static void ReleaseDragDropFormats(IComDataObject comDataObject) } /// - /// Sets boolean formats into a data object used to set and display drag images and drop descriptions. + /// 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) { @@ -324,14 +298,14 @@ private static unsafe void SetBooleanFormat(IComDataObject dataObject, string fo } /// - /// Initializes the drag-image manager and sets the drag image bitmap into a data object. + /// Initializes the drag-image manager and sets the drag image bitmap into a data object. /// /// - /// - /// Because InitializeFromBitmap always performs the RGB multiplication step in calculating the alpha value, you should - /// always pass a bitmap without pre-multiplied alpha blending. Note that no error will result from passing a bitmap - /// with pre-multiplied alpha blending, but this method will multiply it again, doubling the resulting alpha value. - /// + /// + /// Because InitializeFromBitmap always performs the RGB multiplication step in calculating the alpha value, you should + /// always pass a bitmap without pre-multiplied alpha blending. Note that no error will result from passing a bitmap + /// 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) { @@ -340,20 +314,21 @@ public static void SetDragImage(IComDataObject dataObject, GiveFeedbackEventArgs } /// - /// Initializes the drag-image manager and sets the drag image bitmap into a data object. + /// Initializes the drag-image manager and sets the drag image bitmap into a data object. /// /// - /// - /// Because InitializeFromBitmap always performs the RGB multiplication step in calculating the alpha value, you should - /// always pass a bitmap without pre-multiplied alpha blending. Note that no error will result from passing a bitmap - /// with pre-multiplied alpha blending, but this method will multiply it again, doubling the resulting alpha value. - /// + /// + /// Because InitializeFromBitmap always performs the RGB multiplication step in calculating the alpha value, you should + /// always pass a bitmap without pre-multiplied alpha blending. Note that no error will result from passing a bitmap + /// 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) { ArgumentNullException.ThrowIfNull(dataObject); - if (!TryGetDragDropHelper(out IDragSourceHelper2? dragSourceHelper)) + using ComScope dragSourceHelper = new(null); + if (!TryGetDragDropHelper(dragSourceHelper)) { return; } @@ -368,34 +343,30 @@ public static void SetDragImage(IComDataObject dataObject, Bitmap? dragImage, Po SetInDragLoop(dataObject, inDragLoop: true); } - HBITMAP hbmpDragImage = (HBITMAP)IntPtr.Zero; + using HBITMAP hbmpDragImage = dragImage is not null ? dragImage.GetHBITMAP() : HBITMAP.Null; - try + // 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. + SHDRAGIMAGE shDragImage = new() { - // 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. - hbmpDragImage = dragImage is not null ? dragImage.GetHBITMAP() : hbmpDragImage; - Shell.SHDRAGIMAGE shDragImage = new() - { - hbmpDragImage = hbmpDragImage, - sizeDragImage = dragImage is not null ? dragImage.Size : default, - ptOffset = cursorOffset, - crColorKey = (COLORREF)PInvoke.GetSysColor(SYS_COLOR_INDEX.COLOR_WINDOW) - }; + hbmpDragImage = hbmpDragImage, + sizeDragImage = dragImage is not null ? dragImage.Size : default, + ptOffset = cursorOffset, + crColorKey = (COLORREF)PInvoke.GetSysColor(SYS_COLOR_INDEX.COLOR_WINDOW) + }; - // Allow text specified in DROPDESCRIPTION to be displayed on the drag image. If you pass a drag image into an IDragSourceHelper - // object, then by default, the extra text description of the drag-and-drop operation is not displayed. - dragSourceHelper.SetFlags((int)DSH_FLAGS.DSH_ALLOWDROPDESCRIPTIONTEXT).ThrowOnFailure(); - dragSourceHelper.InitializeFromBitmap(shDragImage, dataObject).ThrowOnFailure(); - } - catch + // Allow text specified in DROPDESCRIPTION to be displayed on the drag image. If you pass a drag image into an IDragSourceHelper + // object, then by default, the extra text description of the drag-and-drop operation is not displayed. + if (dragSourceHelper.Value->SetFlags((uint)DSH_FLAGS.DSH_ALLOWDROPDESCRIPTIONTEXT).Failed) { PInvokeCore.DeleteObject(hbmpDragImage); return; } - finally + + using var dataObjectScope = ComHelpers.GetComScope(dataObject); + if (dragSourceHelper.Value->InitializeFromBitmap(shDragImage, dataObjectScope).Failed) { - Marshal.ReleaseComObject(dragSourceHelper); + return; } // To effectively display the drop description after changing the drag image bitmap, set IsShowingText to true; @@ -405,7 +376,7 @@ 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. + /// Sets the drop description into a data object. Describes the image and accompanying text for a drop object. /// public static void SetDropDescription(DragEventArgs e) { @@ -420,13 +391,13 @@ public static void SetDropDescription(DragEventArgs e) } /// - /// Sets the drop description into a data object. Describes the image and accompanying text for a drop object. + /// Sets the drop description into a data object. Describes the image and accompanying text for a drop object. /// /// - /// - /// Some UI coloring is applied to the text in if used by specifying %1 in - /// . The characters %% and %1 are the subset of FormatMessage markers that are processed here. - /// + /// + /// Some UI coloring is applied to the text in if used by specifying %1 in + /// . The characters %% and %1 are the subset of FormatMessage markers that are processed here. + /// /// public static unsafe void SetDropDescription( IComDataObject dataObject, @@ -497,12 +468,12 @@ public static unsafe void SetDropDescription( } /// - /// Sets the InDragLoop format into a data object. + /// Sets the InDragLoop format into a data object. /// /// - /// - /// Used to determine whether the data object is in a drag loop. - /// + /// + /// Used to determine whether the data object is in a drag loop. + /// /// public static void SetInDragLoop(IComDataObject dataObject, bool inDragLoop) { @@ -516,56 +487,45 @@ public static void SetInDragLoop(IComDataObject dataObject, bool inDragLoop) } /// - /// Sets the IsShowingText format into a data object. + /// Sets the IsShowingText format into a data object. /// /// - /// - /// To effectively display the drop description after changing the drag image bitmap, set - /// to true; otherwise the drop description text will not be displayed. - /// + /// + /// To effectively display the drop description after changing the drag image bitmap, set + /// to true; otherwise the drop description text will not be displayed. + /// /// private static void SetIsShowingText(IComDataObject dataObject, bool isShowingText) => SetBooleanFormat(dataObject, ISSHOWINGTEXT, isShowingText); /// - /// Sets the UsingDefaultDragImage format into a data object. + /// Sets the UsingDefaultDragImage format into a data object. /// /// - /// - /// Specify for to use a layered window drag image with a size of 96x96. - /// + /// + /// Specify for to use a layered window drag image with a size of 96x96. + /// /// private static void SetUsingDefaultDragImage(IComDataObject dataObject, bool usingDefaultDragImage) => SetBooleanFormat(dataObject, USINGDEFAULTDRAGIMAGE, usingDefaultDragImage); /// - /// Creates an in-process server drag-image manager object and returns the specified interface pointer. + /// Creates an in-process server drag-image manager object and returns the specified interface pointer. /// - private static bool TryGetDragDropHelper([NotNullWhen(true)] out TDragDropHelper? dragDropHelper) + private static bool TryGetDragDropHelper(TDragHelper** dragDropHelper) + where TDragHelper : unmanaged, IComIID { if (Control.CheckForIllegalCrossThreadCalls && Application.OleRequired() != ApartmentState.STA) { throw new InvalidOperationException(SR.ThreadMustBeSTA); } - try - { - HRESULT hr = Ole32.CoCreateInstance( - in CLSID.DragDropHelper, - IntPtr.Zero, - CLSCTX.CLSCTX_INPROC_SERVER, - in IID.GetRef(), - out object obj); - - if (hr.Succeeded) - { - dragDropHelper = (TDragDropHelper)obj; - return true; - } - } - catch { } + HRESULT hr = PInvokeCore.CoCreateInstance( + in CLSID.DragDropHelper, + pUnkOuter: null, + CLSCTX.CLSCTX_INPROC_SERVER, + out *dragDropHelper); - dragDropHelper = default; - return false; + return hr.Succeeded; } } 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 820b6ce3f16..4208c2043ac 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 @@ -2,11 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; +using Windows.Win32.System.Com; using Windows.Win32.System.SystemServices; -using Ole = Windows.Win32.System.Ole; using Com = Windows.Win32.System.Com; using ComTypes = System.Runtime.InteropServices.ComTypes; -using Windows.Win32.System.Com; +using Ole = Windows.Win32.System.Ole; namespace System.Windows.Forms; @@ -15,7 +15,7 @@ internal unsafe class DropTarget : Ole.IDropTarget.Interface, IManagedWrapper
    + /// Prepares the drag data to be passed out. + /// should implement to be passed out as is. + /// Otherwise, the data will be wrapped in a . + ///
+ private static IDataObject? PrepareOutgoingDropData(object dataObj) + { + if (dataObj is IDataObject dataObject) + { + return dataObject; + } + else if (dataObj is ComTypes.IDataObject nativeDataObject) + { + return new DataObject(nativeDataObject); + } + + return null; // Unknown data object interface; we can't work with this so return null + } + private DragEventArgs? CreateDragEventArgs(Com.IDataObject* pDataObj, MODIFIERKEYS_FLAGS grfKeyState, POINTL pt, Ole.DROPEFFECT pdwEffect) { IDataObject? data; @@ -58,18 +77,10 @@ private void ClearDropDescription() } else { - object obj = ComHelpers.GetObjectForIUnknown((Com.IUnknown*)pDataObj); - if (obj is IDataObject dataObject) - { - data = dataObject; - } - else if (obj is ComTypes.IDataObject nativeDataObject) - { - data = new DataObject(nativeDataObject); - } - else + data = PrepareOutgoingDropData(ComHelpers.GetObjectForIUnknown((IUnknown*)pDataObj)); + if (data is null) { - return null; // Unknown data object interface; we can't work with this so return null + return null; } } diff --git a/src/System.Windows.Forms/tests/IntegrationTests/UIIntegrationTests/DragDropTests.cs b/src/System.Windows.Forms/tests/IntegrationTests/UIIntegrationTests/DragDropTests.cs index 970e0a20a26..95ebd897c64 100644 --- a/src/System.Windows.Forms/tests/IntegrationTests/UIIntegrationTests/DragDropTests.cs +++ b/src/System.Windows.Forms/tests/IntegrationTests/UIIntegrationTests/DragDropTests.cs @@ -3,12 +3,10 @@ using System.ComponentModel; using System.Drawing; -using System.Runtime.InteropServices; using Windows.Win32.System.Com; +using Windows.Win32.UI.Accessibility; using Windows.Win32.UI.WindowsAndMessaging; using Xunit.Abstractions; -using static Interop; -using static UiaClient; namespace System.Windows.Forms.UITests; @@ -126,149 +124,148 @@ await InputSimulator.SendAsync( [WinFormsFact(Skip = "Crashes dotnet.exe, see: https://github.com/dotnet/winforms/issues/8598")] public async Task DragDrop_RTF_FromExplorer_ToRichTextBox_ReturnsExpected_Async() { - await RunTestAsync(async dragDropForm => + await RunTestAsync(dragDropForm => { string dragAcceptRtfDestPath = string.Empty; string dragDropDirectory = Path.Combine(Directory.GetCurrentDirectory(), DragDrop); - IUIAutomation? uiAutomation = null; - IUIAutomationElement? uiAutomationElement = null; + RunTest(); - try + unsafe void RunTest() { - string dragAcceptRtfSourcePath = Path.Combine(Directory.GetCurrentDirectory(), Resources, DragAcceptRtf); + using ComScope uiAutomation = new(null); + using ComScope uiAutomationElement = new(null); - if (!Directory.Exists(dragDropDirectory)) + try { - Directory.CreateDirectory(dragDropDirectory); - } + string dragAcceptRtfSourcePath = Path.Combine(Directory.GetCurrentDirectory(), Resources, DragAcceptRtf); - dragAcceptRtfDestPath = Path.Combine(dragDropDirectory, DragAcceptRtf); + if (!Directory.Exists(dragDropDirectory)) + { + Directory.CreateDirectory(dragDropDirectory); + } - if (!File.Exists(dragAcceptRtfDestPath)) - { - File.Copy(dragAcceptRtfSourcePath, dragAcceptRtfDestPath); - } + dragAcceptRtfDestPath = Path.Combine(dragDropDirectory, DragAcceptRtf); - string dragAcceptRtfContent = string.Empty; - string dragAcceptRtfTextContent = string.Empty; + if (!File.Exists(dragAcceptRtfDestPath)) + { + File.Copy(dragAcceptRtfSourcePath, dragAcceptRtfDestPath); + } - using (RichTextBox richTextBox = new()) - { - richTextBox.Rtf = File.ReadAllText(dragAcceptRtfDestPath); - dragAcceptRtfContent = richTextBox.Rtf; - dragAcceptRtfTextContent = richTextBox.Text; - } + string dragAcceptRtfContent = string.Empty; + string dragAcceptRtfTextContent = string.Empty; - TestOutputHelper.WriteLine($"dragAcceptRtfPath: {dragAcceptRtfDestPath}"); + using (RichTextBox richTextBox = new()) + { + richTextBox.Rtf = File.ReadAllText(dragAcceptRtfDestPath); + dragAcceptRtfContent = richTextBox.Rtf; + dragAcceptRtfTextContent = richTextBox.Text; + } - // Open the DragDrop directory and set focus on DragAccept.rtf - Process.Start("explorer.exe", $"/select,\"{dragAcceptRtfDestPath}\""); - WaitForExplorer(DragDrop, new Point(dragDropForm.Location.X + dragDropForm.Width, dragDropForm.Location.Y)); - Assert.True(IsExplorerOpen(DragDrop)); + TestOutputHelper.WriteLine($"dragAcceptRtfPath: {dragAcceptRtfDestPath}"); - // Create a CUIAutomation object and obtain the IUIAutomation interface - Assert.True(TryGetUIAutomation(out uiAutomation)); - Assert.NotNull(uiAutomation); + // Open the DragDrop directory and set focus on DragAccept.rtf + Process.Start("explorer.exe", $"/select,\"{dragAcceptRtfDestPath}\""); + WaitForExplorer(DragDrop, new Point(dragDropForm.Location.X + dragDropForm.Width, dragDropForm.Location.Y)); + Assert.True(IsExplorerOpen(DragDrop)); - // Retrieve the UI element that has focus - Assert.Equal((int)HRESULT.S_OK, uiAutomation?.GetFocusedElement(out uiAutomationElement)); - Assert.NotNull(uiAutomationElement); + // Create a CUIAutomation object and obtain the IUIAutomation interface + Assert.True(TryGetUIAutomation(uiAutomation)); + Assert.False(uiAutomation.IsNull); - string elementName = GetElementName(uiAutomationElement!); - TestOutputHelper.WriteLine($"Focused element name: {elementName}"); + // Retrieve the UI element that has focus + Assert.Equal(HRESULT.S_OK, uiAutomation.Value->GetFocusedElement(uiAutomationElement)); + Assert.False(uiAutomationElement.IsNull); - RECT rect = default; - uiAutomationElement?.get_CurrentBoundingRectangle(out rect); - TestOutputHelper.WriteLine($"Focused element bounding rect: {rect}"); + string elementName = GetElementName(uiAutomationElement); + TestOutputHelper.WriteLine($"Focused element name: {elementName}"); - // Retrieve a point that can be clicked - Point clickable = default; - bool gotClickable = false; - Assert.Equal(HRESULT.S_OK, uiAutomationElement?.GetClickablePoint(out clickable, out gotClickable)); - Assert.True(gotClickable); - TestOutputHelper.WriteLine($"gotClickable: {gotClickable}"); - TestOutputHelper.WriteLine($"clickable: {clickable}"); + uiAutomationElement.Value->get_CurrentBoundingRectangle(out RECT rect); + TestOutputHelper.WriteLine($"Focused element bounding rect: {rect}"); - Point startCoordinates = clickable; - Rectangle endRect = dragDropForm.RichTextBoxDropTarget.ClientRectangle; - Point endCoordinates = dragDropForm.RichTextBoxDropTarget.PointToScreen(GetCenter(endRect)); + // Retrieve a point that can be clicked + Assert.Equal(HRESULT.S_OK, uiAutomationElement.Value->GetClickablePoint(out Point clickable, out BOOL gotClickable)); + Assert.True(gotClickable); + TestOutputHelper.WriteLine($"gotClickable: {gotClickable}"); + TestOutputHelper.WriteLine($"clickable: {clickable}"); - Rectangle vscreen = SystemInformation.VirtualScreen; - Point virtualPointStart = new() - { - X = (startCoordinates.X - vscreen.Left) * DesktopNormalizedMax / vscreen.Width + DesktopNormalizedMax / (vscreen.Width * 2), - Y = (startCoordinates.Y - vscreen.Top) * DesktopNormalizedMax / vscreen.Height + DesktopNormalizedMax / (vscreen.Height * 2) - }; - Point virtualPointEnd = new() - { - X = (endCoordinates.X - vscreen.Left) * DesktopNormalizedMax / vscreen.Width + DesktopNormalizedMax / (vscreen.Width * 2), - Y = (endCoordinates.Y - vscreen.Top) * DesktopNormalizedMax / vscreen.Height + DesktopNormalizedMax / (vscreen.Height * 2) - }; + Point startCoordinates = clickable; + Rectangle endRect = dragDropForm.RichTextBoxDropTarget.ClientRectangle; + Point endCoordinates = dragDropForm.RichTextBoxDropTarget.PointToScreen(GetCenter(endRect)); - TestOutputHelper.WriteLine($"virtualPointStart: {virtualPointStart}"); - TestOutputHelper.WriteLine($"virtualPointEnd: {virtualPointEnd}"); + Rectangle vscreen = SystemInformation.VirtualScreen; + Point virtualPointStart = new() + { + X = (startCoordinates.X - vscreen.Left) * DesktopNormalizedMax / vscreen.Width + DesktopNormalizedMax / (vscreen.Width * 2), + Y = (startCoordinates.Y - vscreen.Top) * DesktopNormalizedMax / vscreen.Height + DesktopNormalizedMax / (vscreen.Height * 2) + }; + Point virtualPointEnd = new() + { + X = (endCoordinates.X - vscreen.Left) * DesktopNormalizedMax / vscreen.Width + DesktopNormalizedMax / (vscreen.Width * 2), + Y = (endCoordinates.Y - vscreen.Top) * DesktopNormalizedMax / vscreen.Height + DesktopNormalizedMax / (vscreen.Height * 2) + }; - Assert.Equal((int)HRESULT.S_OK, uiAutomationElement?.SetFocus()); + TestOutputHelper.WriteLine($"virtualPointStart: {virtualPointStart}"); + TestOutputHelper.WriteLine($"virtualPointEnd: {virtualPointEnd}"); - await InputSimulator.SendAsync( - dragDropForm, - inputSimulator - => inputSimulator.Mouse - .MoveMouseToPositionOnVirtualDesktop(virtualPointStart.X, virtualPointStart.Y) - .LeftButtonDown() - .MoveMouseToPositionOnVirtualDesktop(virtualPointEnd.X, virtualPointEnd.Y) - .MoveMouseToPositionOnVirtualDesktop(virtualPointEnd.X + 2, virtualPointEnd.Y + 2) - .MoveMouseToPositionOnVirtualDesktop(virtualPointEnd.X + 4, virtualPointEnd.Y + 4) - .MoveMouseToPositionOnVirtualDesktop(virtualPointEnd.X + 2, virtualPointEnd.Y + 2) - .MoveMouseToPositionOnVirtualDesktop(virtualPointEnd.X + 4, virtualPointEnd.Y + 4) - .MoveMouseToPositionOnVirtualDesktop(virtualPointEnd.X + 2, virtualPointEnd.Y + 2) - .MoveMouseToPositionOnVirtualDesktop(virtualPointEnd.X + 4, virtualPointEnd.Y + 4) - .MoveMouseToPositionOnVirtualDesktop(virtualPointEnd.X, virtualPointEnd.Y) - .LeftButtonUp()); - - Assert.NotNull(dragDropForm); - Assert.NotNull(dragDropForm.RichTextBoxDropTarget); - - TestOutputHelper.WriteLine($"dragAcceptRtfContent: {dragAcceptRtfContent}"); - TestOutputHelper.WriteLine($"RichTextBoxDropTarget.Rtf: {dragDropForm.RichTextBoxDropTarget.Rtf}"); - Assert.False(string.IsNullOrWhiteSpace(dragDropForm.RichTextBoxDropTarget.Rtf), - $"Actual form.RichTextBoxDropTarget.Rtf: {dragDropForm.RichTextBoxDropTarget.Rtf}"); - - TestOutputHelper.WriteLine($"dragAcceptRtfTextContent: {dragAcceptRtfTextContent}"); - TestOutputHelper.WriteLine($"RichTextBoxDropTarget.Text: {dragDropForm.RichTextBoxDropTarget.Text}"); - Assert.False(string.IsNullOrWhiteSpace(dragDropForm.RichTextBoxDropTarget.Text), - $"Actual form.RichTextBoxDropTarget.Text: {dragDropForm.RichTextBoxDropTarget.Text}"); - - Assert.Equal(dragAcceptRtfContent, dragDropForm.RichTextBoxDropTarget?.Rtf); - Assert.Equal(dragAcceptRtfTextContent, dragDropForm.RichTextBoxDropTarget?.Text); - } - finally - { - if (uiAutomationElement is not null) - { - Marshal.ReleaseComObject(uiAutomationElement); - } + Assert.Equal(HRESULT.S_OK, uiAutomationElement.Value->SetFocus()); - if (uiAutomation is not null) - { - Marshal.ReleaseComObject(uiAutomation); - } + RunInputSimulator(virtualPointStart, virtualPointEnd); - if (IsExplorerOpen(DragDrop)) - { - CloseExplorer(DragDrop); - } + Assert.NotNull(dragDropForm); + Assert.NotNull(dragDropForm.RichTextBoxDropTarget); - if (File.Exists(dragAcceptRtfDestPath)) - { - File.Delete(dragAcceptRtfDestPath); - } + TestOutputHelper.WriteLine($"dragAcceptRtfContent: {dragAcceptRtfContent}"); + TestOutputHelper.WriteLine($"RichTextBoxDropTarget.Rtf: {dragDropForm.RichTextBoxDropTarget.Rtf}"); + Assert.False(string.IsNullOrWhiteSpace(dragDropForm.RichTextBoxDropTarget.Rtf), + $"Actual form.RichTextBoxDropTarget.Rtf: {dragDropForm.RichTextBoxDropTarget.Rtf}"); + + TestOutputHelper.WriteLine($"dragAcceptRtfTextContent: {dragAcceptRtfTextContent}"); + TestOutputHelper.WriteLine($"RichTextBoxDropTarget.Text: {dragDropForm.RichTextBoxDropTarget.Text}"); + Assert.False(string.IsNullOrWhiteSpace(dragDropForm.RichTextBoxDropTarget.Text), + $"Actual form.RichTextBoxDropTarget.Text: {dragDropForm.RichTextBoxDropTarget.Text}"); - if (Directory.Exists(dragDropDirectory)) + Assert.Equal(dragAcceptRtfContent, dragDropForm.RichTextBoxDropTarget?.Rtf); + Assert.Equal(dragAcceptRtfTextContent, dragDropForm.RichTextBoxDropTarget?.Text); + } + finally { - Directory.Delete(dragDropDirectory, true); + if (IsExplorerOpen(DragDrop)) + { + CloseExplorer(DragDrop); + } + + if (File.Exists(dragAcceptRtfDestPath)) + { + File.Delete(dragAcceptRtfDestPath); + } + + if (Directory.Exists(dragDropDirectory)) + { + Directory.Delete(dragDropDirectory, true); + } } } + + return Task.CompletedTask; + + async void RunInputSimulator(Point virtualPointStart, Point virtualPointEnd) + { + await InputSimulator.SendAsync( + dragDropForm, + inputSimulator + => inputSimulator.Mouse + .MoveMouseToPositionOnVirtualDesktop(virtualPointStart.X, virtualPointStart.Y) + .LeftButtonDown() + .MoveMouseToPositionOnVirtualDesktop(virtualPointEnd.X, virtualPointEnd.Y) + .MoveMouseToPositionOnVirtualDesktop(virtualPointEnd.X + 2, virtualPointEnd.Y + 2) + .MoveMouseToPositionOnVirtualDesktop(virtualPointEnd.X + 4, virtualPointEnd.Y + 4) + .MoveMouseToPositionOnVirtualDesktop(virtualPointEnd.X + 2, virtualPointEnd.Y + 2) + .MoveMouseToPositionOnVirtualDesktop(virtualPointEnd.X + 4, virtualPointEnd.Y + 4) + .MoveMouseToPositionOnVirtualDesktop(virtualPointEnd.X + 2, virtualPointEnd.Y + 2) + .MoveMouseToPositionOnVirtualDesktop(virtualPointEnd.X + 4, virtualPointEnd.Y + 4) + .MoveMouseToPositionOnVirtualDesktop(virtualPointEnd.X, virtualPointEnd.Y) + .LeftButtonUp()); + } }); } @@ -505,11 +502,12 @@ private void CloseExplorer(string directory) } } - private string GetElementName(IUIAutomationElement element) + private unsafe string GetElementName(IUIAutomationElement* element) { - if (element.get_CurrentName(out BSTR retVal) == 0) + using BSTR name = default; + if (element->get_CurrentName(&name).Succeeded) { - return retVal.AsSpan().ToString(); + return name.ToString(); } return string.Empty; @@ -542,24 +540,16 @@ await RunFormWithoutControlAsync( }); } - private bool TryGetUIAutomation([NotNullWhen(true)] out IUIAutomation? uiAutomation) + private unsafe bool TryGetUIAutomation(IUIAutomation** uiAutomation) { Guid CLSID_CUIAutomation = new("FF48DBA4-60EF-4201-AA87-54103EEF594E"); - Guid IID_IUIAutomation = new("30CBE57D-D9D0-452A-AB13-7AC5AC4825EE"); - HRESULT hr = Ole32.CoCreateInstance( + HRESULT hr = PInvokeCore.CoCreateInstance( in CLSID_CUIAutomation, - IntPtr.Zero, + pUnkOuter: null, CLSCTX.CLSCTX_INPROC_SERVER, - in IID_IUIAutomation, - out object obj); - if (hr.Succeeded) - { - uiAutomation = (IUIAutomation)obj; - return true; - } + out *uiAutomation); - uiAutomation = default; - return false; + return hr.Succeeded; } private void WaitForExplorer(string directory, Point startPosition) diff --git a/src/System.Windows.Forms/tests/IntegrationTests/UIIntegrationTests/GlobalUsings.cs b/src/System.Windows.Forms/tests/IntegrationTests/UIIntegrationTests/GlobalUsings.cs index 1b309c5461d..4ab8f0d8f00 100644 --- a/src/System.Windows.Forms/tests/IntegrationTests/UIIntegrationTests/GlobalUsings.cs +++ b/src/System.Windows.Forms/tests/IntegrationTests/UIIntegrationTests/GlobalUsings.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. global using System.Diagnostics; -global using System.Diagnostics.CodeAnalysis; global using System.Private.Windows.Core; global using Windows.Win32; global using Windows.Win32.Foundation; diff --git a/src/System.Windows.Forms/tests/IntegrationTests/UIIntegrationTests/Interop/UiaClient/Interop.IUIAutomation.cs b/src/System.Windows.Forms/tests/IntegrationTests/UIIntegrationTests/Interop/UiaClient/Interop.IUIAutomation.cs deleted file mode 100644 index 2dbf579b844..00000000000 --- a/src/System.Windows.Forms/tests/IntegrationTests/UIIntegrationTests/Interop/UiaClient/Interop.IUIAutomation.cs +++ /dev/null @@ -1,123 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.InteropServices; - -internal static partial class UiaClient -{ - [ComImport] - [Guid("30CBE57D-D9D0-452A-AB13-7AC5AC4825EE")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IUIAutomation - { - void CompareElements(); - - void CompareRuntimeIds(); - - void GetRootElement(); - - void ElementFromHandle(); - - void ElementFromPoint(); - - int GetFocusedElement(out IUIAutomationElement element); - - void GetRootElementBuildCache(); - - void ElementFromHandleBuildCache(); - - void ElementFromPointBuildCache(); - - void GetFocusedElementBuildCache(); - - void CreateTreeWalker(); - - void get_ControlViewWalker(); - - void get_ContentViewWalker(); - - void get_RawViewWalker(); - - void get_RawViewCondition(); - - void get_ControlViewCondition(); - - void get_ContentViewCondition(); - - void CreateCacheRequest(); - - void CreateTrueCondition(); - - void CreateFalseCondition(); - - void CreatePropertyCondition(); - - void CreatePropertyConditionEx(); - - void CreateAndCondition(); - - void CreateAndConditionFromArray(); - - void CreateAndConditionFromNativeArray(); - - void CreateOrCondition(); - - void CreateOrConditionFromArray(); - - void CreateOrConditionFromNativeArray(); - - void CreateNotCondition(); - - void AddAutomationEventHandler(); - - void RemoveAutomationEventHandler(); - - void AddPropertyChangedEventHandlerNativeArray(); - - void AddPropertyChangedEventHandler(); - - void RemovePropertyChangedEventHandler(); - - void AddStructureChangedEventHandler(); - - void RemoveStructureChangedEventHandler(); - - void AddFocusChangedEventHandler(); - - void RemoveFocusChangedEventHandler(); - - void RemoveAllEventHandlers(); - - void IntNativeArrayToSafeArray(); - - void IntSafeArrayToNativeArray(); - - void RectToVariant(); - - void VariantToRect(); - - void SafeArrayToRectNativeArray(); - - void CreateProxyFactoryEntry(); - - void get_ProxyFactoryMapping(); - - void GetPropertyProgrammaticName(); - - void GetPatternProgrammaticName(); - - void PollForPotentialSupportedPatterns(); - - void PollForPotentialSupportedProperties(); - - void CheckNotSupported(); - - void get_ReservedNotSupportedValue(); - - void get_ReservedMixedAttributeValue(); - - void ElementFromIAccessible(); - - void ElementFromIAccessibleBuildCache(); - } -} diff --git a/src/System.Windows.Forms/tests/IntegrationTests/UIIntegrationTests/Interop/UiaClient/Interop.IUIAutomationElement.cs b/src/System.Windows.Forms/tests/IntegrationTests/UIIntegrationTests/Interop/UiaClient/Interop.IUIAutomationElement.cs deleted file mode 100644 index ea14756fd67..00000000000 --- a/src/System.Windows.Forms/tests/IntegrationTests/UIIntegrationTests/Interop/UiaClient/Interop.IUIAutomationElement.cs +++ /dev/null @@ -1,178 +0,0 @@ -// 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.Runtime.InteropServices; - -internal static partial class UiaClient -{ - [ComImport] - [Guid("D22108AA-8AC5-49A5-837B-37BBB3D7591E")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IUIAutomationElement - { - int SetFocus(); - - void GetRuntimeId(); - - void FindFirst(); - - void FindAll(); - - void FindFirstBuildCache(); - - void FindAllBuildCache(); - - void BuildUpdatedCache(); - - void GetCurrentPropertyValue(); - - void GetCurrentPropertyValueEx(); - - void GetCachedPropertyValue(); - - void GetCachedPropertyValueEx(); - - void GetCurrentPatternAs(); - - void GetCachedPatternAs(); - - void GetCurrentPattern(); - - void GetCachedPattern(); - - void GetCachedParent(); - - void GetCachedChildren(); - - void get_CurrentProcessId(); - - void get_CurrentControlType(); - - void get_CurrentLocalizedControlType(); - - HRESULT get_CurrentName(out BSTR retVal); - - void get_CurrentAcceleratorKey(); - - void get_CurrentAccessKey(); - - void get_CurrentHasKeyboardFocus(); - - void get_CurrentIsKeyboardFocusable(); - - void get_CurrentIsEnabled(); - - void get_CurrentAutomationId(); - - void get_CurrentClassName(); - - void get_CurrentHelpText(); - - void get_CurrentCulture(); - - void get_CurrentIsControlElement(); - - void get_CurrentIsContentElement(); - - void get_CurrentIsPassword(); - - void get_CurrentNativeWindowHandle(); - - void get_CurrentItemType(); - - void get_CurrentIsOffscreen(); - - void get_CurrentOrientation(); - - void get_CurrentFrameworkId(); - - void get_CurrentIsRequiredForForm(); - - void get_CurrentItemStatus(); - - HRESULT get_CurrentBoundingRectangle(out RECT retVal); - - void get_CurrentLabeledBy(); - - void get_CurrentAriaRole(); - - void get_CurrentAriaProperties(); - - void get_CurrentIsDataValidForForm(); - - void get_CurrentControllerFor(); - - void get_CurrentDescribedBy(); - - void get_CurrentFlowsTo(); - - void get_CurrentProviderDescription(); - - void get_CachedProcessId(); - - void get_CachedControlType(); - - void get_CachedLocalizedControlType(); - - void get_CachedName(); - - void get_CachedAcceleratorKey(); - - void get_CachedAccessKey(); - - void get_CachedHasKeyboardFocus(); - - void get_CachedIsKeyboardFocusable(); - - void get_CachedIsEnabled(); - - void get_CachedAutomationId(); - - void get_CachedClassName(); - - void get_CachedHelpText(); - - void get_CachedCulture(); - - void get_CachedIsControlElement(); - - void get_CachedIsContentElement(); - - void get_CachedIsPassword(); - - void get_CachedNativeWindowHandle(); - - void get_CachedItemType(); - - void get_CachedIsOffscreen(); - - void get_CachedOrientation(); - - void get_CachedFrameworkId(); - - void get_CachedIsRequiredForForm(); - - void get_CachedItemStatus(); - - void get_CachedBoundingRectangle(); - - void get_CachedLabeledBy(); - - void get_CachedAriaRole(); - - void get_CachedAriaProperties(); - - void get_CachedIsDataValidForForm(); - - void get_CachedControllerFor(); - - void get_CachedDescribedBy(); - - void get_CachedFlowsTo(); - - void get_CachedProviderDescription(); - - HRESULT GetClickablePoint(out Point clickable, out bool gotClickable); - } -} diff --git a/src/System.Windows.Forms/tests/InteropTests/PropertyGridTests.cs b/src/System.Windows.Forms/tests/InteropTests/PropertyGridTests.cs index 79b23766938..6167e87cb21 100644 --- a/src/System.Windows.Forms/tests/InteropTests/PropertyGridTests.cs +++ b/src/System.Windows.Forms/tests/InteropTests/PropertyGridTests.cs @@ -5,7 +5,6 @@ using System.Runtime.InteropServices; using Windows.Win32.System.ApplicationInstallationAndServicing; using Windows.Win32.System.Com; -using static Interop; namespace System.Windows.Forms.Interop.Tests; @@ -120,26 +119,26 @@ private unsafe void ExecuteWithActivationContext(string applicationManifest, Act } } - private object CreateComObjectWithRawIErrorInfoUsage() + private unsafe object CreateComObjectWithRawIErrorInfoUsage() { Guid clsidRawErrorInfoUsageTest = new("0ED8EE0D-22E3-49EA-850C-E69B20D1F296"); - Ole32.CoCreateInstance(in clsidRawErrorInfoUsageTest, - IntPtr.Zero, + PInvokeCore.CoCreateInstance( + in clsidRawErrorInfoUsageTest, + pUnkOuter: null, CLSCTX.CLSCTX_INPROC_SERVER, - in IID.GetRef(), - out object result); - return result; + out IUnknown* result); + return ComHelpers.GetObjectForIUnknown(result); } - private object CreateComObjectWithStandardIErrorInfoUsage() + private unsafe object CreateComObjectWithStandardIErrorInfoUsage() { Guid clsidStandardErrorInfoUsageTest = new("EA1FCB3A-277C-4C79-AB85-E2ED3E858201"); - Ole32.CoCreateInstance(in clsidStandardErrorInfoUsageTest, - IntPtr.Zero, + PInvokeCore.CoCreateInstance( + in clsidStandardErrorInfoUsageTest, + pUnkOuter: null, CLSCTX.CLSCTX_INPROC_SERVER, - in IID.GetRef(), - out object result); - return result; + out IUnknown* result); + return ComHelpers.GetObjectForIUnknown(result); } [DllImport("kernel32", SetLastError = true)] diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ClipboardTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ClipboardTests.cs index 98dda5694ca..563a5313e0b 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ClipboardTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ClipboardTests.cs @@ -6,6 +6,7 @@ using System.Drawing; using System.Drawing.Imaging; using System.Runtime.Serialization.Formatters.Binary; +using Com = Windows.Win32.System.Com; namespace System.Windows.Forms.Tests; @@ -512,4 +513,26 @@ public void ClipBoard_SetData_CustomFormat_Color_BinaryFormatterDisabled_Seriali Assert.Fail("Formatting should have failed."); } + + [WinFormsFact] + public unsafe void ClipBoard_GetClipboard_ReturnsProxy() + { + DataObject data = new(); + using var dataScope = ComHelpers.GetComScope(data); + PInvoke.OleSetClipboard(dataScope).Succeeded.Should().BeTrue(); + + using ComScope proxy = new(null); + PInvoke.OleGetClipboard(proxy).Succeeded.Should().BeTrue(); + ((nint)proxy.Value).Should().NotBe((nint)dataScope.Value); + + using var dataUnknown = dataScope.Query(); + using var proxyUnknown = proxy.Query(); + ((nint)proxyUnknown.Value).Should().NotBe((nint)dataUnknown.Value); + + // The proxy does not know about this interface, it should give back the real pointer. + using var realDataPointer = proxy.Query(); + using var realDataPointerUnknown = realDataPointer.Query(); + ((nint)proxyUnknown.Value).Should().NotBe((nint)realDataPointerUnknown.Value); + ((nint)dataUnknown.Value).Should().Be((nint)realDataPointerUnknown.Value); + } } diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ComponentModel/Com2Interop/ComNativeDescriptorTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ComponentModel/Com2Interop/ComNativeDescriptorTests.cs index 7dfb9f21eda..3bd912db6ca 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ComponentModel/Com2Interop/ComNativeDescriptorTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ComponentModel/Com2Interop/ComNativeDescriptorTests.cs @@ -11,7 +11,6 @@ using Windows.Win32.System.Variant; using Windows.Win32.UI.Accessibility; using WMPLib; -using static Interop; namespace System.Windows.Forms.ComponentModel.Com2Interop.Tests; @@ -91,16 +90,16 @@ public void ComNativeDescriptor_GetProperties_FromIPicture() public void ComNativeDescriptor_GetProperties_FromActiveXMediaPlayerControl_ComInterop() { Guid guid = typeof(WindowsMediaPlayerClass).GUID; - HRESULT hr = Ole32.CoCreateInstance( + HRESULT hr = PInvokeCore.CoCreateInstance( in guid, - IntPtr.Zero, + pUnkOuter: null, CLSCTX.CLSCTX_INPROC_SERVER, - in IID.GetRef(), - out object mediaPlayer); + out IUnknown* mediaPlayerPtr); Assert.Equal(HRESULT.S_OK, hr); ComNativeDescriptor descriptor = new(); + object mediaPlayer = ComHelpers.GetObjectForIUnknown(mediaPlayerPtr); ValidateMediaPlayerProperties(mediaPlayer, descriptor.GetProperties(mediaPlayer)); } 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 4e4d92335d8..d04ee7b2a04 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 @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Runtime.Serialization; -using Windows.Win32.System.Ole; using Moq; +using Windows.Win32.System.Ole; using IComDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; using Point = System.Drawing.Point; @@ -2426,4 +2426,126 @@ public void IComDataObjectGetDataHere_NoDataPresentNoData_ThrowsCOMException(TYM COMException ex = Assert.Throws(() => iComDataObject.GetDataHere(ref formatetc, ref stgMedium)); Assert.Equal(HRESULT.DV_E_FORMATETC, (HRESULT)ex.HResult); } + + private class DerivedDataObject : DataObject { } + + private class CustomDataObject : IComDataObject, IDataObject + { + public int DAdvise(ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection) => throw new NotImplementedException(); + public void DUnadvise(int connection) => throw new NotImplementedException(); + public int EnumDAdvise(out IEnumSTATDATA enumAdvise) => throw new NotImplementedException(); + public IEnumFORMATETC EnumFormatEtc(DATADIR direction) => throw new NotImplementedException(); + public int GetCanonicalFormatEtc(ref FORMATETC formatIn, out FORMATETC formatOut) => throw new NotImplementedException(); + public void GetData(ref FORMATETC format, out STGMEDIUM medium) => throw new NotImplementedException(); + public object GetData(string format, bool autoConvert) => throw new NotImplementedException(); + public object GetData(string format) => throw new NotImplementedException(); + public object GetData(Type format) => throw new NotImplementedException(); + public void GetDataHere(ref FORMATETC format, ref STGMEDIUM medium) => throw new NotImplementedException(); + public bool GetDataPresent(string format, bool autoConvert) => throw new NotImplementedException(); + public bool GetDataPresent(string format) => throw new NotImplementedException(); + public bool GetDataPresent(Type format) => throw new NotImplementedException(); + public string[] GetFormats(bool autoConvert) => throw new NotImplementedException(); + public string[] GetFormats() => throw new NotImplementedException(); + public int QueryGetData(ref FORMATETC format) => throw new NotImplementedException(); + public void SetData(ref FORMATETC formatIn, ref STGMEDIUM medium, bool release) => throw new NotImplementedException(); + public void SetData(string format, bool autoConvert, object data) => throw new NotImplementedException(); + public void SetData(string format, object data) => throw new NotImplementedException(); + public void SetData(Type format, object data) => throw new NotImplementedException(); + public void SetData(object data) => throw new NotImplementedException(); + } + + public static IEnumerable DataObjectMockRoundTripData() + { + yield return new object[] { new DataObject() }; + yield return new object[] { new DerivedDataObject() }; + yield return new object[] { new CustomDataObject() }; + } + + [WinFormsTheory] + [MemberData(nameof(DataObjectMockRoundTripData))] + public void DataObject_MockRoundTrip_IsSame(object data) + { + dynamic controlAccessor = typeof(Control).TestAccessor().Dynamic; + dynamic dropTargetAccessor = typeof(DropTarget).TestAccessor().Dynamic; + + IComDataObject inData = controlAccessor.PrepareIncomingDragData(data); + inData.Should().BeSameAs(data); + + IDataObject outData = dropTargetAccessor.PrepareOutgoingDropData(inData); + outData.Should().BeSameAs(data); + } + + [WinFormsFact] + public void DataObject_StringData_MockRoundTrip_IsWrapped() + { + string testString = "Test"; + dynamic accessor = typeof(Control).TestAccessor().Dynamic; + dynamic dropTargetAccessor = typeof(DropTarget).TestAccessor().Dynamic; + + IComDataObject inData = accessor.PrepareIncomingDragData(testString); + inData.Should().BeAssignableTo(); + + IDataObject outData = dropTargetAccessor.PrepareOutgoingDropData(inData); + outData.Should().BeSameAs(inData); + outData.GetData(typeof(string)).Should().Be(testString); + } + + [WinFormsFact] + public void DataObject_IDataObject_MockRoundTrip_IsWrapped() + { + CustomIDataObject data = new(); + dynamic accessor = typeof(Control).TestAccessor().Dynamic; + dynamic dropTargetAccessor = typeof(DropTarget).TestAccessor().Dynamic; + + IComDataObject inData = accessor.PrepareIncomingDragData(data); + inData.Should().BeAssignableTo(); + inData.Should().NotBeSameAs(data); + + IDataObject outData = dropTargetAccessor.PrepareOutgoingDropData(inData); + outData.Should().BeSameAs(inData); + } + + private class CustomIDataObject : IDataObject + { + public object GetData(string format, bool autoConvert) => throw new NotImplementedException(); + public object GetData(string format) => throw new NotImplementedException(); + public object GetData(Type format) => throw new NotImplementedException(); + public bool GetDataPresent(string format, bool autoConvert) => throw new NotImplementedException(); + public bool GetDataPresent(string format) => throw new NotImplementedException(); + public bool GetDataPresent(Type format) => throw new NotImplementedException(); + public string[] GetFormats(bool autoConvert) => throw new NotImplementedException(); + public string[] GetFormats() => throw new NotImplementedException(); + public void SetData(string format, bool autoConvert, object data) => throw new NotImplementedException(); + public void SetData(string format, object data) => throw new NotImplementedException(); + public void SetData(Type format, object data) => throw new NotImplementedException(); + public void SetData(object data) => throw new NotImplementedException(); + } + + [WinFormsFact] + public void DataObject_ComTypesIDataObject_MockRoundTrip_IsWrapped() + { + CustomComTypesDataObject data = new(); + dynamic accessor = typeof(Control).TestAccessor().Dynamic; + dynamic dropTargetAccessor = typeof(DropTarget).TestAccessor().Dynamic; + + IComDataObject inData = accessor.PrepareIncomingDragData(data); + inData.Should().BeSameAs(data); + + IDataObject outData = dropTargetAccessor.PrepareOutgoingDropData(inData); + outData.Should().BeAssignableTo(); + outData.Should().NotBeSameAs(inData); + } + + private class CustomComTypesDataObject : IComDataObject + { + public int DAdvise(ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection) => throw new NotImplementedException(); + public void DUnadvise(int connection) => throw new NotImplementedException(); + public int EnumDAdvise(out IEnumSTATDATA enumAdvise) => throw new NotImplementedException(); + public IEnumFORMATETC EnumFormatEtc(DATADIR direction) => throw new NotImplementedException(); + public int GetCanonicalFormatEtc(ref FORMATETC formatIn, out FORMATETC formatOut) => throw new NotImplementedException(); + public void GetData(ref FORMATETC format, out STGMEDIUM medium) => throw new NotImplementedException(); + public void GetDataHere(ref FORMATETC format, ref STGMEDIUM medium) => throw new NotImplementedException(); + public int QueryGetData(ref FORMATETC format) => throw new NotImplementedException(); + public void SetData(ref FORMATETC formatIn, ref STGMEDIUM medium, bool release) => throw new NotImplementedException(); + } }