-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathClipboardRedux.RCW_IDataObject.cs
231 lines (199 loc) · 8.51 KB
/
ClipboardRedux.RCW_IDataObject.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
using System;
using System.Diagnostics;
using System.Threading;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using IComDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
using FormsDataFormats = System.Windows.Forms.DataFormats;
namespace WindowsFormsClipboardRedux
{
partial class ClipboardRedux
{
internal class RCW_IDataObject : IComDataObject
{
private static readonly TYMED[] ALLOWED_TYMEDS = new[]
{
TYMED.TYMED_HGLOBAL,
TYMED.TYMED_ISTREAM,
TYMED.TYMED_GDI
};
private readonly RCW_IAgileReference _agileInstance;
private readonly IntPtr _instanceInSta;
private readonly unsafe DataObjectVTable* _vtableInSta;
public RCW_IDataObject(RCW_IAgileReference agileReference)
{
// Use IAgileReference instance to always be in context.
_agileInstance = agileReference;
Debug.Assert(Thread.CurrentThread.GetApartmentState() == ApartmentState.STA);
// Assuming this class is always in context getting it once is possible.
// See Finalizer for lifetime detail concerns. If the Clipboard instance
// is considered a process singleton, then it could be leaked.
(IntPtr instance, IntPtr vtable) = GetContextSafeRef(_agileInstance);
_instanceInSta = instance;
unsafe
{
_vtableInSta = (DataObjectVTable*)vtable;
}
}
// This Finalizer only works if the IComDataObject is free threaded or if code
// is added to ensure the Release takes place in the correct context.
~RCW_IDataObject()
{
// This should likely be some other mechanism, but the concept is correct.
// For WinForms we need any STA Control since all of them possess a
// BeginInvoke call. Alternatively this could be pass over to a
// cleanup thread that asks the main STA to clean up instances.
var formMaybe = System.Windows.Forms.Form.ActiveForm;
if (formMaybe is not null)
{
IntPtr instanceLocal = _instanceInSta;
if (instanceLocal != IntPtr.Zero)
{
// Clean up on the main thread
formMaybe.BeginInvoke(new Action(() =>
{
Marshal.Release(instanceLocal);
}));
}
}
}
private unsafe static (IntPtr inst, IntPtr vtable) GetContextSafeRef(RCW_IAgileReference agileRef)
{
IntPtr instSafe = agileRef.Resolve(typeof(IComDataObject).GUID);
// Retain the instance's vtable when in context.
var vtableSafe = (IntPtr)(*(DataObjectVTable**)instSafe);
return (instSafe, vtableSafe);
}
public IntPtr GetInstanceForSta()
{
Debug.Assert(Thread.CurrentThread.GetApartmentState() == ApartmentState.STA);
return _instanceInSta;
}
public unsafe void GetData(ref FORMATETC format, out STGMEDIUM medium)
{
// Marshal
var stgmed = default(Interops.STGMEDIUM);
medium = default;
// Dispatch
HRESULT hr;
(IntPtr instance, IntPtr vtable) = GetContextSafeRef(_agileInstance);
fixed (FORMATETC* pFormat = &format)
{
var @delegate = Marshal.GetDelegateForFunctionPointer<DataObjectVTable.GetDataDelegate>(((DataObjectVTable*)vtable)->GetData);
hr = @delegate.Invoke(instance, pFormat, &stgmed);
}
Marshal.Release(instance);
hr.ThrowIfFailed();
// Unmarshal
medium.tymed = stgmed.tymed;
medium.unionmember = stgmed.unionmember;
if (stgmed.pUnkForRelease != IntPtr.Zero)
{
medium.pUnkForRelease = Marshal.GetObjectForIUnknown(stgmed.pUnkForRelease);
}
}
public void GetDataHere(ref FORMATETC format, ref STGMEDIUM medium)
{
throw new NotImplementedException();
}
public unsafe int QueryGetData(ref FORMATETC format)
{
// Dispatch
HRESULT hr;
(IntPtr instance, IntPtr vtable) = GetContextSafeRef(_agileInstance);
fixed (FORMATETC* pFormat = &format)
{
var @delegate = Marshal.GetDelegateForFunctionPointer<DataObjectVTable.QueryGetDataDelegate>(((DataObjectVTable*)vtable)->QueryGetData);
hr = @delegate.Invoke(instance, pFormat);
}
Marshal.Release(instance);
if (hr.Failed())
{
return (int)HRESULT.S_FALSE;
}
if (format.dwAspect != DVASPECT.DVASPECT_CONTENT)
{
return (int)HRESULT.DV_E_DVASPECT;
}
if (!GetTymedUseable(format.tymed))
{
return (int)HRESULT.DV_E_TYMED;
}
if (format.cfFormat == 0)
{
return (int)HRESULT.S_FALSE;
}
FormsDataFormats.Format dataFormat = FormsDataFormats.GetFormat(format.cfFormat);
if (dataFormat.Id != format.cfFormat)
{
format.cfFormat = unchecked((short)(ushort)dataFormat.Id);
if (QueryGetData(ref format) != (int)HRESULT.S_OK)
{
return (int)HRESULT.DV_E_FORMATETC;
}
}
return (int)HRESULT.S_OK;
}
public int GetCanonicalFormatEtc(ref FORMATETC formatIn, out FORMATETC formatOut)
{
throw new NotImplementedException();
}
/// <summary>
/// Returns true if the tymed is useable.
/// </summary>
private bool GetTymedUseable(TYMED tymed)
{
for (int i = 0; i < ALLOWED_TYMEDS.Length; i++)
{
if ((tymed & ALLOWED_TYMEDS[i]) != 0)
{
return true;
}
}
return false;
}
public unsafe void SetData(ref FORMATETC formatIn, ref STGMEDIUM medium, bool release)
{
// Marshal
var pUnk = default(IntPtr);
if (medium.pUnkForRelease is not null)
{
pUnk = Marshal.GetIUnknownForObject(medium.pUnkForRelease);
}
var stgmed = new Interops.STGMEDIUM()
{
unionmember = medium.unionmember,
tymed = medium.tymed,
pUnkForRelease = pUnk
};
int isRelease = release ? 1 : 0;
// Dispatch
HRESULT hr;
(IntPtr instance, IntPtr vtable) = GetContextSafeRef(_agileInstance);
fixed (FORMATETC* pFormat = &formatIn)
{
var setDataDelegate = Marshal.GetDelegateForFunctionPointer<DataObjectVTable.SetDataDelegate>(((DataObjectVTable*)vtable)->SetData);
hr = setDataDelegate.Invoke(instance, pFormat, &stgmed, isRelease);
}
Marshal.Release(instance);
hr.ThrowIfFailed();
}
public IEnumFORMATETC EnumFormatEtc(DATADIR direction)
{
throw new NotImplementedException();
}
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();
}
}
}
}