Skip to content

Commit 6c27601

Browse files
authored
Merge pull request #3708 from WaynePhillipsEA/rewite-comwrapperenumeration
Rewite of ComWrapperEnumeration, to take control of when IEnumVariant RCW's get released.
2 parents 88c6e7c + 4043e68 commit 6c27601

File tree

14 files changed

+129
-51
lines changed

14 files changed

+129
-51
lines changed
Lines changed: 116 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,143 @@
11
using System;
22
using System.Collections;
33
using System.Collections.Generic;
4-
using System.Linq;
4+
using System.Runtime.InteropServices;
5+
using ComTypes = System.Runtime.InteropServices.ComTypes;
56

67
namespace Rubberduck.VBEditor.SafeComWrappers
78
{
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+
882
public class ComWrapperEnumerator<TWrapperItem> : IEnumerator<TWrapperItem>
983
where TWrapperItem : class
1084
{
1185
private readonly Func<object, TWrapperItem> _itemWrapper;
12-
private readonly IEnumerator _internal;
86+
private readonly IEnumVARIANT _enumeratorRCW;
87+
private TWrapperItem _currentItem;
1388

14-
public ComWrapperEnumerator(IEnumerable source, Func<object, TWrapperItem> itemWrapper)
89+
public ComWrapperEnumerator(object source, Func<object, TWrapperItem> itemWrapper)
1590
{
1691
_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+
}
1898
}
1999

20100
public void Dispose()
21101
{
22-
// nothing to dispose here
102+
if (!IsWrappingNullReference) Marshal.ReleaseComObject(_enumeratorRCW);
23103
}
24104

25-
public bool MoveNext()
105+
void IEnumerator.Reset()
26106
{
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+
}
28115
}
116+
117+
public bool IsWrappingNullReference => _enumeratorRCW == null;
118+
119+
public TWrapperItem Current => _currentItem;
120+
object IEnumerator.Current => _currentItem;
29121

30-
public void Reset()
122+
bool IEnumerator.MoveNext()
31123
{
32-
_internal.Reset();
33-
}
124+
if (IsWrappingNullReference) return false;
125+
126+
_currentItem = null;
34127

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
36132

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+
}
38142
}
39143
}

Rubberduck.VBEEditor/SafeComWrappers/Office.Core/CommandBarControls.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@ public ICommandBarControl Add(ControlType type, int before)
3131

3232
IEnumerator<ICommandBarControl> IEnumerable<ICommandBarControl>.GetEnumerator()
3333
{
34-
return IsWrappingNullReference
35-
? new ComWrapperEnumerator<ICommandBarControl>(null, o => new CommandBarControl(null))
36-
: new ComWrapperEnumerator<ICommandBarControl>(Target,
34+
return new ComWrapperEnumerator<ICommandBarControl>(Target,
3735
comObject => new CommandBarControl((Microsoft.Office.Core.CommandBarControl) comObject));
3836
}
3937

Rubberduck.VBEEditor/SafeComWrappers/Office.Core/CommandBars.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,7 @@ public ICommandBarControl FindControl(ControlType type, int id)
5757

5858
IEnumerator<ICommandBar> IEnumerable<ICommandBar>.GetEnumerator()
5959
{
60-
return IsWrappingNullReference
61-
? new ComWrapperEnumerator<ICommandBar>(null, o => new CommandBar(null))
62-
: new ComWrapperEnumerator<ICommandBar>(Target,
60+
return new ComWrapperEnumerator<ICommandBar>(Target,
6361
comObject => new CommandBar((Microsoft.Office.Core.CommandBar) comObject));
6462
}
6563

Rubberduck.VBEEditor/SafeComWrappers/VB6/VBComponents.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,7 @@ public IVBComponent AddMTDesigner(int index = 0)
6969

7070
IEnumerator<IVBComponent> IEnumerable<IVBComponent>.GetEnumerator()
7171
{
72-
return IsWrappingNullReference
73-
? new ComWrapperEnumerator<IVBComponent>(null, o => new VBComponent(null))
74-
: new ComWrapperEnumerator<IVBComponent>(Target, comObject => new VBComponent((VB.VBComponent)comObject));
72+
return new ComWrapperEnumerator<IVBComponent>(Target, comObject => new VBComponent((VB.VBComponent)comObject));
7573
}
7674

7775
IEnumerator IEnumerable.GetEnumerator()

Rubberduck.VBEEditor/SafeComWrappers/VB6/VBProjects.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,7 @@ public IVBProject Open(string path)
5454

5555
IEnumerator<IVBProject> IEnumerable<IVBProject>.GetEnumerator()
5656
{
57-
return IsWrappingNullReference
58-
? new ComWrapperEnumerator<IVBProject>(null, o => new VBProject(null))
59-
: new ComWrapperEnumerator<IVBProject>(Target, comObject => new VBProject((VB.VBProject)comObject));
57+
return new ComWrapperEnumerator<IVBProject>(Target, comObject => new VBProject((VB.VBProject)comObject));
6058
}
6159

6260
IEnumerator IEnumerable.GetEnumerator()

Rubberduck.VBEEditor/SafeComWrappers/VBA/AddIns.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,7 @@ IEnumerator IEnumerable.GetEnumerator()
4848

4949
IEnumerator<IAddIn> IEnumerable<IAddIn>.GetEnumerator()
5050
{
51-
return IsWrappingNullReference
52-
? new ComWrapperEnumerator<IAddIn>(null, o => new AddIn(null))
53-
: new ComWrapperEnumerator<IAddIn>(Target, comObject => new AddIn((VB.AddIn) comObject));
51+
return new ComWrapperEnumerator<IAddIn>(Target, comObject => new AddIn((VB.AddIn) comObject));
5452
}
5553
}
5654
}

