From 2622cc77459fb6584f46d524ace33258ef5f8e7c Mon Sep 17 00:00:00 2001 From: Andrii Kurdiumov Date: Tue, 26 Apr 2022 21:07:10 +0600 Subject: [PATCH 1/9] Use ComWrappers for Clipboard Create missing RCW and CCW Testing happens using existing test suite which is quite good. --- .../Interop/Ole32/Interop.OleGetClipboard.cs | 16 +- .../Interop/Ole32/Interop.OleSetClipboard.cs | 12 +- .../WinFormsComWrappers.DataObjectWrapper.cs | 142 ++++++++++++++++++ .../WinFormsComWrappers.IDataObjectVtbl.cs | 13 +- .../WinFormsComWrappers.STGMEDIUM_Raw.cs | 18 +++ .../src/Interop/WinFormsComWrappers.cs | 9 ++ .../src/System/Windows/Forms/Clipboard.cs | 13 +- 7 files changed, 205 insertions(+), 18 deletions(-) create mode 100644 src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.DataObjectWrapper.cs create mode 100644 src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.STGMEDIUM_Raw.cs 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 index 766afc16ff8..260b1f3405b 100644 --- a/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.OleGetClipboard.cs +++ b/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.OleGetClipboard.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; @@ -10,6 +11,19 @@ internal static partial class Interop internal static partial class Ole32 { [DllImport(Libraries.Ole32, ExactSpelling = true)] - public static extern HRESULT OleGetClipboard(ref IDataObject? data); + public static extern HRESULT OleGetClipboard(out IntPtr data); + + public static bool OleGetClipboard([NotNullWhen(true)] out IDataObject? data, out HRESULT result) + { + result = OleGetClipboard(out IntPtr ptr); + if (result == HRESULT.S_OK) + { + data = (IDataObject)WinFormsComWrappers.Instance.GetOrCreateObjectForComInstance(ptr, CreateObjectFlags.Unwrap); + return true; + } + + data = null; + return false; + } } } 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 index 8b3447e0724..a3d6e85cb63 100644 --- a/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.OleSetClipboard.cs +++ b/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.OleSetClipboard.cs @@ -10,6 +10,16 @@ internal static partial class Interop internal static partial class Ole32 { [DllImport(Libraries.Ole32, ExactSpelling = true)] - public static extern HRESULT OleSetClipboard(IDataObject? pDataObj); + private static extern HRESULT OleSetClipboard(IntPtr pDataObj); + + public static HRESULT OleSetClipboard(IDataObject? pDataObj) + { + if (pDataObj == null) + { + return OleSetClipboard(IntPtr.Zero); + } + + return OleSetClipboard(WinFormsComWrappers.Instance.GetComPointer(pDataObj, IID.IDataObject)); + } } } diff --git a/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.DataObjectWrapper.cs b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.DataObjectWrapper.cs new file mode 100644 index 00000000000..2af3710929d --- /dev/null +++ b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.DataObjectWrapper.cs @@ -0,0 +1,142 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Windows.Forms; + +internal partial class Interop +{ + internal unsafe partial class WinFormsComWrappers + { + internal class DataObjectWrapper : IDataObject + { + private IntPtr _wrappedInstance; + + public DataObjectWrapper(IntPtr wrappedInstance) + { + _wrappedInstance = wrappedInstance.OrThrowIfZero(); + } + + internal IntPtr Instance => _wrappedInstance; + + public void Dispose() + { + Marshal.Release(_wrappedInstance); + _wrappedInstance = IntPtr.Zero; + } + + public void GetData(ref FORMATETC format, out STGMEDIUM medium) + { + fixed (FORMATETC* formatPtr = &format) + { + STGMEDIUM_Raw mediumRaw; + ((delegate* unmanaged)(*(*(void***)_wrappedInstance + 3))) + (_wrappedInstance, formatPtr, &mediumRaw).ThrowIfFailed(); + medium = new() + { + pUnkForRelease = mediumRaw.pUnkForRelease == IntPtr.Zero ? null : Marshal.GetObjectForIUnknown(mediumRaw.pUnkForRelease), + tymed = mediumRaw.tymed, + unionmember = mediumRaw.unionmember, + }; + } + } + + public void GetDataHere(ref FORMATETC format, ref STGMEDIUM medium) + { + fixed (FORMATETC* formatPtr = &format) + { + STGMEDIUM_Raw mediumRaw = new() + { + pUnkForRelease = medium.pUnkForRelease == null ? IntPtr.Zero : Marshal.GetIUnknownForObject(medium.pUnkForRelease), + tymed = medium.tymed, + unionmember = medium.unionmember, + }; + ((delegate* unmanaged)(*(*(void***)_wrappedInstance + 4))) + (_wrappedInstance, formatPtr, &mediumRaw).ThrowIfFailed(); + medium = new() + { + pUnkForRelease = mediumRaw.pUnkForRelease == IntPtr.Zero ? null : Marshal.GetObjectForIUnknown(mediumRaw.pUnkForRelease), + tymed = mediumRaw.tymed, + unionmember = mediumRaw.unionmember, + }; + } + } + + public int QueryGetData(ref FORMATETC format) + { + fixed (FORMATETC* formatPtr = &format) + { + return (int)((delegate* unmanaged)(*(*(void***)_wrappedInstance + 5))) + (_wrappedInstance, formatPtr); + } + } + + public int GetCanonicalFormatEtc(ref FORMATETC formatIn, out FORMATETC formatOut) + { + fixed (FORMATETC* formatInPtr = &formatIn) + fixed (FORMATETC* formatOutPtr = &formatOut) + { + return (int)((delegate* unmanaged)(*(*(void***)_wrappedInstance + 6))) + (_wrappedInstance, formatInPtr, formatOutPtr); + } + } + + public void SetData(ref FORMATETC formatIn, ref STGMEDIUM medium, bool release) + { + fixed (FORMATETC* formatInPtr = &formatIn) + { + STGMEDIUM_Raw mediumRaw = new() + { + pUnkForRelease = medium.pUnkForRelease == null ? IntPtr.Zero : Marshal.GetIUnknownForObject(medium.pUnkForRelease), + tymed = medium.tymed, + unionmember = medium.unionmember, + }; + ((delegate* unmanaged)(*(*(void***)_wrappedInstance + 7))) + (_wrappedInstance, formatInPtr, &mediumRaw, release ? 1 : 0).ThrowIfFailed(); + medium = new() + { + pUnkForRelease = mediumRaw.pUnkForRelease == IntPtr.Zero ? null : Marshal.GetObjectForIUnknown(mediumRaw.pUnkForRelease), + tymed = mediumRaw.tymed, + unionmember = mediumRaw.unionmember, + }; + } + } + + public IEnumFORMATETC EnumFormatEtc(DATADIR direction) + { + IntPtr resultPtr; + ((delegate* unmanaged)(*(*(void***)_wrappedInstance + 8))) + (_wrappedInstance, direction, &resultPtr).ThrowIfFailed(); + return (IEnumFORMATETC)WinFormsComWrappers.Instance.GetOrCreateObjectForComInstance(resultPtr, CreateObjectFlags.Unwrap); + } + + public int DAdvise(ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection) + { + fixed (FORMATETC* formatPtr = &pFormatetc) + fixed (int* connectionPtr = &connection) + { + var adviseSinkPtr = WinFormsComWrappers.Instance.GetOrCreateComInterfaceForObject(adviseSink, CreateComInterfaceFlags.None); + return (int)((delegate* unmanaged)(*(*(void***)_wrappedInstance + 9))) + (_wrappedInstance, formatPtr, advf, adviseSinkPtr, connectionPtr); + } + } + + public void DUnadvise(int connection) + { + ((delegate* unmanaged)(*(*(void***)_wrappedInstance + 10))) + (_wrappedInstance, connection).ThrowIfFailed(); + } + + public int EnumDAdvise(out IEnumSTATDATA? enumAdvise) + { + IntPtr enumAdvisePtr; + var result = ((delegate* unmanaged)(*(*(void***)_wrappedInstance + 11))) + (_wrappedInstance, &enumAdvisePtr); + enumAdvise = result.Succeeded() ? null : (IEnumSTATDATA)Marshal.GetObjectForIUnknown(enumAdvisePtr); + return (int)result; + } + } + } +} diff --git a/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.IDataObjectVtbl.cs b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.IDataObjectVtbl.cs index 5476c0c806d..2079fdb76e3 100644 --- a/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.IDataObjectVtbl.cs +++ b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.IDataObjectVtbl.cs @@ -121,6 +121,12 @@ private static HRESULT EnumFormatEtc(IntPtr thisPtr, DATADIR direction, IntPtr* try { var formatEtc = instance.EnumFormatEtc(direction); + if (Marshal.IsComObject(formatEtc)) + { + *pEnumFormatC = Marshal.GetIUnknownForObject(formatEtc); + return HRESULT.S_OK; + } + var result = WinFormsComWrappers.Instance.TryGetComPointer(formatEtc, IID.IEnumFORMATETC, out var formatEtcPtr); if (result.Failed()) { @@ -174,13 +180,6 @@ private static unsafe HRESULT EnumDAdvise(IntPtr thisPtr, IntPtr* pEnumAdvise) result = WinFormsComWrappers.Instance.TryGetComPointer(enumAdvice, IID.IEnumSTATDATA, out var enumAdvicePtr); return result; } - - internal struct STGMEDIUM_Raw - { - public TYMED tymed; - public IntPtr unionmember; - public IntPtr pUnkForRelease; - } } } } diff --git a/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.STGMEDIUM_Raw.cs b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.STGMEDIUM_Raw.cs new file mode 100644 index 00000000000..33fe48f9074 --- /dev/null +++ b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.STGMEDIUM_Raw.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.InteropServices.ComTypes; + +internal partial class Interop +{ + internal unsafe partial class WinFormsComWrappers + { + internal struct STGMEDIUM_Raw + { + public TYMED tymed; + public IntPtr unionmember; + public IntPtr pUnkForRelease; + } + } +} diff --git a/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.cs b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.cs index 812e4275745..c28597f5c12 100644 --- a/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.cs +++ b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.cs @@ -173,6 +173,14 @@ protected override object CreateObject(IntPtr externalComObject, CreateObjectFla return new PictureWrapper(pictureComObject); } + Guid dataObjectIID = IID.IDataObject; + hr = Marshal.QueryInterface(externalComObject, ref dataObjectIID, out IntPtr dataObjectComObject); + if (hr == S_OK) + { + Marshal.Release(externalComObject); + return new DataObjectWrapper(dataObjectComObject); + } + Guid errorInfoIID = IID.IErrorInfo; hr = Marshal.QueryInterface(externalComObject, ref errorInfoIID, out IntPtr errorInfoComObject); if (hr == S_OK) @@ -283,6 +291,7 @@ private IntPtr GetOrCreateComInterfaceForObject(object obj) ShellItemWrapper siw => siw.Instance, FileOpenDialogWrapper fodw => fodw.Instance, FileSaveDialogWrapper fsdw => fsdw.Instance, + DataObjectWrapper dow => dow.Instance, _ => GetOrCreateComInterfaceForObject(obj, CreateComInterfaceFlags.None), }; } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Clipboard.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Clipboard.cs index 2b38dacf013..da910fc766e 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Clipboard.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Clipboard.cs @@ -139,12 +139,12 @@ public static void SetDataObject(object data, bool copy, int retryTimes, int ret private static IDataObject? GetDataObject(int retryTimes, int retryDelay) { IComDataObject? dataObject = null; - HRESULT hr; + bool success; int retry = retryTimes; do { - hr = Ole32.OleGetClipboard(ref dataObject); - if (hr != HRESULT.S_OK) + success = Ole32.OleGetClipboard(out dataObject, out HRESULT hr); + if (!success) { if (retry == 0) { @@ -155,15 +155,10 @@ public static void SetDataObject(object data, bool copy, int retryTimes, int ret Thread.Sleep(millisecondsTimeout: retryDelay); } } - while (hr != 0); + while (!success); if (dataObject is not null) { - if (dataObject is IDataObject ido && !Marshal.IsComObject(dataObject)) - { - return ido; - } - return new DataObject(dataObject); } From 90a2bca60114ad68c94eecde76d1386c38b1cf89 Mon Sep 17 00:00:00 2001 From: Andrii Kurdiumov Date: Fri, 6 May 2022 11:46:33 +0600 Subject: [PATCH 2/9] Apply suggestions from code review Co-authored-by: Igor Velikorossov --- .../src/Interop/Ole32/Interop.OleGetClipboard.cs | 4 ++-- .../src/Interop/Ole32/Interop.OleSetClipboard.cs | 2 +- .../src/System/Windows/Forms/Clipboard.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) 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 index 260b1f3405b..58bd2eb4abd 100644 --- a/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.OleGetClipboard.cs +++ b/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.OleGetClipboard.cs @@ -11,9 +11,9 @@ internal static partial class Interop internal static partial class Ole32 { [DllImport(Libraries.Ole32, ExactSpelling = true)] - public static extern HRESULT OleGetClipboard(out IntPtr data); + private static extern HRESULT OleGetClipboard(out IntPtr data); - public static bool OleGetClipboard([NotNullWhen(true)] out IDataObject? data, out HRESULT result) + public static bool TryOleGetClipboard(out HRESULT result, [NotNullWhen(true)] out IDataObject? data) { result = OleGetClipboard(out IntPtr ptr); if (result == HRESULT.S_OK) 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 index a3d6e85cb63..7e77dca2e51 100644 --- a/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.OleSetClipboard.cs +++ b/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.OleSetClipboard.cs @@ -14,7 +14,7 @@ internal static partial class Ole32 public static HRESULT OleSetClipboard(IDataObject? pDataObj) { - if (pDataObj == null) + if (pDataObj is null) { return OleSetClipboard(IntPtr.Zero); } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Clipboard.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Clipboard.cs index da910fc766e..2c9d14320c8 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Clipboard.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Clipboard.cs @@ -138,7 +138,7 @@ public static void SetDataObject(object data, bool copy, int retryTimes, int ret /// private static IDataObject? GetDataObject(int retryTimes, int retryDelay) { - IComDataObject? dataObject = null; + IComDataObject? dataObject; bool success; int retry = retryTimes; do From a2cf9768df4db735ceb297939ae1f22ce54f1a9e Mon Sep 17 00:00:00 2001 From: Andrii Kurdiumov Date: Fri, 6 May 2022 12:21:19 +0600 Subject: [PATCH 3/9] Fix compilation errors after Add finalizer --- .../WinFormsComWrappers.DataObjectWrapper.cs | 23 +++++++++++++++++++ .../src/System/Windows/Forms/Clipboard.cs | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.DataObjectWrapper.cs b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.DataObjectWrapper.cs index 2af3710929d..af8a78bd7ec 100644 --- a/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.DataObjectWrapper.cs +++ b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.DataObjectWrapper.cs @@ -21,7 +21,18 @@ public DataObjectWrapper(IntPtr wrappedInstance) internal IntPtr Instance => _wrappedInstance; + ~DataObjectWrapper() + { + this.DisposeInternal(); + } + public void Dispose() + { + DisposeInternal(); + GC.SuppressFinalize(this); + } + + private void DisposeInternal() { Marshal.Release(_wrappedInstance); _wrappedInstance = IntPtr.Zero; @@ -40,6 +51,10 @@ public void GetData(ref FORMATETC format, out STGMEDIUM medium) tymed = mediumRaw.tymed, unionmember = mediumRaw.unionmember, }; + if (mediumRaw.pUnkForRelease != IntPtr.Zero) + { + Marshal.Release(mediumRaw.pUnkForRelease); + } } } @@ -61,6 +76,10 @@ public void GetDataHere(ref FORMATETC format, ref STGMEDIUM medium) tymed = mediumRaw.tymed, unionmember = mediumRaw.unionmember, }; + if (mediumRaw.pUnkForRelease != IntPtr.Zero) + { + Marshal.Release(mediumRaw.pUnkForRelease); + } } } @@ -101,6 +120,10 @@ public void SetData(ref FORMATETC formatIn, ref STGMEDIUM medium, bool release) tymed = mediumRaw.tymed, unionmember = mediumRaw.unionmember, }; + if (mediumRaw.pUnkForRelease != IntPtr.Zero) + { + Marshal.Release(mediumRaw.pUnkForRelease); + } } } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Clipboard.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Clipboard.cs index 2c9d14320c8..c81172b5112 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Clipboard.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Clipboard.cs @@ -143,7 +143,7 @@ public static void SetDataObject(object data, bool copy, int retryTimes, int ret int retry = retryTimes; do { - success = Ole32.OleGetClipboard(out dataObject, out HRESULT hr); + success = Ole32.TryOleGetClipboard(out HRESULT hr, out dataObject); if (!success) { if (retry == 0) From 0c812d295e04166453b483d908ef9959e6490ba4 Mon Sep 17 00:00:00 2001 From: Andrii Kurdiumov Date: Fri, 6 May 2022 12:24:07 +0600 Subject: [PATCH 4/9] Fix style warning --- .../src/Interop/WinFormsComWrappers.DataObjectWrapper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.DataObjectWrapper.cs b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.DataObjectWrapper.cs index af8a78bd7ec..222b50f3f31 100644 --- a/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.DataObjectWrapper.cs +++ b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.DataObjectWrapper.cs @@ -64,7 +64,7 @@ public void GetDataHere(ref FORMATETC format, ref STGMEDIUM medium) { STGMEDIUM_Raw mediumRaw = new() { - pUnkForRelease = medium.pUnkForRelease == null ? IntPtr.Zero : Marshal.GetIUnknownForObject(medium.pUnkForRelease), + pUnkForRelease = medium.pUnkForRelease is null ? IntPtr.Zero : Marshal.GetIUnknownForObject(medium.pUnkForRelease), tymed = medium.tymed, unionmember = medium.unionmember, }; @@ -108,7 +108,7 @@ public void SetData(ref FORMATETC formatIn, ref STGMEDIUM medium, bool release) { STGMEDIUM_Raw mediumRaw = new() { - pUnkForRelease = medium.pUnkForRelease == null ? IntPtr.Zero : Marshal.GetIUnknownForObject(medium.pUnkForRelease), + pUnkForRelease = medium.pUnkForRelease is null ? IntPtr.Zero : Marshal.GetIUnknownForObject(medium.pUnkForRelease), tymed = medium.tymed, unionmember = medium.unionmember, }; From 860ceec5a438c996d94a08825b19022f7a0b6ed5 Mon Sep 17 00:00:00 2001 From: Andrii Kurdiumov Date: Mon, 16 May 2022 20:28:46 +0600 Subject: [PATCH 5/9] Attempt to implement AgileReference --- .../Ole32/Interop.AgileReferenceOptions.cs | 15 ++++ .../Ole32/Interop.RoGetAgileReference.cs | 24 ++++++ ...FormsComWrappers.AgileDataObjectWrapper.cs | 82 +++++++++++++++++++ ...nFormsComWrappers.AgileReferenceWrapper.cs | 38 +++++++++ .../WinFormsComWrappers.DataObjectWrapper.cs | 2 +- .../src/Interop/WinFormsComWrappers.cs | 4 +- 6 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.AgileReferenceOptions.cs create mode 100644 src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.RoGetAgileReference.cs create mode 100644 src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.AgileDataObjectWrapper.cs create mode 100644 src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.AgileReferenceWrapper.cs diff --git a/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.AgileReferenceOptions.cs b/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.AgileReferenceOptions.cs new file mode 100644 index 00000000000..18ea6cd708c --- /dev/null +++ b/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.AgileReferenceOptions.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +internal static partial class Interop +{ + internal static partial class Ole32 + { + public enum AgileReferenceOptions + { + Default = 0, + DelayedMarshal = 1, + } + } +} diff --git a/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.RoGetAgileReference.cs b/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.RoGetAgileReference.cs new file mode 100644 index 00000000000..d789797e74a --- /dev/null +++ b/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.RoGetAgileReference.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Ole32 + { + [DllImport(Libraries.Ole32, ExactSpelling = true)] + public static extern HRESULT RoGetAgileReference( + AgileReferenceOptions opts, + ref Guid riid, + IntPtr instance, + out IntPtr agileReference); + + public static WinFormsComWrappers.AgileReferenceWrapper RoGetAgileReference(AgileReferenceOptions opts, ref Guid riid, IntPtr instance) + { + RoGetAgileReference(opts, ref riid, instance, out var agileReference); + return new WinFormsComWrappers.AgileReferenceWrapper(agileReference); + } + } +} diff --git a/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.AgileDataObjectWrapper.cs b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.AgileDataObjectWrapper.cs new file mode 100644 index 00000000000..a8176752578 --- /dev/null +++ b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.AgileDataObjectWrapper.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; + +internal partial class Interop +{ + internal unsafe partial class WinFormsComWrappers + { + internal class AgileDataObjectWrapper : IDataObject + { + private AgileReferenceWrapper _wrappedInstance; + + public AgileDataObjectWrapper(AgileReferenceWrapper wrappedInstance) + { + _wrappedInstance = wrappedInstance; + } + + private DataObjectWrapper Unwrap() + { + var instance = _wrappedInstance.Resolve(IID.IDataObject); + return new DataObjectWrapper(instance); + } + + public void GetData(ref FORMATETC format, out STGMEDIUM medium) + { + using var dataObject = Unwrap(); + dataObject.GetData(ref format, out medium); + } + + public void GetDataHere(ref FORMATETC format, ref STGMEDIUM medium) + { + using var dataObject = Unwrap(); + dataObject.GetDataHere(ref format, ref medium); + } + + public int QueryGetData(ref FORMATETC format) + { + using var dataObject = Unwrap(); + return dataObject.QueryGetData(ref format); + } + + public int GetCanonicalFormatEtc(ref FORMATETC formatIn, out FORMATETC formatOut) + { + using var dataObject = Unwrap(); + return dataObject.GetCanonicalFormatEtc(ref formatIn, out formatOut); + } + + public void SetData(ref FORMATETC formatIn, ref STGMEDIUM medium, bool release) + { + using var dataObject = Unwrap(); + dataObject.SetData(ref formatIn, ref medium, release); + } + + public IEnumFORMATETC EnumFormatEtc(DATADIR direction) + { + using var dataObject = Unwrap(); + return dataObject.EnumFormatEtc(direction); + } + + public int DAdvise(ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection) + { + using var dataObject = Unwrap(); + return dataObject.DAdvise(ref pFormatetc, advf, adviseSink, out connection); + } + + public void DUnadvise(int connection) + { + using var dataObject = Unwrap(); + dataObject.DUnadvise(connection); + } + + public int EnumDAdvise(out IEnumSTATDATA? enumAdvise) + { + using var dataObject = Unwrap(); + return dataObject.EnumDAdvise(out enumAdvise); + } + } + } +} diff --git a/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.AgileReferenceWrapper.cs b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.AgileReferenceWrapper.cs new file mode 100644 index 00000000000..538963a1767 --- /dev/null +++ b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.AgileReferenceWrapper.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.InteropServices; +using System.Windows.Forms; + +internal partial class Interop +{ + internal unsafe partial class WinFormsComWrappers + { + internal class AgileReferenceWrapper + { + private IntPtr _wrappedInstance; + + public AgileReferenceWrapper(IntPtr wrappedInstance) + { + _wrappedInstance = wrappedInstance.OrThrowIfZero(); + } + + internal IntPtr Instance => _wrappedInstance; + + ~AgileReferenceWrapper() + { + Marshal.Release(_wrappedInstance); + _wrappedInstance = IntPtr.Zero; + } + + public unsafe IntPtr Resolve(Guid iid) + { + IntPtr result = IntPtr.Zero; + ((delegate* unmanaged)(*(*(void***)_wrappedInstance + 3))) + (_wrappedInstance, &iid, &result).ThrowIfFailed(); + return result; + } + } + } +} diff --git a/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.DataObjectWrapper.cs b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.DataObjectWrapper.cs index 222b50f3f31..220e0a21916 100644 --- a/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.DataObjectWrapper.cs +++ b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.DataObjectWrapper.cs @@ -10,7 +10,7 @@ internal partial class Interop { internal unsafe partial class WinFormsComWrappers { - internal class DataObjectWrapper : IDataObject + internal sealed class DataObjectWrapper : IDataObject, IDisposable { private IntPtr _wrappedInstance; diff --git a/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.cs b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.cs index c28597f5c12..05a5c3d51f1 100644 --- a/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.cs +++ b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.cs @@ -178,7 +178,9 @@ protected override object CreateObject(IntPtr externalComObject, CreateObjectFla if (hr == S_OK) { Marshal.Release(externalComObject); - return new DataObjectWrapper(dataObjectComObject); + var agileReference = Ole32.RoGetAgileReference(Ole32.AgileReferenceOptions.Default, ref dataObjectIID, dataObjectComObject); + Marshal.Release(dataObjectComObject); + return new AgileDataObjectWrapper(agileReference); } Guid errorInfoIID = IID.IErrorInfo; From a5b13b2cbfc8dacc828933c268b111636caadd28 Mon Sep 17 00:00:00 2001 From: Andrii Kurdiumov Date: Mon, 16 May 2022 20:41:45 +0600 Subject: [PATCH 6/9] Remove usings --- .../src/Interop/WinFormsComWrappers.AgileDataObjectWrapper.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.AgileDataObjectWrapper.cs b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.AgileDataObjectWrapper.cs index a8176752578..c33d4e1abec 100644 --- a/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.AgileDataObjectWrapper.cs +++ b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.AgileDataObjectWrapper.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; internal partial class Interop From 62a6a5f12455bb7aa079ecbef9d18230930a2734 Mon Sep 17 00:00:00 2001 From: Andrii Kurdiumov Date: Fri, 3 Jun 2022 17:02:49 +0600 Subject: [PATCH 7/9] Add comment --- .../src/Interop/WinFormsComWrappers.DataObjectWrapper.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.DataObjectWrapper.cs b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.DataObjectWrapper.cs index 220e0a21916..8eebe952b0a 100644 --- a/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.DataObjectWrapper.cs +++ b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.DataObjectWrapper.cs @@ -70,6 +70,8 @@ public void GetDataHere(ref FORMATETC format, ref STGMEDIUM medium) }; ((delegate* unmanaged)(*(*(void***)_wrappedInstance + 4))) (_wrappedInstance, formatPtr, &mediumRaw).ThrowIfFailed(); + // Because we do not know if COM interface implementation change value of mediumRaw.pUnkForRelease during the call, + // unmarshal it again. medium = new() { pUnkForRelease = mediumRaw.pUnkForRelease == IntPtr.Zero ? null : Marshal.GetObjectForIUnknown(mediumRaw.pUnkForRelease), From b5eb45424499ab40f54f3969deff6bf7ae0783d8 Mon Sep 17 00:00:00 2001 From: Andrii Kurdiumov Date: Fri, 3 Jun 2022 17:19:39 +0600 Subject: [PATCH 8/9] Restore old codepath for Win7 --- .../Interop/Ole32/Interop.OleGetClipboard.cs | 3 ++ .../src/System/Windows/Forms/Clipboard.cs | 49 ++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) 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 index 58bd2eb4abd..952058117d8 100644 --- a/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.OleGetClipboard.cs +++ b/src/System.Windows.Forms.Primitives/src/Interop/Ole32/Interop.OleGetClipboard.cs @@ -13,6 +13,9 @@ internal static partial class Ole32 [DllImport(Libraries.Ole32, ExactSpelling = true)] private static extern HRESULT OleGetClipboard(out IntPtr data); + [DllImport(Libraries.Ole32, ExactSpelling = true)] + public static extern HRESULT OleGetClipboard(ref IDataObject? data); + public static bool TryOleGetClipboard(out HRESULT result, [NotNullWhen(true)] out IDataObject? data) { result = OleGetClipboard(out IntPtr ptr); diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Clipboard.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Clipboard.cs index c81172b5112..8afdd95c834 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Clipboard.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Clipboard.cs @@ -130,9 +130,19 @@ public static void SetDataObject(object data, bool copy, int retryTimes, int ret } // We need to retry the GetDataObject() since the clipBoard is busy sometimes and hence the GetDataObject would fail with ClipBoardException. - return GetDataObject(retryTimes: 10, retryDelay: 100); + if (UseComWrappers) + { + return GetDataObject(retryTimes: 10, retryDelay: 100); + } + else + { + return GetDataObjectWin7(retryTimes: 10, retryDelay: 100); + } } + // If rewrite this property via ILLink, you can get rid of Win7 code. + private bool UseComWrappers => OsVersion.IsWindows8OrGreater; + /// /// Private method to help accessing clipBoard for know retries before failing. /// @@ -165,6 +175,43 @@ public static void SetDataObject(object data, bool copy, int retryTimes, int ret return null; } + /// + /// Private method to help accessing clipBoard for know retries before failing. + /// + private static IDataObject? GetDataObjectWin7(int retryTimes, int retryDelay) + { + IComDataObject? dataObject = null; + HRESULT hr; + int retry = retryTimes; + do + { + hr = Ole32.OleGetClipboard(ref dataObject); + if (hr != HRESULT.S_OK) + { + if (retry == 0) + { + throw new ExternalException(SR.ClipboardOperationFailed, (int)hr); + } + + retry--; + Thread.Sleep(millisecondsTimeout: retryDelay); + } + } + while (hr != 0); + + if (dataObject is not null) + { + if (dataObject is IDataObject ido && !Marshal.IsComObject(dataObject)) + { + return ido; + } + + return new DataObject(dataObject); + } + + return null; + } + public static void Clear() { SetDataObject(new DataObject()); From 3d51ec54f7479cb583baeb50597ca9f5aa7d5742 Mon Sep 17 00:00:00 2001 From: Andrii Kurdiumov Date: Fri, 3 Jun 2022 17:23:36 +0600 Subject: [PATCH 9/9] Wrap COM implementation in try/catch --- .../WinFormsComWrappers.IDataObjectVtbl.cs | 68 ++++++++++++++----- 1 file changed, 50 insertions(+), 18 deletions(-) diff --git a/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.IDataObjectVtbl.cs b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.IDataObjectVtbl.cs index 2079fdb76e3..52f11e69fe4 100644 --- a/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.IDataObjectVtbl.cs +++ b/src/System.Windows.Forms.Primitives/src/Interop/WinFormsComWrappers.IDataObjectVtbl.cs @@ -35,9 +35,9 @@ public static IntPtr Create(IntPtr fpQueryInterface, IntPtr fpAddRef, IntPtr fpR [UnmanagedCallersOnly] private static HRESULT GetData(IntPtr thisPtr, FORMATETC* format, STGMEDIUM_Raw* pMedium) { - var instance = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr); try { + var instance = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr); instance.GetData(ref *format, out var medium); pMedium->pUnkForRelease = medium.pUnkForRelease == null ? IntPtr.Zero : Marshal.GetIUnknownForObject(medium.pUnkForRelease); pMedium->tymed = medium.tymed; @@ -54,9 +54,9 @@ private static HRESULT GetData(IntPtr thisPtr, FORMATETC* format, STGMEDIUM_Raw* [UnmanagedCallersOnly] private static unsafe HRESULT GetDataHere(IntPtr thisPtr, FORMATETC* format, STGMEDIUM_Raw* pMedium) { - var instance = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr); try { + var instance = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr); STGMEDIUM medium = new() { pUnkForRelease = pMedium->pUnkForRelease == IntPtr.Zero ? null : Marshal.GetObjectForIUnknown(pMedium->pUnkForRelease), @@ -80,23 +80,39 @@ private static unsafe HRESULT GetDataHere(IntPtr thisPtr, FORMATETC* format, STG [UnmanagedCallersOnly] private static unsafe HRESULT QueryGetData(IntPtr thisPtr, FORMATETC* format) { - var instance = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr); - return (HRESULT)instance.QueryGetData(ref *format); + try + { + var instance = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr); + return (HRESULT)instance.QueryGetData(ref *format); + } + catch (Exception ex) + { + Debug.WriteLine(ex); + return (HRESULT)ex.HResult; + } } [UnmanagedCallersOnly] private static unsafe HRESULT GetCanonicalFormatEtc(IntPtr thisPtr, FORMATETC* formatIn, FORMATETC* formatOut) { - var instance = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr); - return (HRESULT)instance.GetCanonicalFormatEtc(ref *formatIn, out *formatOut); + try + { + var instance = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr); + return (HRESULT)instance.GetCanonicalFormatEtc(ref *formatIn, out *formatOut); + } + catch (Exception ex) + { + Debug.WriteLine(ex); + return (HRESULT)ex.HResult; + } } [UnmanagedCallersOnly] private static HRESULT SetData(IntPtr thisPtr, FORMATETC* format, STGMEDIUM_Raw* pMedium, int release) { - var instance = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr); try { + var instance = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr); STGMEDIUM medium = new() { pUnkForRelease = pMedium->pUnkForRelease == IntPtr.Zero ? null : Marshal.GetObjectForIUnknown(pMedium->pUnkForRelease), @@ -117,9 +133,9 @@ private static HRESULT SetData(IntPtr thisPtr, FORMATETC* format, STGMEDIUM_Raw* [UnmanagedCallersOnly] private static HRESULT EnumFormatEtc(IntPtr thisPtr, DATADIR direction, IntPtr* pEnumFormatC) { - var instance = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr); try { + var instance = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr); var formatEtc = instance.EnumFormatEtc(direction); if (Marshal.IsComObject(formatEtc)) { @@ -146,17 +162,25 @@ private static HRESULT EnumFormatEtc(IntPtr thisPtr, DATADIR direction, IntPtr* [UnmanagedCallersOnly] private static unsafe HRESULT DAdvise(IntPtr thisPtr, FORMATETC* pFormatetc, ADVF advf, IntPtr pAdviseSink, int* connection) { - var instance = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr); - var adviseSink = (IAdviseSink)Marshal.GetObjectForIUnknown(pAdviseSink); - return (HRESULT)instance.DAdvise(ref *pFormatetc, advf, adviseSink, out *connection); + try + { + var instance = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr); + var adviseSink = (IAdviseSink)Marshal.GetObjectForIUnknown(pAdviseSink); + return (HRESULT)instance.DAdvise(ref *pFormatetc, advf, adviseSink, out *connection); + } + catch (Exception ex) + { + Debug.WriteLine(ex); + return (HRESULT)ex.HResult; + } } [UnmanagedCallersOnly] private static unsafe HRESULT DUnadvise(IntPtr thisPtr, int connection) { - var instance = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr); try { + var instance = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr); instance.DUnadvise(connection); return HRESULT.S_OK; } @@ -170,15 +194,23 @@ private static unsafe HRESULT DUnadvise(IntPtr thisPtr, int connection) [UnmanagedCallersOnly] private static unsafe HRESULT EnumDAdvise(IntPtr thisPtr, IntPtr* pEnumAdvise) { - var instance = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr); - var result = (HRESULT)instance.EnumDAdvise(out var enumAdvice); - if (result.Failed()) + try { + var instance = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr); + var result = (HRESULT)instance.EnumDAdvise(out var enumAdvice); + if (result.Failed()) + { + return result; + } + + result = WinFormsComWrappers.Instance.TryGetComPointer(enumAdvice, IID.IEnumSTATDATA, out var enumAdvicePtr); return result; } - - result = WinFormsComWrappers.Instance.TryGetComPointer(enumAdvice, IID.IEnumSTATDATA, out var enumAdvicePtr); - return result; + catch (Exception ex) + { + Debug.WriteLine(ex); + return (HRESULT)ex.HResult; + } } } }