|
3 | 3 | using System.Runtime.CompilerServices;
|
4 | 4 | using Rubberduck.VBEditor.SafeComWrappers.Abstract;
|
5 | 5 |
|
| 6 | +#if DEBUG |
| 7 | +using System.Linq; |
| 8 | +using System.Runtime.InteropServices; |
| 9 | +#endif |
| 10 | + |
6 | 11 | namespace Rubberduck.VBEditor.ComManagement
|
7 | 12 | {
|
8 | 13 | public class WeakComSafe : IComSafe
|
9 | 14 | {
|
10 | 15 | //We use weak references to allow the GC to reclaim RCWs earlier if possible.
|
11 |
| - private readonly ConcurrentDictionary<int, WeakReference<ISafeComWrapper>> _comWrapperCache = new ConcurrentDictionary<int, WeakReference<ISafeComWrapper>>(); |
12 |
| - |
| 16 | + private readonly ConcurrentDictionary<int, (DateTime insertTime, WeakReference<ISafeComWrapper> weakRef)> _comWrapperCache = new ConcurrentDictionary<int, (DateTime, WeakReference<ISafeComWrapper>)>(); |
13 | 17 |
|
14 | 18 | public void Add(ISafeComWrapper comWrapper)
|
15 | 19 | {
|
16 | 20 | if (comWrapper != null)
|
17 | 21 | {
|
18 | 22 | _comWrapperCache.AddOrUpdate(
|
19 | 23 | GetComWrapperObjectHashCode(comWrapper),
|
20 |
| - key => new WeakReference<ISafeComWrapper>(comWrapper), |
21 |
| - (key, value) => new WeakReference<ISafeComWrapper>(comWrapper)); |
| 24 | + key => (DateTime.UtcNow, new WeakReference<ISafeComWrapper>(comWrapper)), |
| 25 | + (key, value) => (DateTime.UtcNow, new WeakReference<ISafeComWrapper>(comWrapper))); |
22 | 26 | }
|
23 | 27 |
|
24 | 28 | }
|
@@ -47,13 +51,65 @@ public void Dispose()
|
47 | 51 |
|
48 | 52 | foreach (var weakReference in _comWrapperCache.Values)
|
49 | 53 | {
|
50 |
| - if(weakReference.TryGetTarget(out var comWrapper)) |
| 54 | + if(weakReference.weakRef.TryGetTarget(out var comWrapper)) |
51 | 55 | {
|
52 | 56 | comWrapper.Dispose();
|
53 | 57 | }
|
54 | 58 | }
|
55 | 59 |
|
56 | 60 | _comWrapperCache.Clear();
|
57 | 61 | }
|
| 62 | + |
| 63 | +#if DEBUG |
| 64 | + /// <summary> |
| 65 | + /// Provide a serialized list of the COM Safe |
| 66 | + /// to make it easy to analyze what is inside |
| 67 | + /// the COM Safe at the different points of |
| 68 | + /// the session's lifetime. |
| 69 | + /// </summary> |
| 70 | + public void Serialize() |
| 71 | + { |
| 72 | + using (var stream = System.IO.File.AppendText($"comSafeOutput {DateTime.UtcNow:yyyyMMddhhmmss}.csv")) |
| 73 | + { |
| 74 | + stream.WriteLine("Ordinal\tKey\tCOM Wrapper Type\tWrapping Null?\tIUnknown Pointer Address"); |
| 75 | + var i = 0; |
| 76 | + foreach (var kvp in _comWrapperCache.OrderBy(kvp => kvp.Value.insertTime)) |
| 77 | + { |
| 78 | + var line = kvp.Value.weakRef.TryGetTarget(out var target) |
| 79 | + ? $"{i++}\t{kvp.Key}\t\"{target.GetType().FullName}\"\t\"{target.IsWrappingNullReference}\"\t\"{(target.IsWrappingNullReference ? "null" : GetPtrAddress(target.Target))}\"" |
| 80 | + : $"{i++}\t{kvp.Key}\t\"null\"\t\"null\"\t\"null\""; |
| 81 | + stream.WriteLine(line); |
| 82 | + } |
| 83 | + } |
| 84 | + } |
| 85 | + |
| 86 | + private static string GetPtrAddress(object target) |
| 87 | + { |
| 88 | + if (target == null) |
| 89 | + { |
| 90 | + return IntPtr.Zero.ToString(); |
| 91 | + } |
| 92 | + |
| 93 | + if (!Marshal.IsComObject(target)) |
| 94 | + { |
| 95 | + return "Not a COM object"; |
| 96 | + } |
| 97 | + |
| 98 | + var pointer = IntPtr.Zero; |
| 99 | + try |
| 100 | + { |
| 101 | + pointer = Marshal.GetIUnknownForObject(target); |
| 102 | + } |
| 103 | + finally |
| 104 | + { |
| 105 | + if (pointer != IntPtr.Zero) |
| 106 | + { |
| 107 | + Marshal.Release(pointer); |
| 108 | + } |
| 109 | + } |
| 110 | + |
| 111 | + return pointer.ToString(); |
| 112 | + } |
| 113 | +#endif |
58 | 114 | }
|
59 | 115 | }
|
0 commit comments