Skip to content

Commit 9d288f0

Browse files
committed
Extract all the mess and try to hide it away in an abstract base class.
Modify StrongComSafe to be more consistent with hashing.
1 parent 71f43ce commit 9d288f0

File tree

4 files changed

+158
-94
lines changed

4 files changed

+158
-94
lines changed
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Runtime.CompilerServices;
4+
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
5+
6+
#if DEBUG
7+
using System.Diagnostics;
8+
using System.Runtime.InteropServices;
9+
#endif
10+
11+
namespace Rubberduck.VBEditor.ComManagement
12+
{
13+
public abstract class ComSafeBase : IComSafe
14+
{
15+
#if DEBUG
16+
protected IEnumerable<string> Trace = null;
17+
#endif
18+
19+
public abstract void Add(ISafeComWrapper comWrapper);
20+
21+
//We do not use GetHashCode because subclasses of SafeComWrapper<T> overwrite this method
22+
//and we need to distinguish between individual instances.
23+
protected int GetComWrapperObjectHashCode(ISafeComWrapper comWrapper)
24+
{
25+
return RuntimeHelpers.GetHashCode(comWrapper);
26+
}
27+
28+
public abstract bool TryRemove(ISafeComWrapper comWrapper);
29+
30+
public void Dispose()
31+
{
32+
Dispose(true);
33+
}
34+
35+
protected abstract void Dispose(bool disposing);
36+
37+
#if DEBUG
38+
/// <summary>
39+
/// Provide a serialized list of the COM Safe
40+
/// to make it easy to analyze what is inside
41+
/// the COM Safe at the different points of
42+
/// the session's lifetime.
43+
/// </summary>
44+
public void Serialize()
45+
{
46+
using (var stream = System.IO.File.AppendText($"comSafeOutput {DateTime.UtcNow:yyyyMMddhhmmss}.csv"))
47+
{
48+
stream.WriteLine(
49+
"Ordinal\tKey\tCOM Wrapper Type\tWrapping Null?\tIUnknown Pointer Address\tLevel 1\tLevel 2\tLevel 3");
50+
var i = 0;
51+
foreach (var kvp in GetWrappers())
52+
{
53+
var line = kvp.Value != null
54+
? $"{i++}\t{kvp.Key}\t\"{kvp.Value.GetType().FullName}\"\t\"{kvp.Value.IsWrappingNullReference}\"\t\"{(kvp.Value.IsWrappingNullReference ? "null" : GetPtrAddress(kvp.Value))}\"\t\"{string.Join("\"\t\"", Trace)}\""
55+
: $"{i++}\t{kvp.Key}\t\"null\"\t\"null\"\t\"null\"\t\"{string.Join("\"\t\"", Trace)}\"";
56+
stream.WriteLine(line);
57+
}
58+
}
59+
}
60+
61+
protected abstract IDictionary<int, ISafeComWrapper> GetWrappers();
62+
63+
protected static IEnumerable<string> GetStackTrace(int frames, int offset)
64+
{
65+
var list = new List<string>();
66+
var trace = new StackTrace();
67+
if ((trace.FrameCount - offset) < frames)
68+
{
69+
frames = (trace.FrameCount - offset);
70+
}
71+
72+
for (var i = 1; i <= frames; i++)
73+
{
74+
var frame = trace.GetFrame(i + offset);
75+
var typeName = frame.GetMethod().DeclaringType?.FullName ?? string.Empty;
76+
var methodName = frame.GetMethod().Name;
77+
78+
var qualifiedName = $"{typeName}{(typeName.Length > 0 ? "::" : string.Empty)}{methodName}";
79+
list.Add(qualifiedName);
80+
}
81+
82+
return list;
83+
}
84+
85+
protected static string GetPtrAddress(object target)
86+
{
87+
if (target == null)
88+
{
89+
return IntPtr.Zero.ToString();
90+
}
91+
92+
if (!Marshal.IsComObject(target))
93+
{
94+
return "Not a COM object";
95+
}
96+
97+
var pointer = IntPtr.Zero;
98+
try
99+
{
100+
pointer = Marshal.GetIUnknownForObject(target);
101+
}
102+
finally
103+
{
104+
if (pointer != IntPtr.Zero)
105+
{
106+
Marshal.Release(pointer);
107+
}
108+
}
109+
110+
return pointer.ToString();
111+
}
112+
#endif
113+
}
114+
}
Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,43 @@
1-
using System;
2-
using System.Collections.Concurrent;
1+
using System.Collections.Concurrent;
2+
using System.Collections.Generic;
33
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
44

