-
Notifications
You must be signed in to change notification settings - Fork 999
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use ComWrappers for Clipboard #7087
Changes from all commits
2622cc7
90a2bca
a2cf976
0c812d2
860ceec
a5b13b2
62a6a5f
b5eb454
3d51ec5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
// 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 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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is how apartment support for |
||
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); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<IntPtr, Guid*, IntPtr*, HRESULT>)(*(*(void***)_wrappedInstance + 3))) | ||
(_wrappedInstance, &iid, &result).ThrowIfFailed(); | ||
return result; | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,167 @@ | ||||||||||
// 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 | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see anything here around apartment marshalling. If I recall, the entire clipboard stack is required to be on the STA. This doesn't enforce that or even bother to check, which means we can get into data corruption or random crashes. This type only ever going to be used on the STA and if so, how is that is that enforced? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the clipboard we check:
Perhaps we should be doing the same here as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm all for multilayered checks, but I wonder, how .NET already handling that? Also My understnading was that I should use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All .NET objects are free-threaded so COM servers implemented in .NET can run on any apartment. For built-in RCWs, this is incredibly complex and handled in the RCW implementation - see There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I believe this is mentioned in the ClipboardRedux example above. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Independent of the answer on Windows 7 support, can we just implement new Windows Forms features the new best way and say that we'll document which features are not supported on Windows 7 going forward if that proves necessary? Put another way, if we decide to support Windows 7 again -- which I really hope not -- it's not really intended as a first-class citizen. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @AaronRobinsonMSFT and @RussKie can we consider current implementation as "limited support" for Win7? I'm not sure that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My take on this is that we add OS checks to use the new functionality on Win8+ and use the original implementations for Win7. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems to be Did not matter. I have to somehow drag to that point and I need old implementation hanging around. Still looking for approximate list of test cases for clipboard. |
||||||||||
{ | ||||||||||
internal sealed class DataObjectWrapper : IDataObject, IDisposable | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would expect the |
||||||||||
{ | ||||||||||
private IntPtr _wrappedInstance; | ||||||||||
|
||||||||||
public DataObjectWrapper(IntPtr wrappedInstance) | ||||||||||
{ | ||||||||||
_wrappedInstance = wrappedInstance.OrThrowIfZero(); | ||||||||||
} | ||||||||||
|
||||||||||
internal IntPtr Instance => _wrappedInstance; | ||||||||||
|
||||||||||
~DataObjectWrapper() | ||||||||||
{ | ||||||||||
this.DisposeInternal(); | ||||||||||
kant2002 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
} | ||||||||||
|
||||||||||
public void Dispose() | ||||||||||
{ | ||||||||||
DisposeInternal(); | ||||||||||
GC.SuppressFinalize(this); | ||||||||||
} | ||||||||||
|
||||||||||
private void DisposeInternal() | ||||||||||
{ | ||||||||||
Marshal.Release(_wrappedInstance); | ||||||||||
_wrappedInstance = IntPtr.Zero; | ||||||||||
} | ||||||||||
|
||||||||||
public void GetData(ref FORMATETC format, out STGMEDIUM medium) | ||||||||||
{ | ||||||||||
fixed (FORMATETC* formatPtr = &format) | ||||||||||
{ | ||||||||||
STGMEDIUM_Raw mediumRaw; | ||||||||||
((delegate* unmanaged<IntPtr, FORMATETC*, STGMEDIUM_Raw*, HRESULT>)(*(*(void***)_wrappedInstance + 3))) | ||||||||||
(_wrappedInstance, formatPtr, &mediumRaw).ThrowIfFailed(); | ||||||||||
medium = new() | ||||||||||
{ | ||||||||||
pUnkForRelease = mediumRaw.pUnkForRelease == IntPtr.Zero ? null : Marshal.GetObjectForIUnknown(mediumRaw.pUnkForRelease), | ||||||||||
RussKie marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
tymed = mediumRaw.tymed, | ||||||||||
unionmember = mediumRaw.unionmember, | ||||||||||
}; | ||||||||||
if (mediumRaw.pUnkForRelease != IntPtr.Zero) | ||||||||||
kant2002 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
{ | ||||||||||
Marshal.Release(mediumRaw.pUnkForRelease); | ||||||||||
} | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
public void GetDataHere(ref FORMATETC format, ref STGMEDIUM medium) | ||||||||||
{ | ||||||||||
fixed (FORMATETC* formatPtr = &format) | ||||||||||
{ | ||||||||||
STGMEDIUM_Raw mediumRaw = new() | ||||||||||
{ | ||||||||||
pUnkForRelease = medium.pUnkForRelease is null ? IntPtr.Zero : Marshal.GetIUnknownForObject(medium.pUnkForRelease), | ||||||||||
RussKie marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
tymed = medium.tymed, | ||||||||||
unionmember = medium.unionmember, | ||||||||||
}; | ||||||||||
((delegate* unmanaged<IntPtr, FORMATETC*, STGMEDIUM_Raw*, HRESULT>)(*(*(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), | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you please help me understand why we need to marshal again, and can't just copy There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what if somebody replace value, stored in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since there was a question here consider a clarifying comment |
||||||||||
tymed = mediumRaw.tymed, | ||||||||||
unionmember = mediumRaw.unionmember, | ||||||||||
}; | ||||||||||
if (mediumRaw.pUnkForRelease != IntPtr.Zero) | ||||||||||
{ | ||||||||||
Marshal.Release(mediumRaw.pUnkForRelease); | ||||||||||
} | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
public int QueryGetData(ref FORMATETC format) | ||||||||||
{ | ||||||||||
fixed (FORMATETC* formatPtr = &format) | ||||||||||
{ | ||||||||||
return (int)((delegate* unmanaged<IntPtr, FORMATETC*, HRESULT>)(*(*(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<IntPtr, FORMATETC*, FORMATETC*, HRESULT>)(*(*(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 is null ? IntPtr.Zero : Marshal.GetIUnknownForObject(medium.pUnkForRelease), | ||||||||||
tymed = medium.tymed, | ||||||||||
unionmember = medium.unionmember, | ||||||||||
}; | ||||||||||
((delegate* unmanaged<IntPtr, FORMATETC*, STGMEDIUM_Raw*, int, HRESULT>)(*(*(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, | ||||||||||
}; | ||||||||||
if (mediumRaw.pUnkForRelease != IntPtr.Zero) | ||||||||||
{ | ||||||||||
Marshal.Release(mediumRaw.pUnkForRelease); | ||||||||||
} | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
public IEnumFORMATETC EnumFormatEtc(DATADIR direction) | ||||||||||
{ | ||||||||||
IntPtr resultPtr; | ||||||||||
((delegate* unmanaged<IntPtr, DATADIR, IntPtr*, HRESULT>)(*(*(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<IntPtr, FORMATETC*, ADVF, IntPtr, int*, HRESULT>)(*(*(void***)_wrappedInstance + 9))) | ||||||||||
(_wrappedInstance, formatPtr, advf, adviseSinkPtr, connectionPtr); | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
public void DUnadvise(int connection) | ||||||||||
{ | ||||||||||
((delegate* unmanaged<IntPtr, int, HRESULT>)(*(*(void***)_wrappedInstance + 10))) | ||||||||||
(_wrappedInstance, connection).ThrowIfFailed(); | ||||||||||
} | ||||||||||
|
||||||||||
public int EnumDAdvise(out IEnumSTATDATA? enumAdvise) | ||||||||||
{ | ||||||||||
IntPtr enumAdvisePtr; | ||||||||||
var result = ((delegate* unmanaged<IntPtr, IntPtr*, HRESULT>)(*(*(void***)_wrappedInstance + 11))) | ||||||||||
(_wrappedInstance, &enumAdvisePtr); | ||||||||||
enumAdvise = result.Succeeded() ? null : (IEnumSTATDATA)Marshal.GetObjectForIUnknown(enumAdvisePtr); | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @AaronRobinsonMSFT @kant2002 could you please help me understand the best practices for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Release is responsibility of RCW finalizer. |
||||||||||
return (int)result; | ||||||||||
} | ||||||||||
} | ||||||||||
} | ||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Entirely up to the WinForms owners, @RussKie and @JeremyKuhne, but this could be declared within the method body below as a static local function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As in like this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's what Aaron means. Do you want me apply this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How will it play if we switch to
LibraryImport
later on? Will source gen produce code for this?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we'll cross that bridge when we get to it :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn’t #7172 practically done. And it will land definitely faster then this one. Am I miss something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Judging by the @JeremyKuhne's comments there are perf implications that may need to be addressed first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding on this, that this comment applied only if custom marshaller would be used with
HandleRef
orIHandle
, but withIntPtr
these concerns are not applicable. So that PR looks like it can be merged as is right now.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're just switching to
LibraryImport
it won't necessitate changing anything here.