Rubberduck.VBEEditor/SafeComWrappers/VBA/CodePanes.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@ public ICodePane Current
2828

2929
IEnumerator<ICodePane> IEnumerable<ICodePane>.GetEnumerator()
3030
{
31-
return IsWrappingNullReference
32-
? new ComWrapperEnumerator<ICodePane>(null, o => new CodePane(null))
33-
: new ComWrapperEnumerator<ICodePane>(Target, comObject => new CodePane((VB.CodePane) comObject));
31+
return new ComWrapperEnumerator<ICodePane>(Target, comObject => new CodePane((VB.CodePane) comObject));
3432
}
3533

3634
IEnumerator IEnumerable.GetEnumerator()

Rubberduck.VBEEditor/SafeComWrappers/VBA/Controls.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ public Controls(VB.Forms.Controls target, bool rewrapping = false)
2020
IEnumerator<IControl> IEnumerable<IControl>.GetEnumerator()
2121
{
2222
// soft-casting because ImageClass doesn't implement IControl
23-
return IsWrappingNullReference
24-
? new ComWrapperEnumerator<IControl>(null, o => new Control(null))
25-
: new ComWrapperEnumerator<IControl>(Target, comObject => new Control(comObject as VB.Forms.Control));
23+
return new ComWrapperEnumerator<IControl>(Target, comObject => new Control(comObject as VB.Forms.Control));
2624
}
2725

2826
IEnumerator IEnumerable.GetEnumerator()

Rubberduck.VBEEditor/SafeComWrappers/VBA/LinkedWindows.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,7 @@ IEnumerator IEnumerable.GetEnumerator()
4747

4848
IEnumerator<IWindow> IEnumerable<IWindow>.GetEnumerator()
4949
{
50-
return IsWrappingNullReference
51-
? new ComWrapperEnumerator<IWindow>(null, o => new Window(null))
52-
: new ComWrapperEnumerator<IWindow>(Target, comObject => new Window((VB.Window) comObject));
50+
return new ComWrapperEnumerator<IWindow>(Target, comObject => new Window((VB.Window) comObject));
5351
}
5452

5553
public override bool Equals(ISafeComWrapper<VB.LinkedWindows> other)

Rubberduck.VBEEditor/SafeComWrappers/VBA/Properties.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,7 @@ public Properties(VB.Properties target, bool rewrapping = false)
2424

2525
IEnumerator<IProperty> IEnumerable<IProperty>.GetEnumerator()
2626
{
27-
return IsWrappingNullReference
28-
? new ComWrapperEnumerator<IProperty>(null, o => new Property(null))
29-
: new ComWrapperEnumerator<IProperty>(Target, comObject => new Property((VB.Property) comObject));
27+
return new ComWrapperEnumerator<IProperty>(Target, comObject => new Property((VB.Property) comObject));
3028
}
3129

3230
IEnumerator IEnumerable.GetEnumerator()

0 commit comments

Comments
 (0)