1
1
using System ;
2
2
using System . Collections ;
3
3
using System . Collections . Generic ;
4
- using System . Linq ;
4
+ using System . Runtime . InteropServices ;
5
+ using ComTypes = System . Runtime . InteropServices . ComTypes ;
5
6
6
7
namespace Rubberduck . VBEditor . SafeComWrappers
7
8
{
9
+ // The CLR automagically handles COM enumeration by custom marshalling IEnumVARIANT to a managed class (see EnumeratorToEnumVariantMarshaler)
10
+ // But as we need explicit control over all COM objects in RD, this is unacceptable. We need to obtain the explicit IEnumVARIANT interface
11
+ // and ensure this RCW is destroyed in a timely fashion, using Marshal.ReleaseComObject.
12
+ // The automatic custom marshalling of the enumeration getter method (DISPID_ENUM) prohibits access to the underlying IEnumVARIANT interface.
13
+ // To work around it, we must call the IDispatch:::Invoke method directly (instead of using CLRs normal late-bound method calling ability).
14
+
15
+ [ ComImport ( ) , Guid ( "00020400-0000-0000-C000-000000000046" ) ]
16
+ [ InterfaceType ( ComInterfaceType . InterfaceIsIUnknown ) ]
17
+ interface IDispatch
18
+ {
19
+ [ PreserveSig ] int GetTypeInfoCount ( [ Out ] out uint pctinfo ) ;
20
+ [ PreserveSig ] int GetTypeInfo ( [ In ] uint iTInfo , [ In ] uint lcid , [ Out ] out ComTypes . ITypeInfo pTypeInfo ) ;
21
+ [ PreserveSig ] int GetIDsOfNames ( [ In ] ref Guid riid , [ In ] string [ ] rgszNames , [ In ] uint cNames , [ In ] uint lcid , [ Out ] out int [ ] rgDispId ) ;
22
+
23
+ [ PreserveSig ]
24
+ int Invoke ( [ In ] int dispIdMember ,
25
+ [ In ] ref Guid riid ,
26
+ [ In ] uint lcid ,
27
+ [ In ] uint dwFlags ,
28
+ [ In , Out ] ref ComTypes . DISPPARAMS pDispParams ,
29
+ [ Out ] out Object pVarResult ,
30
+ [ In , Out ] ref ComTypes . EXCEPINFO pExcepInfo ,
31
+ [ Out ] out uint pArgErr ) ;
32
+ }
33
+
34
+ class IDispatchHelper
35
+ {
36
+ public enum StandardDispIds : int
37
+ {
38
+ DISPID_ENUM = - 4
39
+ }
40
+
41
+ public enum InvokeKind : int
42
+ {
43
+ DISPATCH_METHOD = 1 ,
44
+ DISPATCH_PROPERTYGET = 2 ,
45
+ DISPATCH_PROPERTYPUT = 4 ,
46
+ DISPATCH_PROPERTYPUTREF = 8 ,
47
+ }
48
+
49
+ public static object PropertyGet_NoArgs ( IDispatch obj , int memberId )
50
+ {
51
+ var pDispParams = new ComTypes . DISPPARAMS ( ) ;
52
+ object pVarResult ;
53
+ var pExcepInfo = new ComTypes . EXCEPINFO ( ) ;
54
+ uint ErrArg ;
55
+ Guid guid = new Guid ( ) ;
56
+
57
+ int hr = obj . Invoke ( memberId , ref guid , 0 , ( uint ) ( InvokeKind . DISPATCH_METHOD | InvokeKind . DISPATCH_PROPERTYGET ) ,
58
+ ref pDispParams , out pVarResult , ref pExcepInfo , out ErrArg ) ;
59
+
60
+ if ( hr < 0 )
61
+ {
62
+ // could expand this to better handle DISP_E_EXCEPTION
63
+ throw Marshal . GetExceptionForHR ( hr ) ;
64
+ }
65
+
66
+ return pVarResult ;
67
+ }
68
+ }
69
+
70
+ [ ComImport ( ) , Guid ( "00020404-0000-0000-C000-000000000046" ) ]
71
+ [ InterfaceType ( ComInterfaceType . InterfaceIsIUnknown ) ]
72
+ public interface IEnumVARIANT
73
+ {
74
+ // rgVar is technically an unmanaged array here, but we only ever call with celt=1, so this is compatible.
75
+ [ PreserveSig ] int Next ( [ In ] uint celt , [ Out ] out object rgVar , [ Out ] out uint pceltFetched ) ;
76
+
77
+ [ PreserveSig ] int Skip ( [ In ] uint celt ) ;
78
+ [ PreserveSig ] int Reset ( ) ;
79
+ [ PreserveSig ] int Clone ( [ Out ] out IEnumVARIANT retval ) ;
80
+ }
81
+
8
82
public class ComWrapperEnumerator < TWrapperItem > : IEnumerator < TWrapperItem >
9
83
where TWrapperItem : class
10
84
{
11
85
private readonly Func < object , TWrapperItem > _itemWrapper ;
12
- private readonly IEnumerator _internal ;
86
+ private readonly IEnumVARIANT _enumeratorRCW ;
87
+ private TWrapperItem _currentItem ;
13
88
14
- public ComWrapperEnumerator ( IEnumerable source , Func < object , TWrapperItem > itemWrapper )
89
+ public ComWrapperEnumerator ( object source , Func < object , TWrapperItem > itemWrapper )
15
90
{
16
91
_itemWrapper = itemWrapper ;
17
- _internal = source ? . GetEnumerator ( ) ?? Enumerable . Empty < TWrapperItem > ( ) . GetEnumerator ( ) ;
92
+
93
+ if ( source != null )
94
+ {
95
+ _enumeratorRCW = ( IEnumVARIANT ) IDispatchHelper . PropertyGet_NoArgs ( ( IDispatch ) source , ( int ) IDispatchHelper . StandardDispIds . DISPID_ENUM ) ;
96
+ ( ( IEnumerator ) this ) . Reset ( ) ; // precaution
97
+ }
18
98
}
19
99
20
100
public void Dispose ( )
21
101
{
22
- // nothing to dispose here
102
+ if ( ! IsWrappingNullReference ) Marshal . ReleaseComObject ( _enumeratorRCW ) ;
23
103
}
24
104
25
- public bool MoveNext ( )
105
+ void IEnumerator . Reset ( )
26
106
{
27
- return _internal . MoveNext ( ) ;
107
+ if ( ! IsWrappingNullReference )
108
+ {
109
+ int hr = _enumeratorRCW . Reset ( ) ;
110
+ if ( hr < 0 )
111
+ {
112
+ throw Marshal . GetExceptionForHR ( hr ) ;
113
+ }
114
+ }
28
115
}
116
+
117
+ public bool IsWrappingNullReference => _enumeratorRCW == null ;
118
+
119
+ public TWrapperItem Current => _currentItem ;
120
+ object IEnumerator . Current => _currentItem ;
29
121
30
- public void Reset ( )
122
+ bool IEnumerator . MoveNext ( )
31
123
{
32
- _internal . Reset ( ) ;
33
- }
124
+ if ( IsWrappingNullReference ) return false ;
125
+
126
+ _currentItem = null ;
34
127
35
- public TWrapperItem Current => _itemWrapper . Invoke ( _internal . Current ) ;
128
+ object currentItemRCW ;
129
+ uint celtFetched ;
130
+ int hr = _enumeratorRCW . Next ( 1 , out currentItemRCW , out celtFetched ) ;
131
+ // hr == S_FALSE (1) or S_OK (0), or <0 means error
36
132
37
- object IEnumerator . Current => Current ;
133
+ _currentItem = _itemWrapper . Invoke ( currentItemRCW ) ; // creates a null wrapped reference even on end/error, just as a precaution
134
+
135
+ if ( hr < 0 )
136
+ {
137
+ throw Marshal . GetExceptionForHR ( hr ) ;
138
+ }
139
+
140
+ return ( celtFetched == 1 ) ; // celtFetched will be 0 when we reach the end of the collection
141
+ }
38
142
}
39
143
}
0 commit comments