55
namespace Rubberduck.VBEditor.ComManagement
66
{
7-
public class StrongComSafe: IComSafe
7+
public class StrongComSafe: ComSafeBase
88
{
99
//We override the equality comparison and hash code because subclasses of SafeComWrapper<T> override the corresponding methods.
1010
//We need to distinguish between the individual wrapper instances no matter whether they are semantically equal.
11-
private readonly ConcurrentDictionary<ISafeComWrapper, byte> _comWrapperCache = new ConcurrentDictionary<ISafeComWrapper, byte>(new ReferenceEqualityComparer());
11+
private readonly ConcurrentDictionary<int, ISafeComWrapper> _comWrapperCache = new ConcurrentDictionary<int, ISafeComWrapper>();
1212

13-
14-
public void Add(ISafeComWrapper comWrapper)
13+
public override void Add(ISafeComWrapper comWrapper)
1514
{
1615
if (comWrapper != null)
1716
{
18-
_comWrapperCache.AddOrUpdate(comWrapper, key => 1, (key, value) => value);
17+
#if DEBUG
18+
Trace = GetStackTrace(3, 3);
19+
#endif
20+
_comWrapperCache.AddOrUpdate(
21+
GetComWrapperObjectHashCode(comWrapper),
22+
value => comWrapper,
23+
(key, value) =>
24+
{
25+
#if DEBUG
26+
System.Diagnostics.Debug.Assert(false);
27+
#endif
28+
return value;
29+
});
1930
}
20-
2131
}
2232

23-
public bool TryRemove(ISafeComWrapper comWrapper)
33+
public override bool TryRemove(ISafeComWrapper comWrapper)
2434
{
25-
return !_disposed && comWrapper != null && _comWrapperCache.TryRemove(comWrapper, out _);
35+
return !_disposed && comWrapper != null &&
36+
_comWrapperCache.TryRemove(GetComWrapperObjectHashCode(comWrapper), out _);
2637
}
2738

2839
private bool _disposed;
29-
public void Dispose()
40+
protected override void Dispose(bool disposing)
3041
{
3142
if (_disposed)
3243
{
@@ -35,12 +46,19 @@ public void Dispose()
3546

3647
_disposed = true;
3748

38-
foreach (var comWrapper in _comWrapperCache.Keys)
49+
foreach (var comWrapper in _comWrapperCache.Values)
3950
{
4051
comWrapper.Dispose();
4152
}
4253

4354
_comWrapperCache.Clear();
4455
}
56+
57+
#if DEBUG
58+
protected override IDictionary<int, ISafeComWrapper> GetWrappers()
59+
{
60+
return _comWrapperCache;
61+
}
62+
#endif
4563
}
4664
}

Rubberduck.VBEEditor/ComManagement/WeakComSafe.cs

Lines changed: 13 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,26 @@
11
using System;
22
using System.Collections.Concurrent;
33
using System.Collections.Generic;
4-
using System.Runtime.CompilerServices;
54
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
65

76
#if DEBUG
87
using System.Diagnostics;
98
using System.Linq;
10-
using System.Runtime.InteropServices;
119
#endif
1210

1311
namespace Rubberduck.VBEditor.ComManagement
1412
{
15-
public class WeakComSafe : IComSafe
13+
public class WeakComSafe : ComSafeBase
1614
{
1715
//We use weak references to allow the GC to reclaim RCWs earlier if possible.
1816
private readonly ConcurrentDictionary<int, (DateTime insertTime, WeakReference<ISafeComWrapper> weakRef)> _comWrapperCache = new ConcurrentDictionary<int, (DateTime, WeakReference<ISafeComWrapper>)>();
1917

20-
#if DEBUG
21-
private IEnumerable<string> trace = null;
22-
#endif
23-
24-
public void Add(ISafeComWrapper comWrapper)
18+
public override void Add(ISafeComWrapper comWrapper)
2519
{
2620
if (comWrapper != null)
2721
{
2822
#if DEBUG
29-
trace = GetStackTrace(3, 3);
23+
Trace = GetStackTrace(3, 3);
3024
#endif
3125
_comWrapperCache.AddOrUpdate(
3226
GetComWrapperObjectHashCode(comWrapper),
@@ -42,20 +36,13 @@ public void Add(ISafeComWrapper comWrapper)
4236

4337
}
4438

45-
//We do not use GetHashCode because subclasses of SafeComWrapper<T> overwrite this method
46-
//and we need to distinguish between individual instances.
47-
private int GetComWrapperObjectHashCode(ISafeComWrapper comWrapper)
48-
{
49-
return RuntimeHelpers.GetHashCode(comWrapper);
50-
}
51-
52-
public bool TryRemove(ISafeComWrapper comWrapper)
39+
public override bool TryRemove(ISafeComWrapper comWrapper)
5340
{
5441
return !_disposed && comWrapper != null && _comWrapperCache.TryRemove(GetComWrapperObjectHashCode(comWrapper), out _);
5542
}
5643

5744
private bool _disposed;
58-
public void Dispose()
45+
protected override void Dispose(bool disposing)
5946
{
6047
if (_disposed)
6148
{
@@ -66,7 +53,7 @@ public void Dispose()
6653

6754
foreach (var weakReference in _comWrapperCache.Values)
6855
{
69-
if(weakReference.weakRef.TryGetTarget(out var comWrapper))
56+
if (weakReference.weakRef.TryGetTarget(out var comWrapper))
7057
{
7158
comWrapper.Dispose();
7259
}
@@ -76,75 +63,19 @@ public void Dispose()
7663
}
7764

7865
#if DEBUG
79-
/// <summary>
80-
/// Provide a serialized list of the COM Safe
81-
/// to make it easy to analyze what is inside
82-
/// the COM Safe at the different points of
83-
/// the session's lifetime.
84-
/// </summary>
85-
public void Serialize()
86-
{
87-
using (var stream = System.IO.File.AppendText($"comSafeOutput {DateTime.UtcNow:yyyyMMddhhmmss}.csv"))
88-
{
89-
stream.WriteLine("Ordinal\tKey\tCOM Wrapper Type\tWrapping Null?\tIUnknown Pointer Address\tLevel 1\tLevel 2\tLevel 3");
90-
var i = 0;
91-
foreach (var kvp in _comWrapperCache.OrderBy(kvp => kvp.Value.insertTime))
92-
{
93-
var line = kvp.Value.weakRef.TryGetTarget(out var target)
94-
? $"{i++}\t{kvp.Key}\t\"{target.GetType().FullName}\"\t\"{target.IsWrappingNullReference}\"\t\"{(target.IsWrappingNullReference ? "null" : GetPtrAddress(target.Target))}\"\t\"{string.Join("\"\t\"", trace)}\""
95-
: $"{i++}\t{kvp.Key}\t\"null\"\t\"null\"\t\"null\"\t\"{string.Join("\"\t\"", trace)}\"";
96-
stream.WriteLine(line);
97-
}
98-
}
99-
}
100-
101-
private static IEnumerable<string> GetStackTrace(int frames, int offset)
102-
{
103-
var list = new List<string>();
104-
var trace = new StackTrace();
105-
if ((trace.FrameCount - offset) < frames)
106-
{
107-
frames = (trace.FrameCount - offset);
108-
}
109-
110-
for (var i = 1; i <= frames; i++)
111-
{
112-
var frame = trace.GetFrame(i + offset);
113-
var typeName = frame.GetMethod().DeclaringType?.FullName ?? string.Empty;
114-
var methodName = frame.GetMethod().Name;
115-
116-
var qualifiedName = $"{typeName}{(typeName.Length > 0 ? "::" : string.Empty)}{methodName}";
117-
list.Add(qualifiedName);
118-
}
119-
return list;
120-
}
121-
122-
private static string GetPtrAddress(object target)
66+
protected override IDictionary<int, ISafeComWrapper> GetWrappers()
12367
{
124-
if (target == null)
125-
{
126-
return IntPtr.Zero.ToString();
127-
}
128-
129-
if (!Marshal.IsComObject(target))
130-
{
131-
return "Not a COM object";
132-
}
133-
134-
var pointer = IntPtr.Zero;
135-
try
136-
{
137-
pointer = Marshal.GetIUnknownForObject(target);
138-
}
139-
finally
68+
var dictionary = new Dictionary<int, ISafeComWrapper>();
69+
foreach (var kvp in _comWrapperCache.OrderBy(kvp => kvp.Value.insertTime))
14070
{
141-
if (pointer != IntPtr.Zero)
71+
if (!kvp.Value.weakRef.TryGetTarget(out var target))
14272
{
143-
Marshal.Release(pointer);
73+
target = null;
14474
}
75+
dictionary.Add(kvp.Key, target);
14576
}
14677

147-
return pointer.ToString();
78+
return dictionary;
14879
}
14980
#endif
15081
}

Rubberduck.VBEEditor/Rubberduck.VBEditor.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
</ItemGroup>
6060
<ItemGroup>
6161
<Compile Include="ComManagement\ComMessagePumper.cs" />
62+
<Compile Include="ComManagement\ComSafeBase.cs" />
6263
<Compile Include="ComManagement\TypeLibs\ITypeLibWrapper.cs" />
6364
<Compile Include="ComManagement\TypeLibs\ITypeLibWrapperProvider.cs" />
6465
<Compile Include="ComManagement\TypeLibs\IVBETypeLibsAPI.cs" />

0 commit comments

Comments
 (0)