1
1
// Copyright (c) Jeremy W. Kuhne. All rights reserved.
2
2
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
4
+ using System . Runtime . CompilerServices ;
5
+ using System . Runtime . InteropServices ;
4
6
using Windows . Dialogs ;
7
+ using Windows . Win32 . Foundation ;
8
+ using Windows . Win32 . System . Com . Marshal ;
5
9
using Windows . Win32 . UI . Shell ;
10
+ using InteropMarshal = global ::System . Runtime . InteropServices . Marshal ;
6
11
7
12
namespace Windows . Win32 . System . Com ;
8
13
@@ -27,4 +32,144 @@ public void Com_GetComPointer_SameInterfaceInstance()
27
32
28
33
Assert . True ( iEvents1 . Pointer == iEvents2 . Pointer ) ;
29
34
}
35
+
36
+ [ Fact ]
37
+ public void Com_BuiltInCom_RCW_Behavior ( )
38
+ {
39
+ UnknownTest unknown = new ( ) ;
40
+ using ComScope < IUnknown > iUnknown = new ( UnknownCCW . CreateInstance ( unknown ) ) ;
41
+
42
+ object rcw = InteropMarshal . GetObjectForIUnknown ( ( IntPtr ) iUnknown . Pointer ) ;
43
+
44
+ unknown . AddRefCount . Should ( ) . Be ( 1 ) ;
45
+ unknown . ReleaseCount . Should ( ) . Be ( 1 ) ;
46
+ unknown . LastRefCount . Should ( ) . Be ( 2 ) ;
47
+ unknown . QueryInterfaceGuids . Should ( ) . BeEquivalentTo ( [
48
+ IUnknown . IID_Guid ,
49
+ INoMarshal . IID_Guid ,
50
+ IAgileObject . IID_Guid ,
51
+ IMarshal . IID_Guid ] ) ;
52
+
53
+ // Release and FinalRelease look the same from our IUnknown's perspective
54
+ InteropMarshal . FinalReleaseComObject ( rcw ) ;
55
+
56
+ unknown . AddRefCount . Should ( ) . Be ( 1 ) ;
57
+ unknown . ReleaseCount . Should ( ) . Be ( 2 ) ;
58
+ unknown . LastRefCount . Should ( ) . Be ( 1 ) ;
59
+ unknown . QueryInterfaceGuids . Should ( ) . BeEquivalentTo ( [
60
+ IUnknown . IID_Guid ,
61
+ INoMarshal . IID_Guid ,
62
+ IAgileObject . IID_Guid ,
63
+ IMarshal . IID_Guid ] ) ;
64
+ }
65
+
66
+ public interface IUnkownTest
67
+ {
68
+ public void QueryInterface ( Guid riid ) ;
69
+ public void AddRef ( uint current ) ;
70
+ public void Release ( uint current ) ;
71
+ }
72
+
73
+ public class UnknownTest : IUnkownTest
74
+ {
75
+ public int AddRefCount { get ; private set ; }
76
+ public int ReleaseCount { get ; private set ; }
77
+ public List < Guid > QueryInterfaceGuids { get ; } = [ ] ;
78
+ public int LastRefCount { get ; private set ; }
79
+
80
+ void IUnkownTest . AddRef ( uint current )
81
+ {
82
+ AddRefCount ++ ;
83
+ LastRefCount = ( int ) current ;
84
+ }
85
+
86
+ void IUnkownTest . QueryInterface ( Guid riid )
87
+ {
88
+ QueryInterfaceGuids . Add ( riid ) ;
89
+ }
90
+
91
+ void IUnkownTest . Release ( uint current )
92
+ {
93
+ ReleaseCount ++ ;
94
+ LastRefCount = ( int ) current ;
95
+ }
96
+ }
97
+
98
+ public static class UnknownCCW
99
+ {
100
+ public static unsafe IUnknown * CreateInstance ( IUnkownTest @object )
101
+ => ( IUnknown * ) Lifetime < IUnknown . Vtbl , IUnkownTest > . Allocate ( @object , CCWVTable ) ;
102
+
103
+ private static readonly IUnknown . Vtbl * CCWVTable = AllocateVTable ( ) ;
104
+
105
+ private static unsafe IUnknown . Vtbl * AllocateVTable ( )
106
+ {
107
+ // Allocate and create a static VTable for this type projection.
108
+ var vtable = ( IUnknown . Vtbl * ) RuntimeHelpers . AllocateTypeAssociatedMemory ( typeof ( UnknownCCW ) , sizeof ( IUnknown . Vtbl ) ) ;
109
+
110
+ // IUnknown
111
+ vtable ->QueryInterface_1 = & QueryInterface ;
112
+ vtable ->AddRef_2 = & AddRef ;
113
+ vtable ->Release_3 = & Release ;
114
+ return vtable ;
115
+ }
116
+
117
+ [ UnmanagedCallersOnly ( CallConvs = [ typeof ( CallConvStdcall ) ] ) ]
118
+ private static HRESULT QueryInterface ( IUnknown * @this , Guid * riid , void * * ppvObject )
119
+ {
120
+ if ( ppvObject is null )
121
+ {
122
+ return HRESULT . E_POINTER ;
123
+ }
124
+
125
+ var unknown = Lifetime < IUnknown . Vtbl , IUnkownTest > . GetObject ( @this ) ;
126
+ if ( unknown is null )
127
+ {
128
+ return HRESULT . COR_E_OBJECTDISPOSED ;
129
+ }
130
+
131
+ unknown . QueryInterface ( * riid ) ;
132
+
133
+ if ( * riid == typeof ( IUnknown ) . GUID )
134
+ {
135
+ * ppvObject = @this ;
136
+ }
137
+ else
138
+ {
139
+ * ppvObject = null ;
140
+ return HRESULT . E_NOINTERFACE ;
141
+ }
142
+
143
+ Lifetime < IUnknown . Vtbl , IUnkownTest > . AddRef ( @this ) ;
144
+ return HRESULT . S_OK ;
145
+ }
146
+
147
+ [ UnmanagedCallersOnly ( CallConvs = [ typeof ( CallConvStdcall ) ] ) ]
148
+ private static uint AddRef ( IUnknown * @this )
149
+ {
150
+ var unknown = Lifetime < IUnknown . Vtbl , IUnkownTest > . GetObject ( @this ) ;
151
+ if ( unknown is null )
152
+ {
153
+ return HRESULT . COR_E_OBJECTDISPOSED ;
154
+ }
155
+
156
+ uint current = Lifetime < IUnknown . Vtbl , IUnkownTest > . AddRef ( @this ) ;
157
+ unknown . AddRef ( current ) ;
158
+ return current ;
159
+ }
160
+
161
+ [ UnmanagedCallersOnly ( CallConvs = [ typeof ( CallConvStdcall ) ] ) ]
162
+ private static uint Release ( IUnknown * @this )
163
+ {
164
+ var unknown = Lifetime < IUnknown . Vtbl , IUnkownTest > . GetObject ( @this ) ;
165
+ if ( unknown is null )
166
+ {
167
+ return HRESULT . COR_E_OBJECTDISPOSED ;
168
+ }
169
+
170
+ uint current = Lifetime < IUnknown . Vtbl , IUnkownTest > . Release ( @this ) ;
171
+ unknown . Release ( current ) ;
172
+ return current ;
173
+ }
174
+ }
30
175
}
0 commit comments