-
Notifications
You must be signed in to change notification settings - Fork 5.1k
/
Copy pathProgram.cs
520 lines (448 loc) · 18.6 KB
/
Program.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
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
using System;
using System.Collections;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static System.Runtime.InteropServices.ComWrappers;
#if NET5_0
#pragma warning disable CA1416 // This is only needed in .NET 5.
#endif
namespace Tutorial
{
class Program
{
static void Main(string[] _)
{
var cw = new DemoComWrappers();
var demo = new DemoImpl();
string? value = demo.GetString();
Console.WriteLine($"Initial string: {value ?? "<null>"}");
// Create a managed object wrapper for the Demo object.
// Note the returned COM interface will always be for IUnknown.
IntPtr ccwUnknown = cw.GetOrCreateComInterfaceForObject(demo, CreateComInterfaceFlags.None);
// Create a native object wrapper for the managed object wrapper of the Demo object.
var rcw = cw.GetOrCreateObjectForComInstance(ccwUnknown, CreateObjectFlags.UniqueInstance);
// Release the managed object wrapper because the native object wrapper now manages
// its lifetime. See the native wrapper implementation that will handle the final two releases.
int count = Marshal.Release(ccwUnknown);
Debug.Assert(count == 2);
var getter = (IDemoGetType)rcw;
var store = (IDemoStoreType)rcw;
string msg = "hello world!";
store.StoreString(msg.Length, msg);
Console.WriteLine($"Setting string through wrapper: {msg}");
value = demo.GetString();
Console.WriteLine($"Get string through managed object: {value}");
msg = msg.ToUpper();
demo.StoreString(msg.Length, msg.ToUpper());
Console.WriteLine($"Setting string through managed object: {msg}");
value = getter.GetString();
Console.WriteLine($"Get string through wrapper: {value}");
// The DemoComWrappers supports creation of UniqueInstances - see above. This means
// the IDisposable contract may be provided to enable immediate COM object release.
(rcw as IDisposable)?.Dispose();
}
}
/// <summary>
/// IUnknown based COM interface.
/// </summary>
/// <remarks>
/// Win32 C/C++ definition:
/// <code>
/// MIDL_INTERFACE("92BAA992-DB5A-4ADD-977B-B22838EE91FD")
/// IDemoGetType : public IUnknown
/// {
/// HRESULT STDMETHODCALLTYPE GetString(_Outptr_ wchar_t** str) = 0;
/// };
/// </code>
/// </remarks>
interface IDemoGetType
{
/// <summary>
/// Statically defined Interface ID.
/// </summary>
/// <remarks>
/// Used instead of Type.GUID to be AOT friendly and avoid using Reflection.
/// </remarks>
public static Guid IID_IDemoGetType = new("92BAA992-DB5A-4ADD-977B-B22838EE91FD");
/// <summary>
/// Get the currently stored string.
/// </summary>
/// <returns>Returns the stored string or <c>null</c>.</returns>
string? GetString();
}
/// <summary>
/// IUnknown based COM interface.
/// </summary>
/// <remarks>
/// Win32 C/C++ definition:
/// <code>
/// MIDL_INTERFACE("30619FEA-E995-41EA-8C8B-9A610D32ADCB")
/// IDemoStoreType : public IUnknown
/// {
/// HRESULT STDMETHODCALLTYPE StoreString(int len, _In_z_ const wchar_t* str) = 0;
/// };
/// </code>
/// </remarks>
interface IDemoStoreType
{
/// <summary>
/// Statically defined Interface ID.
/// </summary>
/// <remarks>
/// Used instead of Type.GUID to be AOT friendly and avoid using Reflection.
/// </remarks>
public static Guid IID_IDemoStoreType = new("30619FEA-E995-41EA-8C8B-9A610D32ADCB");
/// <summary>
/// Store the supplied string.
/// </summary>
/// <param name="len">The length of the string to store.</param>
/// <param name="str">The string to store.</param>
void StoreString(int len, string? str);
}
/// <summary>
/// Managed implementation
/// </summary>
class DemoImpl : IDemoGetType, IDemoStoreType
{
string? _string;
public string? GetString() => _string;
public void StoreString(int _, string? str) => _string = str;
}
/// <summary>
/// User defined ComWrappers
/// </summary>
sealed unsafe class DemoComWrappers : ComWrappers
{
static readonly IntPtr s_IDemoGetTypeVTable;
static readonly IntPtr s_IDemoStoreVTable;
static readonly ComInterfaceEntry* s_DemoImplDefinition;
static readonly int s_DemoImplDefinitionLen;
/// <summary>
/// Preallocate COM artifacts to avoid penalty during wrapper creation.
/// </summary>
static DemoComWrappers()
{
// Get system provided IUnknown implementation.
GetIUnknownImpl(
out IntPtr fpQueryInterface,
out IntPtr fpAddRef,
out IntPtr fpRelease);
//
// Construct VTables for supported interfaces
//
{
int tableCount = 4;
int idx = 0;
var vtable = (IntPtr*)RuntimeHelpers.AllocateTypeAssociatedMemory(
typeof(DemoComWrappers),
IntPtr.Size * tableCount);
vtable[idx++] = fpQueryInterface;
vtable[idx++] = fpAddRef;
vtable[idx++] = fpRelease;
vtable[idx++] = (IntPtr)(delegate* unmanaged<IntPtr, IntPtr*, int>)&ABI.IDemoGetTypeManagedWrapper.GetString;
Debug.Assert(tableCount == idx);
s_IDemoGetTypeVTable = (IntPtr)vtable;
}
{
int tableCount = 4;
int idx = 0;
var vtable = (IntPtr*)RuntimeHelpers.AllocateTypeAssociatedMemory(
typeof(DemoComWrappers),
IntPtr.Size * tableCount);
vtable[idx++] = fpQueryInterface;
vtable[idx++] = fpAddRef;
vtable[idx++] = fpRelease;
vtable[idx++] = (IntPtr)(delegate* unmanaged<IntPtr, int, IntPtr, int>)&ABI.IDemoStoreTypeManagedWrapper.StoreString;
Debug.Assert(tableCount == idx);
s_IDemoStoreVTable = (IntPtr)vtable;
}
//
// Construct entries for supported managed types
//
{
s_DemoImplDefinitionLen = 2;
int idx = 0;
var entries = (ComInterfaceEntry*)RuntimeHelpers.AllocateTypeAssociatedMemory(
typeof(DemoComWrappers),
sizeof(ComInterfaceEntry) * s_DemoImplDefinitionLen);
entries[idx].IID = IDemoGetType.IID_IDemoGetType;
entries[idx++].Vtable = s_IDemoGetTypeVTable;
entries[idx].IID = IDemoStoreType.IID_IDemoStoreType;
entries[idx++].Vtable = s_IDemoStoreVTable;
Debug.Assert(s_DemoImplDefinitionLen == idx);
s_DemoImplDefinition = entries;
}
}
readonly delegate* <IntPtr, object?> _createIfSupported;
public DemoComWrappers(bool useDynamicNativeWrapper = false)
{
// Determine which wrapper create function to use.
_createIfSupported = useDynamicNativeWrapper
? &ABI.DemoNativeDynamicWrapper.CreateIfSupported
: &ABI.DemoNativeStaticWrapper.CreateIfSupported;
}
protected override unsafe ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count)
{
Debug.Assert(flags is CreateComInterfaceFlags.None);
if (obj is DemoImpl)
{
count = s_DemoImplDefinitionLen;
return s_DemoImplDefinition;
}
// Note: this implementation does not handle cases where the passed in object implements
// one or both of the supported interfaces but is not the expected .NET class.
count = 0;
return null;
}
protected override object CreateObject(IntPtr externalComObject, CreateObjectFlags flags)
{
// Assert use of the UniqueInstance flag since the returned Native Object Wrapper always
// supports IDisposable and its Dispose will always release and suppress finalization.
// If the wrapper doesn't always support IDisposable the assert can be relaxed.
Debug.Assert(flags.HasFlag(CreateObjectFlags.UniqueInstance));
// Throw an exception if the type is not supported by the implementation.
// Null can be returned as well, but an ArgumentNullException will be thrown for
// the consumer of this ComWrappers instance.
return _createIfSupported(externalComObject) ?? throw new NotSupportedException();
}
protected override void ReleaseObjects(IEnumerable objects)
{
throw new NotImplementedException();
}
}
namespace ABI
{
internal enum HRESULT : int
{
S_OK = 0
}
/// <summary>
/// Managed object wrapper for IDemoGetType.
/// </summary>
internal static unsafe class IDemoGetTypeManagedWrapper
{
[UnmanagedCallersOnly]
public static int GetString(IntPtr _this, IntPtr* str)
{
try
{
string? s = ComInterfaceDispatch.GetInstance<IDemoGetType>((ComInterfaceDispatch*)_this).GetString();
// Marshal to COM
*str = Marshal.StringToCoTaskMemUni(s);
}
catch (Exception e)
{
return e.HResult;
}
return (int)HRESULT.S_OK;
}
}
/// <summary>
/// Managed object wrapper for IDemoStoreType.
/// </summary>
internal static unsafe class IDemoStoreTypeManagedWrapper
{
[UnmanagedCallersOnly]
public static int StoreString(IntPtr _this, int len, IntPtr str)
{
try
{
// Marshal to .NET
string? strLocal = str == IntPtr.Zero ? null : Marshal.PtrToStringUni(str, len);
// Since we've taken ownership we need to free the native memory.
// This is a policy decision because the API could require returning the same pointer
// stored and not just a copy.
Marshal.FreeCoTaskMem(str);
ComInterfaceDispatch.GetInstance<IDemoStoreType>((ComInterfaceDispatch*)_this).StoreString(len, strLocal);
}
catch (Exception e)
{
return e.HResult;
}
return (int)HRESULT.S_OK;
}
}
/// <summary>
/// Native object wrapper static implementation.
/// </summary>
/// <remarks>
/// This class statically implements two desired Demo interfaces and is limited to
/// those interfaces that it implements. For scenarios involving native objects
/// with a potentially unknown number of interfaces see <see cref="DemoNativeDynamicWrapper"/>.
/// </remarks>
internal class DemoNativeStaticWrapper
: IDemoGetType
, IDemoStoreType
, IDisposable
{
bool _isDisposed = false;
private DemoNativeStaticWrapper() { }
~DemoNativeStaticWrapper()
{
DisposeInternal();
}
public IntPtr IDemoGetTypeInst { get; init; }
public IntPtr IDemoStoreTypeInst { get; init; }
public static DemoNativeStaticWrapper? CreateIfSupported(IntPtr ptr)
{
int hr = Marshal.QueryInterface(ptr, ref IDemoGetType.IID_IDemoGetType, out IntPtr IDemoGetTypeInst);
if (hr != (int)HRESULT.S_OK)
{
return default;
}
hr = Marshal.QueryInterface(ptr, ref IDemoStoreType.IID_IDemoStoreType, out IntPtr IDemoStoreTypeInst);
if (hr != (int)HRESULT.S_OK)
{
Marshal.Release(IDemoGetTypeInst);
return default;
}
return new DemoNativeStaticWrapper()
{
IDemoGetTypeInst = IDemoGetTypeInst,
IDemoStoreTypeInst = IDemoStoreTypeInst
};
}
public void Dispose()
{
DisposeInternal();
GC.SuppressFinalize(this);
}
public string? GetString() =>
IDemoGetTypeNativeWrapper.GetString(IDemoGetTypeInst);
public void StoreString(int len, string? str) =>
IDemoStoreTypeNativeWrapper.StoreString(IDemoStoreTypeInst, len, str);
void DisposeInternal()
{
if (_isDisposed)
return;
// [WARNING] This is unsafe for COM objects that have specific thread affinity.
Marshal.Release(IDemoGetTypeInst);
Marshal.Release(IDemoStoreTypeInst);
_isDisposed = true;
}
}
/// <summary>
/// Native object wrapper dynamic implementation.
/// </summary>
/// <remarks>
/// This class relies upon IDynamicInterfaceCastable to enable COM style casting
/// via QueryInterface. The current implementation validates the supplied COM pointer
/// implements the two desired Demo interfaces, but this checked could be delayed and
/// enable a highly dynamic scenario.
/// </remarks>
internal class DemoNativeDynamicWrapper
: IDynamicInterfaceCastable
, IDisposable
{
bool _isDisposed = false;
private DemoNativeDynamicWrapper() { }
~DemoNativeDynamicWrapper()
{
DisposeInternal();
}
public IntPtr IDemoGetTypeInst { get; init; }
public IntPtr IDemoStoreTypeInst { get; init; }
public static DemoNativeDynamicWrapper? CreateIfSupported(IntPtr ptr)
{
int hr = Marshal.QueryInterface(ptr, ref IDemoGetType.IID_IDemoGetType, out IntPtr IDemoGetTypeInst);
if (hr != (int)HRESULT.S_OK)
{
return default;
}
hr = Marshal.QueryInterface(ptr, ref IDemoStoreType.IID_IDemoStoreType, out IntPtr IDemoStoreTypeInst);
if (hr != (int)HRESULT.S_OK)
{
Marshal.Release(IDemoGetTypeInst);
return default;
}
return new DemoNativeDynamicWrapper()
{
IDemoGetTypeInst = IDemoGetTypeInst,
IDemoStoreTypeInst = IDemoStoreTypeInst
};
}
public RuntimeTypeHandle GetInterfaceImplementation(RuntimeTypeHandle interfaceType)
{
if (interfaceType.Equals(typeof(IDemoGetType).TypeHandle))
{
return typeof(IDemoGetTypeNativeWrapper).TypeHandle;
}
else if (interfaceType.Equals(typeof(IDemoStoreType).TypeHandle))
{
return typeof(IDemoStoreTypeNativeWrapper).TypeHandle;
}
return default;
}
public bool IsInterfaceImplemented(RuntimeTypeHandle interfaceType, bool throwIfNotImplemented)
{
if (interfaceType.Equals(typeof(IDemoGetType).TypeHandle)
|| interfaceType.Equals(typeof(IDemoStoreType).TypeHandle))
{
return true;
}
if (throwIfNotImplemented)
throw new InvalidCastException($"{nameof(DemoNativeDynamicWrapper)} doesn't support {interfaceType}");
return false;
}
public void Dispose()
{
DisposeInternal();
GC.SuppressFinalize(this);
}
void DisposeInternal()
{
if (_isDisposed)
return;
// [WARNING] This is unsafe for COM objects that have specific thread affinity.
Marshal.Release(IDemoGetTypeInst);
Marshal.Release(IDemoStoreTypeInst);
_isDisposed = true;
}
}
/// <summary>
/// Native object wrapper for IDemoGetType.
/// </summary>
[DynamicInterfaceCastableImplementation]
internal unsafe interface IDemoGetTypeNativeWrapper : IDemoGetType
{
public static string? GetString(IntPtr inst)
{
IntPtr str;
int hr = ((delegate* unmanaged<IntPtr, IntPtr*, int>)(*(*(void***)inst + 3 /* IDemoGetType.GetString slot */)))(inst, &str);
if (hr != (int)HRESULT.S_OK)
Marshal.ThrowExceptionForHR(hr);
string? strLocal = Marshal.PtrToStringUni(str);
Marshal.FreeCoTaskMem(str);
return strLocal;
}
string? IDemoGetType.GetString()
{
var inst = ((DemoNativeDynamicWrapper)this).IDemoGetTypeInst;
return GetString(inst);
}
}
/// <summary>
/// Native object wrapper for IDemoStoreType.
/// </summary>
[DynamicInterfaceCastableImplementation]
internal unsafe interface IDemoStoreTypeNativeWrapper : IDemoStoreType
{
public static void StoreString(IntPtr inst, int len, string? str)
{
IntPtr strLocal = Marshal.StringToCoTaskMemUni(str);
int hr = ((delegate* unmanaged<IntPtr, int, IntPtr, int>)(*(*(void***)inst + 3 /* IDemoStoreType.StoreString slot */)))(inst, len, strLocal);
if (hr != (int)HRESULT.S_OK)
{
Marshal.FreeCoTaskMem(strLocal);
Marshal.ThrowExceptionForHR(hr);
}
}
void IDemoStoreType.StoreString(int len, string? str)
{
var inst = ((DemoNativeDynamicWrapper)this).IDemoStoreTypeInst;
StoreString(inst, len, str);
}
}
}
}